Reputation: 13
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.
Upvotes: 0
Views: 55
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
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