将NSTextAttachment图像置于单行UILabel旁边的中心

143

我想在我的属性字符串中添加一个 NSTextAttachment 图像,并使其垂直居中。

我使用以下代码创建了我的字符串:

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

然而,该图片似乎与单元格的textLabel顶部对齐。

文本附件偏移问题屏幕截图

我该如何更改绘制附件的矩形区域?


我有一个类别类,可以将NSString与UIImage互相转换。https://github.com/Pradeepkn/TextWithImage 享受吧。 - PradeepKN
11个回答

276

你可以使用字体的capHeight属性。

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

Swift

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)

附件图像以文本基线为基础进行呈现。同时,它的y轴与核心绘图坐标系相反。如果您想将图像向上移动,请将bounds.origin.y设为正数。

图像应该与文本的capHeight垂直居中对齐。因此我们需要将bounds.origin.y设置为(capHeight - imageHeight)/2

为了避免图像出现一些锯齿状的效果,我们应该在y的小数部分四舍五入。但是,字体和图像通常很小,即使只有1像素的差异,也会让图像看起来错位。因此,在除法之前,我使用了round函数。它使得y值的小数部分为.0或.5

在您的情况下,图像高度大于字体的capHeight。但是您仍然可以使用同样的方法。偏移量y值将为负值。并且它将从基线以下布局。

enter image description here

参考资料:

Apple developer iOS文本编程指南


2
这个图示本身就值得一加一了!感谢分享。 - turingtested
“图片”背后的文本是什么? - CyberMew

117

尝试使用 - [NSTextAttachment bounds] 方法。不需要子类化。

为了上下文,我正在渲染一个 UILabel 以用作附件图像,然后设置边界框如下: attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height) 这样标签图像中文字的基线与属性字符串行中的文本对齐。


只要您不需要缩放图像,这将有效。 - phatmann
14
对于 Swift 3.0 版本: attachment.bounds = CGRect(x: 0.0, y: self.font.descender, width: attachment.image!.size.width, height: attachment.image!.size.height) 的翻译如下:attachment.bounds 表示附件的边界,CGRect 是一个结构体,用来表示矩形区域。这段代码中,使用了四个参数来初始化 CGRect:x 表示左上角横坐标,y 表示左上角纵坐标,width 表示宽度,height 表示高度。其中,x 设为 0.0,y 设为 self.font.descender,即字体 descender 的值,attachment.image!.size.width 表示附件图片的宽度,attachment.image!.size.height 表示附件图片的高度。 - Andrew
1
太棒了,谢谢!我不知道UIFont的descender属性! - Ben
如何渲染标签以用作附件图像? - frank61003

67
我找到了一个完美的解决方案,对我非常有效,但你需要自己尝试一下(可能常数取决于设备的分辨率和其他因素 :))。
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
}

应该能正常工作,并且不会有任何模糊的情况(感谢CGRectIntegral


谢谢发布这篇内容,它让我找到了一个相当好的方法。 我注意到你在y坐标计算中添加了一个有点神奇的2。 - Ben
2
这是我用于y计算的公式:下降高度+(绝对值下降高度+大写字母高度)/2-图标高度/2。 - Ben
为什么Y轴原点要加2? - William LeGate
@WilliamLeGate 我真的不知道,只是尝试了一下,对于我测试过的所有字体大小(我需要的那些)都有效。 - borchero
好厉害...这个答案太棒了。 - GGirotto
Xcode 12.5,IOS 14.5 textAttachment.bounds = CGRect(x: 0, y: font.descender - image.size.height / 2 + mid + 2, width: image.size.height, height: image.size.height).integral - app4g

65
你可以通过继承 NSTextAttachment 并重写 attachmentBoundsForTextContainer:proposedLineFragment:glyphPosition:characterIndex: 来改变矩形。 例如:
- (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;
}

这并不是一个完美的解决方案。您必须“凭眼”找出Y轴原点,如果更改字体或图标大小,则可能需要更改Y轴原点。但我找不到更好的方法,除非将图标放在单独的图像视图中(这也有其缺点)。


2
不知道为什么他们会踩这个,它对我很有帮助,所以我点赞。 - Jasper
Y轴原点是字体下行高度。请查看我的回答。 - phatmann
4
Travis的答案是不需要子类化的更简洁的解决方案。 - SwampThingTom
更多细节请查看 @mg-han 的回答 https://dev59.com/L18e5IYBdhLWcg3wFnPm#45161058 (在我看来,这应该是问题的最佳答案。) - gardenofwine

43

怎么样:

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

无需子类化


2
比使用self.font.descender更好(在运行iOS 8的iPhone 4s模拟器上具有默认值约为4)。-10看起来更接近于默认字体样式/大小的近似值。 - Kedar Paranjape

15

@Travis提到的偏移量是字体下行高度。如果你还需要缩放图像,你需要使用NSTextAttachment的子类。以下是代码,灵感来自于这篇文章。我也将其发布在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)
    }
}

使用方法如下:

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

10
我们可以在Swift 4中制作一个扩展,生成一个带有像这样居中图像的附件:
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
    }
}

然后您可以发送图像名称和字体进行呼叫:

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

然后将imageAttachment追加到attributedString中


10

如果您有一个非常大的上升高度,并且想像我一样将图片居中(在小写字母盖高度的中心位置),请尝试以下方法

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
}

y的计算如下图所示

enter image description here

请注意,y值为0,因为我们希望图像从原点向下移动

如果您希望它位于整个标签的中间,请使用此y值:

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

2
在我的情况下,调用sizeToFit()有所帮助。 在Swift 5.1中,
在你的自定义标签内:
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()
}

1

可以使用NSAttributedString.Key.baselineOffset属性来在NSMutbleAttributedString中垂直移动NSTextAttachment。


快速而简单。谢谢。 :) - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接