Search code examples
swiftnsattributedstringios15nstextattachment

NSTextAttachment.bounds does not work on iOS 15


To add an image attachment to an attributed string I used the following code that works fine before iOS 15:

let textAttachment = NSTextAttachment()
textAttachment.image = UIImage(named:"imageName")!
textAttachment.bounds = CGSize(origin: CGPoint(x: x, y: y), size: CGSize(width: width, height: height))
let textAttachmentString = NSMutableAttributedString(attachment: textAttachment)

let attributedText = NSMutableAttributedString(string: "base text", attributes: [:])
attributedText.append(textAttachmentString)

The problem is that after iOS 15 textAttachment.bounds.origin.x does not work: there is no space between text/image and the origin of the attachment is unchanged.

Probably NSTextAttachmentViewProvider should be used, but I don't know how to procede.


Solution

  • A while back I got around the problem with an inelegant workaround, I wrote a function that inserts an empty text attachment into the content to create an horizontal space between the text and the icon:

    func addIconToText(_ toText: NSAttributedString, icon: UIImage, iconOrigin: CGPoint, iconSize: CGSize, atEnd: Bool)->NSAttributedString{
        // Inserts a textAttachment containing the given icon into the attributed string (at the beginning or at the end of the text depending on the value of atEnd)
        
        // Constructs an empty text attachment to separate the icon from the text instead of using NSTextAttachment.bounds.origin.x because it does not work with iOS 15
        let iconTextAttachment = NSTextAttachment(data: icon.withRenderingMode(.alwaysOriginal).pngData(), ofType: "public.png")
        iconTextAttachment.bounds = CGRect(origin: CGPoint(x: 0, y: iconOrigin.y), size: iconSize)
        let iconString = NSMutableAttributedString(attachment:  iconTextAttachment)
        
        let emptyIconSize = CGSize(width: abs(iconOrigin.x), height: abs(iconOrigin.y))
        let emptyIconTextAttachment = NSTextAttachment(image: UIGraphicsImageRenderer(size: emptyIconSize).image(actions: {
            con in
            UIColor.clear.setFill()
            con.fill(CGRect(origin: .zero, size: emptyIconSize))
        }))
        emptyIconTextAttachment.bounds = CGRect(origin: .zero, size: emptyIconSize)
        let emptyIconString = NSMutableAttributedString(attachment: emptyIconTextAttachment)
        
        var finalString: NSMutableAttributedString!
        if atEnd{
            finalString = NSMutableAttributedString(attributedString: toText)
            finalString.append(emptyIconString)
            finalString.append(iconString)
        }else{
            finalString = NSMutableAttributedString(attributedString: iconString)
            finalString.append(emptyIconString)
            finalString.append(toText)
        }
        
        return finalString
    }