I have this code from a post on SO, I cannot find the original link, so I do deeply apologize.
Essentially, this code works for everything I need to do in a UITextView, multiple links with underline. However, when I try to add the Bold attribute, I am unable to get that to work.
import UIKit
extension UITextView {
func buildLink(originalText: String, hyperLinks: [String: String]) {
let style = NSMutableParagraphStyle()
style.alignment = .left
let attributedOriginalText = NSMutableAttributedString(string: originalText)
for (hyperLink, urlString) in hyperLinks {
let linkRange = attributedOriginalText.mutableString.range(of: hyperLink)
let fullRange = NSRange(location: 0, length: attributedOriginalText.length)
attributedOriginalText.addAttribute(.font, value: UIFont(name: "RobotoCondensed-Bold", size: 12.0)!, range: linkRange) /// this is the non functioning line
attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: urlString, range: linkRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.font, value: UIFont(name: "RobotoCondensed-Regular", size 12.0)!, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.darkText, range: fullRange)
}
self.linkTextAttributes = [
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue
]
self.attributedText = attributedOriginalText
}
}
If I also put the bold attribute in the linkTextAttributes array, it also doesn't work.
scratching my head on this.
The problem is that you are applying the regular (NOT BOLD) text style to fullRange AFTER the bold has been applied to the linkRange.
for (hyperLink, urlString) in hyperLinks {
let linkRange = attributedOriginalText.mutableString.range(of: hyperLink)
let fullRange = NSRange(location: 0, length: attributedOriginalText.length)
/// Here the correct attribute is applied for linkRange
attributedOriginalText.addAttribute(.font, value: UIFont(name: "RobotoCondensed-Bold", size: 12.0)!, range: linkRange) /// this is the non functioning line
attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: urlString, range: linkRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange)
/// Here what was done correctly above gets overwritten
attributedOriginalText.addAttribute(NSAttributedString.Key.font, value: UIFont(name: "RobotoCondensed-Regular", size 12.0)!, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.darkText, range: fullRange)
}
This can be simplified to following -
let attributedOriginalText = NSMutableAttributedString(string: originalText)
/// Apply the normal text style to whole string at once
let fullRange = NSRange(location: 0, length: attributedOriginalText.length)
attributedOriginalText.addAttribute(.paragraphStyle, value: style, range: fullRange)
attributedOriginalText.addAttribute(.font, value: UIFont(name: "RobotoCondensed-Regular", size 12.0)!, range: fullRange)
attributedOriginalText.addAttribute(.foregroundColor, value: UIColor.darkText, range: fullRange)
/// Apply the link attributes at specified link ranges
for (hyperLink, urlString) in hyperLinks {
let linkRange = attributedOriginalText.mutableString.range(of: hyperLink)
attributedOriginalText.addAttribute(.font, value: UIFont(name: "RobotoCondensed-Bold", size: 12.0)!, range: linkRange)
attributedOriginalText.addAttribute(.link, value: urlString, range: linkRange)
}
Since installing iOS13 the UITextView extension is only customizing the background (to white color) around the text itself and not the entire UITextView.
How can I make sure that the entire UITextview object's background color is changed to white?
Extention:
extension UITextView {
func hyperLink(originalText: String, hyperLink: String, urlString: String) {
let style = NSMutableParagraphStyle()
style.alignment = .center
let attributedOriginalText = NSMutableAttributedString(string: originalText)
let linkRange = attributedOriginalText.mutableString.range(of: hyperLink)
let fullRange = NSMakeRange(0, attributedOriginalText.length)
attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: urlString, range: linkRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.black, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.backgroundColor, value: UIColor.white, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 11), range: fullRange)
self.linkTextAttributes = [
kCTForegroundColorAttributeName: UIColor.black,
kCTUnderlineStyleAttributeName: NSUnderlineStyle.single.rawValue,
] as [NSAttributedString.Key : Any]
self.attributedText = attributedOriginalText
}//end func
}
Usage:
class LoginVC: UIViewController {
#IBOutlet weak var disclaimerTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
disclaimerTextView.hyperLink(originalText: "By continuing, you agree to our Terms and Privacy Policy found on our website", hyperLink: "website", urlString: WEBSITE_URL)
}
}
EDIT: output of the suggested solution - not working.
Changing the extension to this:
extension UITextView {
func hyperLink(originalText: String, hyperLink: String, urlString: String) {
let style = NSMutableParagraphStyle()
style.alignment = .center
let attributedOriginalText = NSMutableAttributedString(string: originalText)
let linkRange = attributedOriginalText.mutableString.range(of: hyperLink)
let fullRange = NSMakeRange(0, attributedOriginalText.length)
attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: urlString, range: linkRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 11), range: fullRange)
self.linkTextAttributes = [
kCTForegroundColorAttributeName: UIColor.black,
kCTUnderlineStyleAttributeName: NSUnderlineStyle.single.rawValue,
] as [NSAttributedString.Key : Any]
self.attributedText = attributedOriginalText
}
}
Is working for me. I simply removed the attributed text attributes that changed the foreground and background colors. I'm not sure why they were there to begin with.
My code not working with NSRange location on iOS13, on iOS12 and below it's work. Is there a way to color the text from the letters to be colored until the total letters afterwards? because I only have data starting from the colored letters and the total letters afterwards.
override func viewDidLoad() {
super.viewDidLoad()
let text = "Testing Attributed Strings"
let attributedString = NSMutableAttributedString(string: text)
let dataStartIHave = 0
let dataTotalIHave = 7
attributedString.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange(location: dataStartIHave, length: dataTotalIHave))
attributedLabel.attributedText = attributedString
}
Thanks in advance.
To get the range from the end of the word until the end of the text reliably you have to convert NSRange to Range<String.Index> and back
let text = "Testing Attributed Strings"
let attributedString = NSMutableAttributedString(string: text)
let dataStartIHave = 0
let dataTotalIHave = 7
let wordRange = NSRange(location: dataStartIHave, length: dataTotalIHave)
let upperBound = Range(wordRange, in: text)!.upperBound
let upperRange = NSRange(upperBound..., in: text)
attributedString.addAttribute(.foregroundColor, value: UIColor.red, range: upperRange)
attributedLabel.attributedText = attributedString
In Swift it's more efficient to get the Range<String.Index> from the word
let text = "Testing Attributed Strings"
let attributedString = NSMutableAttributedString(string: text)
if let testingRange = text.range(of: "Testing") {
let upperRange = NSRange(testingRange.upperBound..., in: text)
attributedString.addAttribute(.foregroundColor, value: UIColor.red, range: upperRange)
}
attributedLabel.attributedText = attributedString
This will work:
attributedString.addAttribute(.foregroundColor, value: UIColor.red, range: NSRange(location: dataStartIHave, length: dataTotalIHave))
I have every label in my tableViewCell displayed correctly, but only my attributeString output is "optional(1)"
let mString = String(self.productList?["market_price"].int)
let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: mString)
attributeString.addAttribute(NSStrikethroughStyleAttributeName, value: 2, rnge: NSMakeRange(0, attributeString.length))
attributeString.addAttribute(NSStrikethroughColorAttributeName, value: UIColor.lightGrayColor(), range: NSMakeRange(0, attributeString.length))
self.marketPriceLabel.attributedText = attributeString
I found a solution.
let mInt: Int = (self.productList?["market_price"].int)!
let mString: String = String(mInt)
let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: mString)
attributeString.addAttribute(NSStrikethroughStyleAttributeName, value: 2, rnge: NSMakeRange(0, attributeString.length))
attributeString.addAttribute(NSStrikethroughColorAttributeName, value: UIColor.lightGrayColor(), range: NSMakeRange(0, attributeString.length))
self.marketPriceLabel.attributedText = attributeString
With a non-editable UITextView, I would like to embed text like this in iOS9+:
Just click here to register
I can create a function and manipulate the text but is there a simpler way?
I see that I can use NSTextCheckingTypeLink so getting the text clickable without the 'click here' part is straightforward in Interface Builder:
Just http://example.com to register
I'm using Xcode 8 and Swift 3 if that's relevant.
Set isEditable = false or the text view will go into text-editing mode when user taps on it.
Swift 4 and later
let attributedString = NSMutableAttributedString(string: "Just click here to register")
let url = URL(string: "https://www.apple.com")!
// Set the 'click here' substring to be the link
attributedString.setAttributes([.link: url], range: NSMakeRange(5, 10))
self.textView.attributedText = attributedString
self.textView.isUserInteractionEnabled = true
self.textView.isEditable = false
// Set how links should appear: blue and underlined
self.textView.linkTextAttributes = [
.foregroundColor: UIColor.blue,
.underlineStyle: NSUnderlineStyle.single.rawValue
]
If you want to use multiple hyperlinks you can use this alternative for Swift 5
extension UITextView {
func addHyperLinksToText(originalText: String, hyperLinks: [String: String]) {
let style = NSMutableParagraphStyle()
style.alignment = .left
let attributedOriginalText = NSMutableAttributedString(string: originalText)
for (hyperLink, urlString) in hyperLinks {
let linkRange = attributedOriginalText.mutableString.range(of: hyperLink)
let fullRange = NSRange(location: 0, length: attributedOriginalText.length)
attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: urlString, range: linkRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.font, value: YourFont, range: fullRange)
}
self.linkTextAttributes = [
NSAttributedString.Key.foregroundColor: YourColor,
NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,
]
self.attributedText = attributedOriginalText
}
}
Usage:
yourTextView.addHyperLinksToText(originalText: "Testing hyperlinks here and there", hyperLinks: ["here": "someUrl1", "there": "someUrl2"])
The same solution for Swift 3 using extensions :
A. Add extension -
extension UITextView {
func hyperLink(originalText: String, hyperLink: String, urlString: String) {
let style = NSMutableParagraphStyle()
style.alignment = .center
let attributedOriginalText = NSMutableAttributedString(string: originalText)
let linkRange = attributedOriginalText.mutableString.range(of: hyperLink)
let fullRange = NSMakeRange(0, attributedOriginalText.length)
attributedOriginalText.addAttribute(NSLinkAttributeName, value: urlString, range: linkRange)
attributedOriginalText.addAttribute(NSParagraphStyleAttributeName, value: style, range: fullRange)
attributedOriginalText.addAttribute(NSFontAttributeName, value: UIFont.systemFont(ofSize: 10), range: fullRange)
self.linkTextAttributes = [
NSForegroundColorAttributeName: UIConfig.primaryColour,
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue,
]
self.attributedText = attributedOriginalText
}
}
B. Add link url - let linkUrl = "https://www.my_website.com"
C. Implement UITextViewDelegate in your ViewController like this -
class MyViewController: UIViewController, UITextViewDelegate {
}
D. Add delegate method to handle tap events -
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if (URL.absoluteString == linkUrl) {
UIApplication.shared.openURL(URL)
}
return false
}
}
E. And finally, things to make sure for your UITextView under attribute inspector -
Behaviour - Editable is turned OFF & Selectable is turned ON.
Data Detectors - Link is turned ON.
Usage -
textView.hyperLink(originalText: "To find out more please visit our website", hyperLink: "website", urlString: linkUrl)
Cheers & happy coding!
Swift 5
This is based on Tejas' answer as a few items in both classes were deprecated.
extension UITextView {
func hyperLink(originalText: String, hyperLink: String, urlString: String) {
let style = NSMutableParagraphStyle()
style.alignment = .left
let attributedOriginalText = NSMutableAttributedString(string: originalText)
let linkRange = attributedOriginalText.mutableString.range(of: hyperLink)
let fullRange = NSMakeRange(0, attributedOriginalText.length)
attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: urlString, range: linkRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.blue, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 10), range: fullRange)
self.linkTextAttributes = [
kCTForegroundColorAttributeName: UIColor.blue,
kCTUnderlineStyleAttributeName: NSUnderlineStyle.single.rawValue,
] as [NSAttributedString.Key : Any]
self.attributedText = attributedOriginalText
}
Don't forget to add UITextViewDelegate to your view controller and set your let linkUrl = "https://example.com"
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if (URL.absoluteString == linkUrl) {
UIApplication.shared.open(URL) { (Bool) in
}
}
return false
}
Usage stays the same:
textView.hyperLink(originalText: "To find out more please visit our website", hyperLink: "website", urlString: linkUrl)
Swift 4 code.
May be I'm the only one who needs to set several links and color the words in one message. I created an AttribTextHolder class to accumulate all information about text inside this holder and easily pass it between objects to set text to UITextView somewhere deep inside a controller.
class AttribTextHolder {
enum AttrType {
case link
case color
}
let originalText: String
var attributes: [(text: String, type: AttrType, value: Any)]
init(text: String, attrs: [(text: String, type: AttrType, value: Any)] = [])
{
originalText = text
attributes = attrs
}
func addAttr(_ attr: (text: String, type: AttrType, value: Any)) -> AttribTextHolder {
attributes.append(attr)
return self
}
func setTo(textView: UITextView)
{
let style = NSMutableParagraphStyle()
style.alignment = .left
let attributedOriginalText = NSMutableAttributedString(string: originalText)
for item in attributes {
let arange = attributedOriginalText.mutableString.range(of: item.text)
switch item.type {
case .link:
attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: item.value, range: arange)
case .color:
var color = UIColor.black
if let c = item.value as? UIColor { color = c }
else if let s = item.value as? String { color = s.color() }
attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: arange)
default:
break
}
}
let fullRange = NSMakeRange(0, attributedOriginalText.length)
attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange)
textView.linkTextAttributes = [
kCTForegroundColorAttributeName: UIColor.blue,
kCTUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue,
] as [String : Any]
textView.attributedText = attributedOriginalText
}
}
Use it like this:
let txt = AttribTextHolder(text: "To find out more visit our website or email us your questions")
.addAttr((text: "our website", type: .link, "http://example.com"))
.addAttr((text: "our website", type: .color, "#33BB22"))
.addAttr((text: "email us", type: .link, "mailto:us#example.com"))
.addAttr((text: "email us", type: .color, UIColor.red))
....
....
txt.setTo(textView: myUITextView)
Also in this code I use simple String extension to convert String hex values into UIColor objects
extension String {
/// Converts string color (ex: #23FF33) into UIColor
func color() -> UIColor {
let hex = self.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt32()
Scanner(string: hex).scanHexInt32(&int)
let a, r, g, b: UInt32
switch hex.characters.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (255, 0, 0, 0)
}
return UIColor(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
}
}
Using Swift >= 4:
let descriptionText = NSMutableAttributedString(string:"To learn more, check out our ", attributes: [:])
let linkText = NSMutableAttributedString(string: "Privacy Policy and Terms of Use", attributes: [NSAttributedString.Key.link: URL(string: example.com)!])
descriptionText.append(linkText)
The same solution for Swift 4 using extensions:
extension UITextView {
func hyperLink(originalText: String, hyperLink: String, urlString: String) {
let style = NSMutableParagraphStyle()
style.alignment = .left
let attributedOriginalText = NSMutableAttributedString(string: originalText)
let linkRange = attributedOriginalText.mutableString.range(of: hyperLink)
let fullRange = NSMakeRange(0, attributedOriginalText.length)
attributedOriginalText.addAttribute(NSAttributedStringKey.link, value: urlString, range: linkRange)
attributedOriginalText.addAttribute(NSAttributedStringKey.paragraphStyle, value: style, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedStringKey.font, value: UIFont.systemFont(ofSize: 10), range: fullRange)
self.linkTextAttributes = [
kCTForegroundColorAttributeName: UIColor.blue,
kCTUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue,
] as [String : Any]
self.attributedText = attributedOriginalText
}
}
A safer solution to implement hyperlink via UITextView
var termsConditionsTextView: UITextView = {
let view = UITextView()
view.backgroundColor = .clear
view.textAlignment = .left
let firstTitleString = "By registering for THIS_APP I agree with the "
let secondTitleString = "Terms & Conditions"
let finishTitleString = firstTitleString + secondTitleString
let attributedString = NSMutableAttributedString(string: finishTitleString)
attributedString.addAttribute(.link, value: "https://stackoverflow.com", range: NSRange(location: firstTitleString.count, length: secondTitleString.count))
view.attributedText = attributedString
view.textContainerInset = .zero
view.linkTextAttributes = [
.foregroundColor: UIColor.blue,
.underlineStyle: NSUnderlineStyle.single.isEmpty
]
view.font = view.font = UIFont(name: "YOUR_FONT_NAME", size: 16)
view.textColor = UIColor.black
return view }()
SWIFT 5 AND MORE THAN ONE LINK
import UIKit
public extension UITextView {
func hyperLink(originalText: String, linkTextsAndTypes: [String: String]) {
let style = NSMutableParagraphStyle()
style.alignment = .left
let attributedOriginalText = NSMutableAttributedString(string: originalText)
for linkTextAndType in linkTextsAndTypes {
let linkRange = attributedOriginalText.mutableString.range(of: linkTextAndType.key)
let fullRange = NSRange(location: 0, length: attributedOriginalText.length)
attributedOriginalText.addAttribute(NSAttributedString.Key.link, value: linkTextAndType.value, range: linkRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.paragraphStyle, value: style, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.blue, range: fullRange)
attributedOriginalText.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 10), range: fullRange)
}
self.linkTextAttributes = [
kCTForegroundColorAttributeName: UIColor.blue,
kCTUnderlineStyleAttributeName: NSUnderlineStyle.single.rawValue
] as [NSAttributedString.Key: Any]
self.attributedText = attributedOriginalText
}
}
And the usage in your viewController:
#IBOutlet weak var termsHyperlinkTextView: UITextView! {
didSet {
termsHyperlinkTextView.delegate = self
termsHyperlinkTextView.hyperLink(originalText: "Check out terms & conditions or our privacy policy",
linkTextsAndTypes: ["terms & conditions": LinkType.termsAndConditions.rawValue,
"privacy policy": LinkType.privacyPolicy.rawValue])
}
}
enum LinkType: String {
case termsAndConditions
case privacyPolicy
}
// MARK: - UITextViewDelegate
extension ViewController: UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if let linkType = LinkType(rawValue: URL.absoluteString) {
// TODO: handle linktype here with switch or similar.
}
return false
}
}
You could use this simple method to add a hyperlink to any set of characters starting with tag
func addLink(forString string : NSMutableAttributedString
,baseURL : String
,tag : String){
let array = string.string.replacingOccurrences(of: "\n", with: " ").components(separatedBy: " ")
let filterArray = array.filter { (string) -> Bool in
return string.contains(tag)
}
for element in filterArray {
let removedHashtag = element.replacingOccurrences(of: tag, with: "")
let url = baseURL + removedHashtag
let range = NSString.init(string: (string.string)).range(of: element)
string.addAttributes([NSAttributedStringKey.link : url.replacingOccurrences(of: " ", with: "")], range: range)
}
}
I wanted to do the same thing and ended up just using a UIButton with the title "click here" surrounded by UILabels "just " and " to register", and then:
#IBAction func btnJustClickHereLink(_ sender: UIButton) {
if let url = URL(string: "http://example.com") {
UIApplication.shared.openURL(url)
}
}