J'aimerais ajouter une image NSTextAttachment à 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 en haut du libellé textLabel de la cellule.
Comment puis-je changer le rect dans lequel l'attachement est dessiné?
Vous pouvez changer le recto en sous-classant NSTextAttachment
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 des Y à l’œil et, si vous modifiez la taille de la police ou de l’icône, vous souhaiterez probablement modifier l’origine des Y. Mais je ne pouvais pas trouver de meilleur moyen, sauf en plaçant l'icône dans une vue d'image séparée (ce qui a ses propres inconvénients).
Vous pouvez utiliser le capHeight de la police.
Objective-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 pièce jointe est rendue sur la ligne de base du texte. Et son axe y est inversé comme le système de coordonnées graphique principal. Si vous souhaitez déplacer l'image vers le haut, définissez le paramètre bounds.Origin.y
au positif.
L'image doit être alignée verticalement au centre avec la majuscule du texte. Nous devons donc définir le bounds.Origin.y
à (capHeight - imageHeight)/2
.
En évitant certains effets irréguliers 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 1 px fait que l’image semble mal alignée. J'ai donc appliqué la fonction round avant de diviser. Cela fait que la partie fraction de la valeur y soit 0,0 ou 0,5
Dans votre cas, la hauteur de l’image est plus grande que la majuscule de la police. Mais vous pouvez utiliser la même manière. La valeur de décalage y sera négative. Et il sera présenté à partir du bas de la ligne de base.
Essayez - [NSTextAttachment bounds]
. Aucun sous-classement requis.
Pour le contexte, je suis en train de rendre un UILabel
pour l'utiliser comme image de pièce jointe, puis je fixe les limites comme suit: 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 du texte dans la chaîne attribuée s'alignent comme souhaité .
J'ai trouvé une solution parfaite à ce problème, fonctionne comme un charme pour moi, cependant, vous devez l'essayer vous-même (la constante dépend probablement de la résolution de l'appareil et peut-être de ce que vous voulez;)
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 (grâce à CGRectIntegral
)
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
@ Travis a raison de dire que le décalage correspond au descendant de police. Si vous devez également redimensionner l'image, vous devrez utiliser une sous-classe de NSTextAttachment. Ci-dessous, le code qui a été inspiré par cet article . Je l'ai aussi posté en tant que Gist .
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
Si vous avez un très gros ascendant et que vous voulez centrer l'image (centre de la hauteur de la casquette) 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 de y est comme l'image ci-dessous
Notez que la valeur y est 0 car nous voulons que l’image passe de l’origine
Si vous voulez qu'il se trouve au milieu de l'étiquette entière, utilisez cette valeur y:
let y = -((font.ascender-font.descender)/2-image.size.height/2)
Nous pouvons créer 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 faire l'appel en envoyant le nom de l'image et la police:
let imageAttachment = NSTextAttachment.getCenteredImageAttachment(with: imageName,
and: youLabel?.font)
Et ensuite, ajoutez l'imageAttachment à la chaîne attribuée
Veuillez utiliser -lineFrag.size.height/5.0 pour la hauteur des limites. Cela centre exactement l'image et est 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;
}