Wyśrodkuj obraz NSTextAttachment obok pojedynczej linii UILabel

117

Chciałbym dołączyć NSTextAttachmentobraz do mojego przypisanego ciągu i ustawić go w pionie.

Użyłem następującego kodu, aby utworzyć mój ciąg:

NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:DDLocalizedString(@"title.upcomingHotspots") attributes:attrs];
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
attachment.image = [[UIImage imageNamed:@"help.png"] imageScaledToFitSize:CGSizeMake(14.f, 14.f)];
cell.textLabel.attributedText = [str copy];

Jednak obraz wydaje się być wyrównany do górnej części komórki textLabel.

zrzut ekranu problemu z przesunięciem załącznika tekstu

Jak mogę zmienić prostotę, w której jest narysowany załącznik?

Sean Danzeiser
źródło
Mam klasę kategorii do posiadania NSString z UIImage i vice versa. github.com/Pradeepkn/TextWithImage Ciesz się.
PradeepKN

Odpowiedzi:

59

Możesz zmienić prostokąt przez podklasę NSTextAttachmenti nadpisanie attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex:. Przykład:

- (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer proposedLineFragment:(CGRect)lineFrag glyphPosition:(CGPoint)position characterIndex:(NSUInteger)charIndex {
    CGRect bounds;
    bounds.origin = CGPointMake(0, -5);
    bounds.size = self.image.size;
    return bounds;
}

To nie jest idealne rozwiązanie. Musisz znaleźć punkt początkowy Y „na oko”, a jeśli zmienisz czcionkę lub rozmiar ikony, prawdopodobnie będziesz chciał zmienić początek Y. Ale nie mogłem znaleźć lepszego sposobu, poza umieszczeniem ikony w osobnym widoku obrazu (który ma swoje wady).

rob mayoff
źródło
2
Nie mam pojęcia, dlaczego odrzucili to głosowanie, pomogło mi to +1
Jasper,
Początek Y to opadanie czcionki. Zobacz moją odpowiedź poniżej.
phatmann
4
Odpowiedź Travisa to czystsze rozwiązanie bez podklas.
SwampThingTom
Aby uzyskać więcej informacji, sprawdź odpowiedź @ mg-han stackoverflow.com/a/45161058/280503 (Moim zdaniem powinna to być wybrana odpowiedź na pytanie.)
gardenofwine
191

Możesz użyć capHeight czcionki.

Cel C

NSTextAttachment *icon = [[NSTextAttachment alloc] init];
UIImage *iconImage = [UIImage imageNamed:@"icon.png"];
[icon setBounds:CGRectMake(0, roundf(titleFont.capHeight - iconImage.size.height)/2.f, iconImage.size.width, iconImage.size.height)];
[icon setImage:iconImage];
NSAttributedString *iconString = [NSAttributedString attributedStringWithAttachment:icon];
[titleText appendAttributedString:iconString];

Szybki

let iconImage = UIImage(named: "icon.png")!
var icon = NSTextAttachment()
icon.bounds = CGRect(x: 0, y: (titleFont.capHeight - iconImage.size.height).rounded() / 2, width: iconImage.size.width, height: iconImage.size.height)
icon.image = iconImage
let iconString = NSAttributedString(attachment: icon)
titleText.append(iconString)

Obraz załącznika jest renderowany na linii bazowej tekstu. Jego oś y jest odwrócona, podobnie jak układ współrzędnych grafiki rdzenia. Jeśli chcesz przesunąć obraz w górę, ustaw bounds.origin.ypozytyw.

Obraz powinien być wyrównany w pionie do środka z górną wysokością tekstu. Więc musimy ustawić bounds.origin.yto (capHeight - imageHeight)/2.

Unikając efektu postrzępienia na obrazie, powinniśmy zaokrąglić część ułamkową y. Ale czcionki i obrazy są zwykle małe, nawet różnica 1 piksela sprawia, że ​​obraz wygląda na źle wyrównany. Więc zastosowałem funkcję zaokrąglania przed podzieleniem. Sprawia, że ​​ułamek jest częścią wartości y na .0 lub .5

W twoim przypadku wysokość obrazu jest większa niż capHeight czcionki. Ale możesz użyć tego samego sposobu. Wartość przesunięcia y będzie ujemna. I zostanie to ułożone od dołu linii bazowej.

wprowadź opis obrazu tutaj

MG Han
źródło
DZIĘKUJĘ CI!!!!!!!!!!!!!!
Alex
107

Spróbuj - [NSTextAttachment bounds]. Nie jest wymagana żadna podklasa.

Dla kontekstu renderuję a UILabeldo użycia jako obraz załącznika, a następnie ustawiam granice w ten sposób: attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height)i linie bazowe tekstu w obrazie etykiety i tekstu w przypisanej linii ciągów zgodnie z potrzebami.

Travis
źródło
Działa to, o ile nie musisz skalować obrazu.
phatmann
12
Dla Swift 3.0:attachment.bounds = CGRect(x: 0.0, y: self.font.descender, width: attachment.image!.size.width, height: attachment.image!.size.height)
Andy
Wielkie dzieki! Nie wiedziałem o descenderwłasności UIFont!
Ben
61

Znalazłem na to idealne rozwiązanie, dla mnie działa jak urok, jednak trzeba to wypróbować samemu (pewnie stała zależy od rozdzielczości urządzenia i może cokolwiek;)

func textAttachment(fontSize: CGFloat) -> NSTextAttachment {
    let font = UIFont.systemFontOfSize(fontSize) //set accordingly to your font, you might pass it in the function
    let textAttachment = NSTextAttachment()
    let image = //some image
    textAttachment.image = image
    let mid = font.descender + font.capHeight
    textAttachment.bounds = CGRectIntegral(CGRect(x: 0, y: font.descender - image.size.height / 2 + mid + 2, width: image.size.width, height: image.size.height))
    return textAttachment
}

Powinien działać i nie powinien być w żaden sposób rozmyty (dzięki CGRectIntegral)

borchero
źródło
Dzięki za opublikowanie tego, doprowadziło mnie to do całkiem niezłego podejścia. Zauważyłem, że dodajesz nieco magiczne 2 do obliczenia współrzędnej y.
Ben
2
Oto, czego użyłem do obliczenia y: descender + (abs (descender) + capHeight) / 2 - iconHeight / 2
Ben
Dlaczego +2 dla pochodzenia Y?
William LeGate,
@WilliamLeGate Naprawdę nie wiem, po prostu go wypróbowałem i działało ze wszystkimi rozmiarami czcionek, które testowałem (te, których potrzebowałem) ..
borchero
Dobra cholera ... Ta odpowiedź jest niesamowita.
GGirotto
38

Co powiesz na:

CGFloat offsetY = -10.0;

NSTextAttachment *attachment = [NSTextAttachment new];
attachment.image = image;
attachment.bounds = CGRectMake(0.0, 
                               offsetY, 
                               attachment.image.size.width, 
                               attachment.image.size.height);

Brak konieczności tworzenia podklas

Jakub Truhlář
źródło
2
Działa lepiej niż użycie self.font.descender (które ma domyślną wartość ~ 4 na symulatorze iPhone'a 4s z systemem iOS 8). -10 wygląda na lepsze przybliżenie domyślnego stylu / rozmiaru czcionki.
Kedar Paranjape
10

@Travis ma rację, że przesunięcie jest zmniejszeniem czcionki. Jeśli chcesz również przeskalować obraz, będziesz musiał użyć podklasy NSTextAttachment. Poniżej znajduje się kod, którego inspiracją był ten artykuł . Opublikowałem to również jako sedno .

import UIKit

class ImageAttachment: NSTextAttachment {
    var verticalOffset: CGFloat = 0.0

    // To vertically center the image, pass in the font descender as the vertical offset.
    // We cannot get this info from the text container since it is sometimes nil when `attachmentBoundsForTextContainer`
    // is called.

    convenience init(_ image: UIImage, verticalOffset: CGFloat = 0.0) {
        self.init()
        self.image = image
        self.verticalOffset = verticalOffset
    }

    override func attachmentBoundsForTextContainer(textContainer: NSTextContainer, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {
        let height = lineFrag.size.height
        var scale: CGFloat = 1.0;
        let imageSize = image!.size

        if (height < imageSize.height) {
            scale = height / imageSize.height
        }

        return CGRect(x: 0, y: verticalOffset, width: imageSize.width * scale, height: imageSize.height * scale)
    }
}

Użyj w następujący sposób:

var text = NSMutableAttributedString(string: "My Text")
let image = UIImage(named: "my-image")!
let imageAttachment = ImageAttachment(image, verticalOffset: myLabel.font.descender)
text.appendAttributedString(NSAttributedString(attachment: imageAttachment))
myLabel.attributedText = text
phatmann
źródło
10

Jeśli masz bardzo dużą ascendencję i chcesz wyśrodkować obraz (środek wysokości kapelusza), tak jak ja, spróbuj tego

let attachment: NSTextAttachment = NSTextAttachment()
attachment.image = image
if let image = attachment.image{
    let y = -(font.ascender-font.capHeight/2-image.size.height/2)
    attachment.bounds = CGRect(x: 0, y: y, width: image.size.width, height: image.size.height).integral
}

Obliczenie y jest takie, jak na poniższym obrazku

wprowadź opis obrazu tutaj

Zwróć uwagę, że wartość y wynosi 0, ponieważ chcemy, aby obraz przesunął się w dół od początku

Jeśli chcesz, aby znajdował się pośrodku całej etykiety, użyj tej wartości y:

let y = -((font.ascender-font.descender)/2-image.size.height/2)
IndyZa
źródło
7

W Swift 4 możemy zrobić rozszerzenie, które generuje załącznik z wyśrodkowanym obrazem, takim jak ten:

extension NSTextAttachment {
    static func getCenteredImageAttachment(with imageName: String, and 
    font: UIFont?) -> NSTextAttachment? {
        let imageAttachment = NSTextAttachment()
    guard let image = UIImage(named: imageName),
        let font = font else { return nil }

    imageAttachment.bounds = CGRect(x: 0, y: (font.capHeight - image.size.height).rounded() / 2, width: image.size.width, height: image.size.height)
    imageAttachment.image = image
    return imageAttachment
    }
}

Następnie możesz nawiązać połączenie, wysyłając nazwę obrazu i czcionkę:

let imageAttachment = NSTextAttachment.getCenteredImageAttachment(with: imageName,
                                                                   and: youLabel?.font)

Następnie dołącz atrybut imageAttachment do atrybutu attributeString

Oscar
źródło
1

W moim przypadku pomogło wywołanie sizeToFit (). W szybkim 5.1

Wewnątrz Twojej etykiety niestandardowej:

func updateUI(text: String?) {
    guard let text = text else {
        attributedText = nil
        return
    }

    let attributedString = NSMutableAttributedString(string:"")

    let textAttachment = NSTextAttachment ()
    textAttachment.image = image

    let sizeSide: CGFloat = 8
    let iconsSize = CGRect(x: CGFloat(0),
                           y: (font.capHeight - sizeSide) / 2,
                           width: sizeSide,
                           height: sizeSide)
    textAttachment.bounds = iconsSize

    attributedString.append(NSAttributedString(attachment: textAttachment))
    attributedString.append(NSMutableAttributedString(string: text))
    attributedText = attributedString

    sizeToFit()
}
Reimond Hill
źródło
0

Proszę użyć -lineFrag.size.height / 5.0 dla wysokości granic. Powoduje to dokładne wyśrodkowanie obrazu i wyrównanie z tekstem dla wszystkich rozmiarów czcionek

override func attachmentBoundsForTextContainer(textContainer: NSTextContainer, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect
{
    var bounds:CGRect = CGRectZero

    bounds.size = self.image?.size as CGSize!
    bounds.origin = CGPointMake(0, -lineFrag.size.height/5.0);

    return bounds;
}
Harish Kumar Kailas
źródło
-lineFrag.size.height/5.0nie jest poprawne. Zamiast tego jest to descender czcionki.
phatmann