user1605810
user1605810

Reputation: 13

iOS UITextView rich text with tags

I want to implement a UITextView whose content consists of plain text and tags, where the tags are text with a rounded gray background.

I tried to use NSAttributedString, but it cannot set rounded background.

Any idea? Thanks in advance.

enter image description here

Upvotes: 0

Views: 55

Answers (2)

Can
Can

Reputation: 8571

From iOS 17 the textItemTag was added, and in iOS 18 the textHighlightColor and textHighlightStyle were added.

Adding textItemTag allows attaching a custom piece of information for your own usage (e.g a UUID of the tag), where the textHighlight ones are for adding a bordered background and foreground colors.

You can use them with AttributedString, or, preferably NSAttributedString if you intend to use it in conjunction with a UITextView:

// Some attributed string
let txt = NSMutableAttributedString(string: "Eat launch for ", attributes: [.font: UIFont.systemFont(ofSize: 16.0)])

// The tag
let tag = NSAttributedString(string: "24 Minutes", attributes: [
            .textItemTag: "time:24m", // Your custom tag
            .font: UIFont.systemFont(ofSize: 16.0),
            .textHighlightColorScheme: NSAttributedString.TextHighlightColorScheme.mint,
            .textHighlightStyle: NSAttributedString.TextHighlightStyle.default
        ])

// Join them together
txt.append(tag)

When implementing UITextViewDelegate you offer additional actions or respond to clicks using:

func textView(_ textView: UITextView, primaryActionFor textItem: UITextItem, defaultAction: UIAction) -> UIAction? {
    UIAction(handler: { (_) in print("Clicked on \(textItem.content)") })
}

Upvotes: 1

saucym
saucym

Reputation: 61

You can implement by generating an Image using CoreGraphics and wrapping it inside a NSTextAttachment.

For example, here's an implementation as an extension of String:

extension String {
    func generateImage(_ size: CGSize, 
                       textFont: UIFont = .systemFont(ofSize: 16),
                       textColor: UIColor = .white, 
                       fillColor: UIColor = .brown) -> NSAttributedString {
        let format = UIGraphicsImageRendererFormat()
        format.scale = UIScreen.main.scale
        let render = UIGraphicsImageRenderer(size: size, format: format)
        
        let image = render.image { context in
            let ellipsePath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: size.height / 2).cgPath
            context.cgContext.setFillColor(fillColor.cgColor)
            context.cgContext.addPath(ellipsePath)
            context.cgContext.fillPath()
            let attributed = NSAttributedString(string: self, attributes: [.font: textFont, .foregroundColor: textColor])
            let textSize = attributed.size()
            attributed.draw(at: CGPoint(x: (size.width - textSize.width) / 2, y: (size.height - textSize.height) / 2))
        }
        let attachment = NSTextAttachment(data: nil, ofType: nil)
        attachment.image = image
        attachment.bounds = .init(x: 0, y: -9.3125, width: size.width, height: size.height)
        attachment.lineLayoutPadding = 5
        return .init(attachment: attachment)
    }
}

Then when you want to use it:


// Your UITextView with an attributed string
let view = UITextView()
view.attributedText = testAttributedString()

// An example usage
func testAttributedString() -> NSAttributedString {
    let test = NSMutableAttributedString()
    test.append(.init(string: "How"))
    test.append("are".generateImage(.init(width: 60, height: 30)))
    test.append(.init(string: "you"))
    return test
}

Here's an example on how it looks

Upvotes: 1

Related Questions