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


116

我想将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配合使用,反之亦然。github.com/Pradeepkn/TextWithImage享受。
PradeepKN 2015年

Answers:


58

您可以通过继承NSTextAttachment和覆盖来更改rect 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
不知道为什么他们对此投下了反对票,这对我有帮助+1
Jasper 2014年

Y来源是字体的下标。请参阅下面的答案。
phatmann

4
Travis的答案是没有子类的更干净的解决方案。
SwampThingTom

有关更多详细信息,请查看@ mg-han的答案stackoverflow.com/a/45161058/280503(在我看来,它应该是该问题的选定答案。)
gardenofwine 17/09/10

191

您可以使用字体的capHeight。

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

迅速

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的分数部分取整。但是字体和图像通常很小,即使1px的差异也会使图像看起来像是未对齐的。所以我在除法之前应用了round函数。它将y值的小数部分设为.0或.5

在您的情况下,图像高度大于字体的capHeight。但是您可以使用相同的方式。偏移y值将为负。它将从基线的下方进行布局。

在此处输入图片说明


谢谢!!!!!!!!!!!!!!
Alex

107

尝试- [NSTextAttachment bounds]。无需子类化。

对于上下文,我渲染一个UILabel用作附件图像,然后像这样设置边界: attachment.bounds = CGRectMake(0, self.font.descender, attachment.image.size.width, attachment.image.size.height)标签图像中的文本基线和属性字符串中的文本基线按需要对齐。


只要您不需要缩放图像,此方法就起作用。
phatmann

12
对于Swift 3.0:attachment.bounds = CGRect(x: 0.0, y: self.font.descender, width: attachment.image!.size.width, height: attachment.image!.size.height)
Andy

很好,谢谢!不知道descenderUIFont 的属性!

61

我找到了一个完美的解决方案,虽然对我来说就像一个魅力,但是您必须自己尝试一下(可能常数取决于设备的分辨率,也许是其他任何因素;)

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。
2016年

2
下面是我用我的y计算:伸+(ABS(伸)+大写高度)/ 2 - iconHeight / 2

为什么+2为Y原点?
威廉·勒加特

@WilliamLeGate我真的不知道,只是尝试了一下,它适用于我测试过的所有字体大小(我需要的字体大小)..
borchero

真该死...这个答案太神奇了。
GGirotto

37

关于什么:

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 2015年

10

@Travis正确的是,偏移量是字体降序。如果还需要缩放图像,则需要使用NSTextAttachment的子类。以下是受本文启发的代码。我也把它作为要点发布。

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

如果您的提升量很大,并且想要像我一样居中图像(笔帽高度的中心),请尝试

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的计算如下图

在此处输入图片说明

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

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

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

7

我们可以在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


1

就我而言,调用sizeToFit()很有帮助。迅速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()
}

0

请使用-lineFrag.size.height / 5.0作为边界高度。这将使图像完全居中并与所有字体大小的文本对齐

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

-lineFrag.size.height/5.0是不正确的。相反,它是字体降序。
phatmann
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.