Zentrieren Sie das NSTextAttachment-Bild neben dem einzeiligen UILabel

116

Ich möchte ein NSTextAttachmentBild an meine zugewiesene Zeichenfolge anhängen und es vertikal zentrieren lassen.

Ich habe den folgenden Code verwendet, um meine Zeichenfolge zu erstellen:

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];

Das Bild scheint jedoch am oberen Rand der Zelle ausgerichtet zu sein textLabel.

Screenshot des Versatzproblems des Textanhangs

Wie kann ich das Rechteck ändern, in dem der Anhang gezeichnet ist?

Sean Danzeiser
quelle
Ich habe eine Kategorieklasse für NSString mit UIImage und umgekehrt. github.com/Pradeepkn/TextWithImage Viel Spaß.
PradeepKN

Antworten:

58

Sie können das Rect durch Unterklassen NSTextAttachmentund Überschreiben ändern attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex:. Beispiel:

- (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;
}

Es ist keine perfekte Lösung. Sie müssen den Y-Ursprung „mit dem Auge“ herausfinden. Wenn Sie die Schriftart oder die Symbolgröße ändern, möchten Sie wahrscheinlich den Y-Ursprung ändern. Aber ich konnte keinen besseren Weg finden, als das Symbol in einer separaten Bildansicht zu platzieren (was seine eigenen Nachteile hat).

Rob Mayoff
quelle
2
Keine Ahnung, warum sie dies abgelehnt haben, es hat mir so geholfen +1
Jasper
Der Y-Ursprung ist der Schriftabstieg. Siehe meine Antwort unten.
Phatmann
4
Travis 'Antwort ist eine sauberere Lösung ohne Unterklassen.
SwampThingTom
Weitere Informationen finden Sie unter @ mg-hans Antwort stackoverflow.com/a/45161058/280503 (Meiner Meinung nach sollte dies die ausgewählte Antwort auf die Frage sein.)
gardenofwine
191

Sie können die capHeight der Schriftart verwenden.

Ziel 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];

Schnell

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)

Das Anhangsbild wird auf der Grundlinie des Textes gerendert. Und die y-Achse ist wie das Kerngrafikkoordinatensystem umgekehrt. Wenn Sie das Bild nach oben verschieben möchten, setzen Sie den Wert bounds.origin.yauf positiv.

Das Bild sollte vertikal mittig mit der Höhe des Texts ausgerichtet sein. Also müssen wir die festlegen bounds.origin.yzu (capHeight - imageHeight)/2.

Um einen gezackten Effekt auf das Bild zu vermeiden, sollten wir den Bruchteil des y runden. Aber Schriftarten und Bilder sind normalerweise klein. Selbst ein Unterschied von 1 Pixel lässt das Bild falsch ausgerichtet aussehen. Also habe ich die Rundungsfunktion vor dem Teilen angewendet. Dadurch wird der Bruchteil des y-Werts auf .0 oder .5 gesetzt

In Ihrem Fall ist die Bildhöhe größer als die Großbuchstaben der Schriftart. Aber Sie können den gleichen Weg verwenden. Der Offset-y-Wert ist negativ. Und es wird von unten unter der Grundlinie angelegt.

Geben Sie hier die Bildbeschreibung ein

MG Han
quelle
DANKE!!!!!!!!!!!!!!
Alex
107

Versuchen Sie es - [NSTextAttachment bounds]. Keine Unterklasse erforderlich.

Für den Kontext rendere ich ein UILabelzur Verwendung als Anhangsbild attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height)und setze dann die Grenzen wie folgt : und die Basislinien des Textes innerhalb des Beschriftungsbilds und des Textes in der zugeordneten Zeichenfolge werden wie gewünscht ausgerichtet.

Travis
quelle
Dies funktioniert so lange, wie Sie das Bild nicht skalieren müssen.
Phatmann
12
Für Swift 3.0:attachment.bounds = CGRect(x: 0.0, y: self.font.descender, width: attachment.image!.size.width, height: attachment.image!.size.height)
Andy
Super, danke! descenderWusste nichts über das Eigentum von UIFont!
Ben
61

Ich habe eine perfekte Lösung dafür gefunden, funktioniert aber wie ein Zauber für mich, aber Sie müssen es selbst ausprobieren (wahrscheinlich hängt die Konstante von der Auflösung des Geräts ab und vielleicht was auch immer;)

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
}

Sollte funktionieren und sollte in keiner Weise verschwommen sein (danke an CGRectIntegral)

Borchero
quelle
Vielen Dank für die Veröffentlichung, es führte mich zu einem ziemlich guten Ansatz. Mir ist aufgefallen, dass Sie Ihrer y-Koordinatenberechnung eine etwas magische 2 hinzufügen.
Ben
2
Folgendes habe ich für meine y-Berechnung verwendet: absteigend + (abs (absteigend) + capHeight) / 2 - iconHeight / 2
Ben
Warum die +2 für den Y-Ursprung?
William LeGate
@ WilliamLeGate Ich weiß es wirklich nicht, habe es einfach ausprobiert und es hat für alle Schriftgrößen funktioniert, auf die ich getestet habe (die, die ich brauchte).
Borchero
Verdammt ... Diese Antwort ist unglaublich.
GGirotto
37

Wie wäre es mit:

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);

Keine Unterklasse erforderlich

Jakub Truhlář
quelle
2
Funktioniert besser als die Verwendung von self.font.descender (mit einem Standardwert von ~ 4 auf dem iPhone 4s-Simulator unter iOS 8). -10 scheint eine bessere Annäherung für den Standardschriftstil / die Standardschriftgröße zu sein.
Kedar Paranjape
10

@Travis ist richtig, dass der Offset der Font-Absender ist. Wenn Sie das Bild auch skalieren müssen, müssen Sie eine Unterklasse von NSTextAttachment verwenden. Unten finden Sie den Code, der von diesem Artikel inspiriert wurde . Ich habe es auch als Kern gepostet .

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)
    }
}

Verwenden Sie wie folgt:

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
quelle
10

Wenn Sie einen sehr großen Aufstieg haben und das Bild (Mitte der Kappenhöhe) wie ich zentrieren möchten, versuchen Sie dies

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
}

Die y-Berechnung ist wie im Bild unten

Geben Sie hier die Bildbeschreibung ein

Beachten Sie, dass der y-Wert 0 ist, da das Bild vom Ursprung nach unten verschoben werden soll

Wenn Sie möchten, dass es sich in der Mitte des gesamten Etiketts befindet. Verwenden Sie diesen y-Wert:

let y = -((font.ascender-font.descender)/2-image.size.height/2)
IndyZa
quelle
7

Wir können in Swift 4 eine Erweiterung erstellen, die einen Anhang mit einem zentrierten Bild wie diesem generiert:

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
    }
}

Dann können Sie den Anruf tätigen und den Namen des Bildes und die Schriftart senden:

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

Fügen Sie dann das imageAttachment an den attributedString an

Oscar
quelle
1

In meinem Fall hat das Aufrufen von sizeToFit () geholfen. In kurzer Zeit 5.1

In Ihrem benutzerdefinierten Etikett:

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
quelle
0

Bitte verwenden Sie -lineFrag.size.height / 5.0 für die Grenzhöhe. Dadurch wird das Bild genau zentriert und für alle Schriftgrößen an Text ausgerichtet

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
quelle
-lineFrag.size.height/5.0das ist nicht richtig. Stattdessen ist es der Font-Absender.
Phatmann