Centrer l'image NSTextAttachment à côté de UILabel d'une seule ligne

117

Je voudrais ajouter une NSTextAttachmentimage à ma chaîne attribuée et la centrer verticalement.

J'ai utilisé le code suivant pour créer ma chaîne:

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

Cependant, l'image semble s'aligner sur le haut de la cellule textLabel.

Capture d'écran du problème de décalage de la pièce jointe au texte

Comment puis-je changer le rect dans lequel la pièce jointe est dessinée?

Sean Danzeiser
la source
J'ai une classe de catégorie pour avoir NSString avec UIImage et vice versa. github.com/Pradeepkn/TextWithImage Profitez.
PradeepKN

Réponses:

59

Vous pouvez changer le rect en sous NSTextAttachment-classant et en remplaçant attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex:. Exemple:

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

Ce n'est pas une solution parfaite. Vous devez déterminer l'origine Y «à l'œil nu» et si vous changez la police ou la taille de l'icône, vous voudrez probablement changer l'origine Y. Mais je n'ai pas pu trouver de meilleur moyen, sauf en mettant l'icône dans une vue d'image séparée (ce qui a ses propres inconvénients).

Rob Mayoff
la source
2
Je ne sais pas pourquoi ils ont voté contre cela, cela m'a tellement aidé +1
Jasper
L'origine Y est le descendant de la police. Voir ma réponse ci-dessous.
phatmann
4
La réponse de Travis est une solution plus propre sans sous-classification.
SwampThingTom
Pour plus de détails, consultez la réponse de @ mg-han's stackoverflow.com/a/45161058/280503 (À mon avis, cela devrait être la réponse sélectionnée à la question.)
gardenofwine
192

Vous pouvez utiliser le capHeight de la police.

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

Rapide

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)

L'image de la pièce jointe est rendue sur la ligne de base du texte. Et l'axe y est inversé comme le système de coordonnées graphiques de base. Si vous souhaitez déplacer l'image vers le haut, définissez le bounds.origin.ysur positif.

L'image doit être alignée verticalement au centre du capHeight du texte. Nous devons donc définir le bounds.origin.ysur (capHeight - imageHeight)/2.

En évitant un effet irrégulier sur l'image, nous devrions arrondir la partie fraction de y. Mais les polices et les images sont généralement petites, même une différence de 1px donne à l'image un aspect mal aligné. J'ai donc appliqué la fonction round avant de diviser. Il rend la fraction partie de la valeur y à 0,0 ou 0,5

Dans votre cas, la hauteur de l'image est plus grande que le capHeight de la police. Mais vous pouvez utiliser la même manière. La valeur du décalage y sera négative. Et il sera disposé à partir du dessous de la ligne de base.

entrez la description de l'image ici

MG Han
la source
MERCI!!!!!!!!!!!!!!
Alex le
107

Essayez - [NSTextAttachment bounds]. Aucun sous-classement requis.

Pour le contexte, je suis en UILabeltrain de rendre un pour l'utiliser comme image de pièce jointe, puis de définir les limites comme ceci: attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height)et les lignes de base du texte dans l'image de l'étiquette et le texte dans la chaîne attribuée s'alignent comme souhaité.

Travis
la source
Cela fonctionne tant que vous n'avez pas besoin de redimensionner l'image.
phatmann
12
Pour 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 merci! Je ne connaissais pas la descenderpropriété d'UIFont!
Ben
61

J'ai trouvé une solution parfaite à cela, cela fonctionne comme un charme pour moi, mais vous devez l'essayer vous-même (probablement la constante dépend de la résolution de l'appareil et peut-être de tout autre chose;)

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
}

Devrait fonctionner et ne devrait en aucun cas être flou (merci à CGRectIntegral)

borchero
la source
Merci d'avoir posté ceci, cela m'a conduit à une assez bonne approche. J'ai remarqué que vous ajoutez un 2 quelque peu magique à votre calcul de coordonnées y.
Ben
2
Voici ce que j'ai utilisé pour mon calcul y: descender + (abs (descender) + capHeight) / 2 - iconHeight / 2
Ben
Pourquoi le +2 pour l'origine Y?
William LeGate
@WilliamLeGate Je ne sais vraiment pas, j'ai juste essayé et cela a fonctionné pour toutes les tailles de police que j'ai testées (celles dont j'avais besoin) ..
borchero
Bon putain ... Cette réponse est incroyable.
GGirotto
38

Qu'en est-il de:

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

Aucun sous-classement nécessaire

Jakub Truhlář
la source
2
Fonctionne mieux que d'utiliser self.font.descender (qui a une valeur par défaut de ~ 4 sur le simulateur iPhone 4s exécutant iOS 8). -10 semble être une meilleure approximation pour le style / taille de police par défaut.
Kedar Paranjape
10

@Travis a raison de dire que le décalage est le descendant de la police. Si vous avez également besoin de mettre l'image à l'échelle, vous devrez utiliser une sous-classe de NSTextAttachment. Vous trouverez ci-dessous le code, inspiré de cet article . Je l'ai également posté comme un élément essentiel .

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

Utilisez comme suit:

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
la source
10

Si vous avez un très grand ascendant et que vous souhaitez centrer l'image (centre de la hauteur du bonnet) comme moi, essayez ceci

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
}

Le calcul y est comme l'image ci-dessous

entrez la description de l'image ici

Notez que la valeur y est 0 car nous voulons que l'image se décale de l'origine

Si vous voulez qu'il soit au milieu de l'étiquette entière, utilisez cette valeur y:

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

Nous pouvons faire une extension dans swift 4 qui génère une pièce jointe avec une image centrée comme celle-ci:

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

Ensuite, vous pouvez passer l'appel en envoyant le nom de l'image et la police:

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

Et puis ajoutez l'imageAttachment à l'attributString

oscar
la source
1

Dans mon cas, l'appel de sizeToFit () m'a aidé. Dans Swift 5.1

À l'intérieur de votre étiquette personnalisée:

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
la source
0

Veuillez utiliser -lineFrag.size.height / 5.0 pour la hauteur des limites. Cela centre exactement l'image et aligné avec le texte pour toutes les tailles de polices

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
la source
-lineFrag.size.height/5.0n'est pas correcte. Au lieu de cela, c'est le descendeur de police.
phatmann