UITextView linkTextAttributes font attribute does not apply to NSAttributedString

I have an NSAttributedString generated from HTML that contains some links. The assigned string is shown in a UITextView. I want to use a different font style for links, and for this I set linkTextAttributes . I added NSForegroundColorAttributeName , NSFontAttributeName and NSUnderlineStyleAttributeName . For some reason, the foreground color is applied, but the rest of the attributes are missing.

 myTextView.linkTextAttributes = [NSForegroundColorAttributeName : UIColor.redColor(), NSFontAttributeName : textLinkFont, NSUnderlineStyleAttributeName : NSUnderlineStyle.StyleNone.rawValue] 

Has anyone else come across this and how do I change the font style for links without applying inline CSS to the original HTML? Thanks.

+15
swift uitextview nsattributedstring
source share
7 answers

Not sure why linkTextAttributes does not work for the font name. But we can achieve this by updating the link attributes of NSAttributedString. Check out the code below.

  do { let htmlStringCode = "For more info <a href=\"http://www.samplelink.com/subpage.php?id=8\">Click here</a>" let string = try NSAttributedString(data: htmlStringCode.dataUsingEncoding(NSUTF8StringEncoding)!, options: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding], documentAttributes: nil) let newString = NSMutableAttributedString(attributedString: string) string.enumerateAttributesInRange(NSRange.init(location: 0, length: string.length), options: .Reverse) { (attributes : [String : AnyObject], range:NSRange, _) -> Void in if let _ = attributes[NSLinkAttributeName] { newString.removeAttribute(NSFontAttributeName, range: range) newString.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(30), range: range) } } textField.attributedText = newString textField.linkTextAttributes = [NSForegroundColorAttributeName : UIColor.redColor(), NSUnderlineStyleAttributeName : NSUnderlineStyle.StyleNone.rawValue] }catch { } 

This is objective C code for this:

 NSDictionary *options = @{NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType}; NSData *data = [html dataUsingEncoding:NSUnicodeStringEncoding allowLossyConversion:NO]; NSAttributedString *attributedString = [[NSAttributedString alloc] initWithData:data options:options documentAttributes:nil error:nil]; NSMutableAttributedString *attributedStringWithBoldLinks = [[NSMutableAttributedString alloc] initWithAttributedString:attributedString]; [attributedString enumerateAttributesInRange:NSMakeRange(0, attributedString.string.length) options:NSAttributedStringEnumerationReverse usingBlock:^(NSDictionary<NSString *,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { if ([attrs objectForKey:NSLinkAttributeName]) { [attributedStringWithBoldLinks removeAttribute:NSFontAttributeName range:range]; [attributedStringWithBoldLinks addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"YourFont-Bold" size:16.0] range:range]; } }]; self.linkTextAttributes = @{NSForegroundColorAttributeName : [UIColor redColor]}; self.attributedText = attributedStringWithBoldLinks; 

Screenshothot

+19
source share

For some reason, the post processing associated with the string with enumerateAttributesInRange: does not work for me.

So I used NSDataDetector to detect the link and enumerateMatchesInString:options:range:usingBlock: to put my style for all the links in the line. Here is my processing function:

 + (void) postProcessTextViewLinksStyle:(UITextView *) textView { NSAttributedString *attributedString = textView.attributedText; NSMutableAttributedString *attributedStringWithItalicLinks = [[NSMutableAttributedString alloc] initWithAttributedString:attributedString]; NSError *error = nil; NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:&error]; [detector enumerateMatchesInString:[attributedString string] options:0 range:NSMakeRange(0, [attributedString length]) usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){ NSRange matchRange = [match range]; NSLog(@"Links style postprocessing. Range (from: %lu, length: %lu )", (unsigned long)matchRange.location, (unsigned long)matchRange.length); if ([match resultType] == NSTextCheckingTypeLink) { [attributedStringWithItalicLinks removeAttribute:NSFontAttributeName range:matchRange]; [attributedStringWithItalicLinks addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"YourFont-Italic" size:14.0f] range:matchRange]; } }]; textView.attributedText = attributedStringWithItalicLinks; } 
+3
source share

This is a quick update 3 replies from @Arun Ammannaya

 guard let font = UIFont.init(name: "Roboto-Regular", size: 15) else { return } let newString = NSMutableAttributedString(attributedString: string) let range = NSRange(location:0,length: string.length) string.enumerateAttributes(in: range, options: .reverse, using: { (attributes : [String : Any], range : NSRange, _) -> Void in if let _ = attributes[NSLinkAttributeName] { newString.removeAttribute(NSFontAttributeName, range: range) newString.addAttribute(NSFontAttributeName, value: font, range: range) } }) errorTextView.attributedText = newString errorTextView.linkTextAttributes = [NSForegroundColorAttributeName : UIColor.green, NSUnderlineStyleAttributeName : NSUnderlineStyle.styleSingle.rawValue] 

This is a quick solution for @CTiPKA, which I prefer as it avoids HTML

 guard let attributedString = errorTextView.attributedText else { return } guard let font = UIFont.init(name: "Roboto-Regular", size: 15) else { return } let newString = NSMutableAttributedString(attributedString: attributedString) let types: NSTextCheckingResult.CheckingType = [.link, .phoneNumber] guard let linkDetector = try? NSDataDetector(types: types.rawValue) else { return } let range = NSRange(location:0,length: attributedString.length) linkDetector.enumerateMatches(in: attributedString.string, options: [], range: range, using: { (match : NSTextCheckingResult?, flags : NSRegularExpression.MatchingFlags, stop) in if let matchRange = match?.range { newString.removeAttribute(NSFontAttributeName, range: matchRange) newString.addAttribute(NSFontAttributeName, value: font, range: matchRange) } }) errorTextView.attributedText = newString 
+3
source share

Updated for Swift 4:

 let originalText = NSMutableAttributedString(attributedString: textView.attributedText) var newString = NSMutableAttributedString(attributedString: textView.attributedText) originalText.enumerateAttributes(in: NSRange(0..<originalText.length), options: .reverse) { (attributes, range, pointer) in if let _ = attributes[NSAttributedString.Key.link] { newString.removeAttribute(NSAttributedString.Key.font, range: range) newString.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 30), range: range) } } self.textView.attributedText = newString // updates the text view on the vc 
+3
source share

for simple cases: (without the horrible use of HTML):

  let linkTextAttributes : [String : Any] = [ NSForegroundColorAttributeName: UIColor.red, NSUnderlineColorAttributeName: UIColor.magenta, NSUnderlineStyleAttributeName: NSUnderlineStyle.patternSolid.rawValue ] self.infoText.linkTextAttributes = linkTextAttributes 
+2
source share

There is also a simple way to apply style for text, if you use html - you can simply add style to the HTML code. Then you do not need to worry about setting attributes for the text. For example:

 NSString *html = [NSString stringWithFormat:@"<p style=\"font-family: Your-Font-Name; color: #344052; font-size: 15px\"><a style=\"color: #0A9FD2\" href=\"https://examplelink.com\">%@</a> %@ on %@</p>", name, taskName, timeString]; NSDictionary *options = @{NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType}; NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding]; NSAttributedString *attributedString = [[NSAttributedString alloc] initWithData:data options:options documentAttributes:nil error:nil]; 
0
source share

Swift 5 version of Ryan Heitner awesome answer:

 guard let attributedString = textView.attributedText else { return } guard let linkFont = UIFont(name: "HelveticaNeue-Bold", size: 20.0) else { return } let newString = NSMutableAttributedString(attributedString: attributedString) let types: NSTextCheckingResult.CheckingType = [.link, .phoneNumber] guard let linkDetector = try? NSDataDetector(types: types.rawValue) else { return } let range = NSRange(location: 0, length: attributedString.length) linkDetector.enumerateMatches(in: attributedString.string, options: [], range: range, using: { (match: NSTextCheckingResult?, flags: NSRegularExpression.MatchingFlags, stop) in if let matchRange = match?.range { newString.removeAttribute(NSAttributedString.Key.font, range: matchRange) newString.addAttribute(NSAttributedString.Key.font, value: linkFont, range: matchRange) } }) textView.attributedText = newString 
0
source share

All Articles