Center NSTextAttachment image next to single line UILabel

后端 未结 10 1096
太阳男子
太阳男子 2020-11-30 16:51

I\'d like to append an NSTextAttachment image to my attributed string and have it centered vertically.

I\'ve used the following code to create my string

相关标签:
10条回答
  • 2020-11-30 17:28

    If you have a very large ascendent and want to center the image (center of the cap height) like me try this

    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
    }
    

    The y calculation is as the picture below

    Note that the y value is 0 because we want the image to shift down from the origin

    If you want it to be in the middle of the whole label.Use this y value:

    let y = -((font.ascender-font.descender)/2-image.size.height/2)
    
    0 讨论(0)
  • 2020-11-30 17:32

    @Travis is correct that the offset is the font descender. If you also need to scale the image, you will need to use a subclass of NSTextAttachment. Below is the code, which was inspired by this article. I also posted it as a 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)
        }
    }
    

    Use as follows:

    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
    
    0 讨论(0)
  • 2020-11-30 17:34

    You can use the capHeight of the font.

    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)
    

    The attachment image is rendered on the baseline of the text. And the y axis of it is reversed like the core graphics coordinate system. If you want to move the image upward, set the bounds.origin.y to positive.

    The image should be aligned vertically center with the capHeight of the text. So we need to set the bounds.origin.y to (capHeight - imageHeight)/2.

    Avoiding some jagged effect on the image, we should round the fraction part of the y. But fonts and images are usually small, even 1px difference makes the image looks like misaligned. So I applied the round function before dividing. It makes the fraction part of the y value to .0 or .5

    In your case, the image height is larger than the capHeight of the font. But you can use the same way. The offset y value will be negative. And it will be laid out from the below of the baseline.

    0 讨论(0)
  • 2020-11-30 17:34

    We can make an extension in swift 4 that generates an attachment with a centered image like this one:

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

    Then you can make the call sending the name of the image and the font:

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

    And then append the imageAttachment to the attributedString

    0 讨论(0)
提交回复
热议问题