157

I am trying to get a snippet of text that is formatted in html to display nicely on an iPhone in a UITableViewCell.

So far I have this:

NSError* error;
NSString* source = @"<strong>Nice</strong> try, Phil";
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithData:[source dataUsingEncoding:NSUTF8StringEncoding]
                                                           options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                     NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]}
                                                              documentAttributes:nil error:&error];

This kind of works. I get some text that has 'Nice' in bold! But... it also sets the font to be Times Roman! This is not the font face I want. I am thinking I need to set something in the documentAttributes, but, I can't find any examples anywhere.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
phil
  • 4,668
  • 4
  • 33
  • 51
  • 1
    Note: NSHTMLTextDocumentType can potentially be slow. See http://stackoverflow.com/questions/21166752/why-does-the-initial-call-to-this-method-take-over-100-times-longer-with-the-deb – finneycanhelp Feb 23 '15 at 21:10
  • IMPORTANT: If you are using custom font you need to see this answer https://stackoverflow.com/a/60786178/1223897 – Yuvrajsinh Mar 21 '20 at 09:28

17 Answers17

149

Swift 2 version, based on the answer given by Javier Querol

extension UILabel {
    func setHTMLFromString(text: String) {
        let modifiedFont = NSString(format:"<span style=\"font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%@</span>", text) as String

        let attrStr = try! NSAttributedString(
            data: modifiedFont.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)!,
            options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

Swift 3.0 and iOS 9+

extension UILabel {
    func setHTMLFromString(htmlText: String) {
        let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>", htmlText)

        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
            options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

Swift 5 and iOS 11+

extension UILabel {
    func setHTMLFromString(htmlText: String) {
        let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>", htmlText)

        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
            options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}
Radu Ursache
  • 1,230
  • 1
  • 22
  • 35
Víctor Albertos
  • 8,093
  • 5
  • 43
  • 71
  • 1
    Not changing current fonts, this is what I was looking for,thanks man! – Mohammad Zaid Pathan Dec 22 '15 at 19:47
  • Hi. What if the string coming from server has Span style already. I wanted to remove that from the existing string and add new span style the way you mentioned in answer. Any idea how to remove existing span style? – harshit2811 Feb 22 '16 at 14:03
  • 3
    This works. You can set the modified string to a String right away and omit the NSString initialization i.e "(text)" – Matthew Korporaal Apr 21 '16 at 20:50
  • 2
    To make this work, (which works really really well) I had to add single quotes around the font-family value so
    – geraldcor Jul 05 '16 at 20:34
  • I get `` when using `fontName` – Daniel Jul 18 '16 at 13:33
  • 4
    I think, since iOS9, it is best to use `font-family: '-apple-system', 'HelveticaNeue';` (which works, and also is backwards compatible). If you only support iOS9 `font-family: -apple-system;` can be used – Daniel Jul 18 '16 at 15:39
  • Very nice! Thanks! – Mokkapps Nov 30 '16 at 21:00
  • you are AWESOME! thanks for sharing! amazing extension. keeps format AND gives me opportunity to use my fonts – David Seek Apr 11 '17 at 22:52
  • 1
    Also handy is ability to set text color, just add color to style attribute with value in hex string format `color: #000000`. See this link for converting UIColor to hex string: https://gist.github.com/yannickl/16f0ed38f0698d9a8ae7 – Miroslav Hrivik Aug 16 '17 at 14:19
116
#import "UILabel+HTML.h"

@implementation UILabel (HTML)

- (void)jaq_setHTMLFromString:(NSString *)string {

    string = [string stringByAppendingString:[NSString stringWithFormat:@"<style>body{font-family: '%@'; font-size:%fpx;}</style>",
                                              self.font.fontName,
                                              self.font.pointSize]];
    self.attributedText = [[NSAttributedString alloc] initWithData:[string dataUsingEncoding:NSUnicodeStringEncoding]
                                                           options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                     NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
                                                documentAttributes:nil
                                                             error:nil];
}


@end

This way you don't need to specify which font you want, it will take the label font and size.

Javier Querol
  • 1,243
  • 2
  • 8
  • 8
  • 2
    This is very elegant! – Merlevede Apr 02 '16 at 19:15
  • 2
    Nice. Though it makes more sense as a category on NSAttributedString imo. – Dimitris Apr 27 '16 at 18:58
  • @Javier Querol Then how to handle link clinks ? – KarenAnne Jun 21 '16 at 03:47
  • You encode string to data with `NSUnicodeStringEncoding` and then encode data back to characters with `NSUTF8StringEncoding`. Is it OK? – Timur Bernikovich Apr 15 '17 at 23:16
  • This solution adds no extra white space at the bottom of the UILabel (which happens when using `div` or `span`) – thijsonline Oct 31 '17 at 12:34
  • 1
    sorry - this solution does NOT work for me, the font is not set to the desired font. - instead of using self.font.fontName and instead using self.font.familyName does set the desired font but then the HTML tags are not preserved. see solution below that does work and does not rely on using HTML styles of any kind. -rrh – Richie Hyatt Sep 30 '19 at 16:43
  • Warning: It will break all markdown, like bold, italyc, monotype... – Nike Kov Dec 17 '22 at 16:19
54

I actually found a working solution to this problem:

Changing the font in your HTML response string before it gets parsed.

NSString *aux = [NSString stringWithFormat:@"<span style=\"font-family: YOUR_FONT_NAME; font-size: SIZE\">%@</span>", htmlResponse];

Example:

NSString *aux = [NSString stringWithFormat:@"<span style=\"font-family: HelveticaNeue-Thin; font-size: 17\">%@</span>", [response objectForKey:@"content"]];

Swift version:

let aux = "<span style=\"font-family: YOUR_FONT_NAME; font-size: SIZE\">\(htmlResponse)</span>"
Teodor Ciuraru
  • 3,417
  • 1
  • 32
  • 39
50

A more generic approach is to look at the font traits while enumerating, and create a font with the same traits (bold, italic, etc.):

extension NSMutableAttributedString {

    /// Replaces the base font (typically Times) with the given font, while preserving traits like bold and italic
    func setBaseFont(baseFont: UIFont, preserveFontSizes: Bool = false) {
        let baseDescriptor = baseFont.fontDescriptor
        let wholeRange = NSRange(location: 0, length: length)
        beginEditing()
        enumerateAttribute(.font, in: wholeRange, options: []) { object, range, _ in
            guard let font = object as? UIFont else { return }
            // Instantiate a font with our base font's family, but with the current range's traits
            let traits = font.fontDescriptor.symbolicTraits
            guard let descriptor = baseDescriptor.withSymbolicTraits(traits) else { return }
            let newSize = preserveFontSizes ? descriptor.pointSize : baseDescriptor.pointSize
            let newFont = UIFont(descriptor: descriptor, size: newSize)
            self.removeAttribute(.font, range: range)
            self.addAttribute(.font, value: newFont, range: range)
        }
        endEditing()
    }
}
Cem Schemel
  • 442
  • 3
  • 13
markiv
  • 1,578
  • 16
  • 12
  • 2
    Even though this isn't very concise, it seems more stable than hacking around the problem with wrapping html with more html. – syvex Jul 31 '19 at 21:36
42

Figured it out. Bit of a bear, and maybe not the best answer.

This code will go through all the font changes. I know that it is using "Times New Roman" and "Times New Roman BoldMT" for the fonts. But regardless, this will find the bold fonts and let me reset them. I can also reset the size while I'm at it.

I honestly hope/think there is a way to set this up at parse time, but I can't find it if there is.

- (void)changeFont:(NSMutableAttributedString*)string
{
    NSRange range = (NSRange){0,[string length]};
    [string enumerateAttribute:NSFontAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id value, NSRange range, BOOL *stop) {
    UIFont* currentFont = value;
    UIFont *replacementFont = nil;

    if ([currentFont.fontName rangeOfString:@"bold" options:NSCaseInsensitiveSearch].location != NSNotFound) {
        replacementFont = [UIFont fontWithName:@"HelveticaNeue-CondensedBold" size:25.0f];
    } else {
        replacementFont = [UIFont fontWithName:@"HelveticaNeue-Thin" size:25.0f];
    }

    [string addAttribute:NSFontAttributeName value:replacementFont range:range];
}];

}

Nike Kov
  • 12,630
  • 8
  • 75
  • 122
phil
  • 4,668
  • 4
  • 33
  • 51
25

Swift 4+ update of UILabel extension

extension UILabel {
    func setHTMLFromString(text: String) {
        let modifiedFont = NSString(format:"<span style=\"font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%@</span>" as NSString, text)

        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: String.Encoding.unicode.rawValue, allowLossyConversion: true)!,
            options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
            documentAttributes: nil)

        self.attributedText = attrStr
    }
}

iOS 9+

extension UILabel {
    func setHTMLFromString(htmlText: String) {
        let modifiedFont = NSString(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>" as NSString, htmlText) as String


        //process collection values
        let attrStr = try! NSAttributedString(
            data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
            options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
            documentAttributes: nil)


        self.attributedText = attrStr
    }
}
Rafat touqir Rafsun
  • 2,777
  • 28
  • 24
23

Yes, there is an easier solution. Set the font in the html source!

NSError* error;
NSString* source = @"<strong>Nice</strong> try, Phil";
source = [source stringByAppendingString:@"<style>strong{font-family: 'Avenir-Roman';font-size: 14px;}</style>"];
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithData:[source dataUsingEncoding:NSUTF8StringEncoding]
                                                           options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                     NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]}
                                                              documentAttributes:nil error:&error];

Hope this helps.

Max
  • 3,371
  • 2
  • 29
  • 30
11

The answers above all work OK if you're doing the conversion at the same time as creating the NSAttributedString. But I think a better solution, which works on the string itself and therefore doesn't need access to the input, is the following category:

extension NSMutableAttributedString
{
    func convertFontTo(font: UIFont)
    {
        var range = NSMakeRange(0, 0)

        while (NSMaxRange(range) < length)
        {
            let attributes = attributesAtIndex(NSMaxRange(range), effectiveRange: &range)
            if let oldFont = attributes[NSFontAttributeName]
            {
                let newFont = UIFont(descriptor: font.fontDescriptor().fontDescriptorWithSymbolicTraits(oldFont.fontDescriptor().symbolicTraits), size: font.pointSize)
                addAttribute(NSFontAttributeName, value: newFont, range: range)
            }
        }
    }
}

Use as:

let desc = NSMutableAttributedString(attributedString: *someNSAttributedString*)
desc.convertFontTo(UIFont.systemFontOfSize(16))

Works on iOS 7+

HughHughTeotl
  • 5,439
  • 3
  • 34
  • 49
5

Improving on Victor's solution, including color:

extension UILabel {
      func setHTMLFromString(text: String) {
          let modifiedFont = NSString(format:"<span style=\"color:\(self.textColor.toHexString());font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%@</span>", text) as String

          let attrStr = try! NSAttributedString(
              data: modifiedFont.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)!,
              options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding],
              documentAttributes: nil)

          self.attributedText = attrStr
      }
  }

For this to work you will also need YLColor.swift of the uicolor to hex conversion https://gist.github.com/yannickl/16f0ed38f0698d9a8ae7

5

Using of NSHTMLTextDocumentType is slow and hard to control styles. I suggest you to try my library which is called Atributika. It has its own very fast parser. Also you can have any tag names and define any style for them.

Example:

let str = "<strong>Nice</strong> try, Phil".style(tags:
    Style("strong").font(.boldSystemFont(ofSize: 15))).attributedString

label.attributedText = str

You can find it here https://github.com/psharanda/Atributika

Pavel Sharanda
  • 919
  • 10
  • 9
5

Joining together everyone's answers, I made two extensions that allow setting a label with html text. Some answers above did not correctly interpret the font family in the attributed strings. Others were incomplete for my needs or failed in other ways. Let me know if there's anything you'd like me to improve on.

I hope this helps someone.

extension UILabel {
    /// Sets the label using the supplied html, using the label's font and font size as a basis.
    /// For predictable results, using only simple html without style sheets.
    /// See https://stackoverflow.com/questions/19921972/parsing-html-into-nsattributedtext-how-to-set-font
    ///
    /// - Returns: Whether the text could be converted.
    @discardableResult func setAttributedText(fromHtml html: String) -> Bool {
        guard let data = html.data(using: .utf8, allowLossyConversion: true) else {
            print(">>> Could not create UTF8 formatted data from \(html)")
            return false
        }

        do {
            let mutableText = try NSMutableAttributedString(
                data: data,
                options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
                documentAttributes: nil)
            mutableText.replaceFonts(with: font)
            self.attributedText = mutableText
            return true
        } catch (let error) {
            print(">>> Could not create attributed text from \(html)\nError: \(error)")
            return false
        }
    }
}

extension NSMutableAttributedString {

    /// Replace any font with the specified font (including its pointSize) while still keeping
    /// all other attributes like bold, italics, spacing, etc.
    /// See https://stackoverflow.com/questions/19921972/parsing-html-into-nsattributedtext-how-to-set-font
    func replaceFonts(with font: UIFont) {
        let baseFontDescriptor = font.fontDescriptor
        var changes = [NSRange: UIFont]()
        enumerateAttribute(.font, in: NSMakeRange(0, length), options: []) { foundFont, range, _ in
            if let htmlTraits = (foundFont as? UIFont)?.fontDescriptor.symbolicTraits,
                let adjustedDescriptor = baseFontDescriptor.withSymbolicTraits(htmlTraits) {
                let newFont = UIFont(descriptor: adjustedDescriptor, size: font.pointSize)
                changes[range] = newFont
            }
        }
        changes.forEach { range, newFont in
            removeAttribute(.font, range: range)
            addAttribute(.font, value: newFont, range: range)
        }
    }
}
dwsolberg
  • 879
  • 9
  • 8
4

Thanks for the answers, I really liked the extension but I have not converted to swift yet. For those old schoolers still in Objective-C this should help a little :D

-(void) setBaseFont:(UIFont*)font preserveSize:(BOOL) bPreserve {

UIFontDescriptor *baseDescriptor = font.fontDescriptor;

[self enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, [self length]) options:0 usingBlock:^(id  _Nullable value, NSRange range, BOOL * _Nonnull stop) {

    UIFont *font = (UIFont*)value;
    UIFontDescriptorSymbolicTraits traits = font.fontDescriptor.symbolicTraits;
    UIFontDescriptor *descriptor = [baseDescriptor fontDescriptorWithSymbolicTraits:traits];
    UIFont *newFont = [UIFont fontWithDescriptor:descriptor size:bPreserve?baseDescriptor.pointSize:descriptor.pointSize];

    [self removeAttribute:NSFontAttributeName range:range];
    [self addAttribute:NSFontAttributeName value:newFont range:range];

}];    } 

Happy Coding! --Greg Frame

Greg Frame
  • 71
  • 3
4

Here is an extension for NSString that returns an NSAttributedString using Objective-C.

It correctly handles a string with HTML tags and sets the desired Font and Font color while preserving HTML tags including BOLD, ITALICS...

Best of all it does not rely on any HTML markers to set the font attributes.

@implementation NSString (AUIViewFactory)

- (NSAttributedString*)attributedStringFromHtmlUsingFont:(UIFont*)font fontColor:(UIColor*)fontColor
{
    NSMutableAttributedString* mutableAttributedString = [[[NSAttributedString alloc] initWithData:[self dataUsingEncoding:NSUTF8StringEncoding] options:@{NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding)} documentAttributes:nil error:nil] mutableCopy]; // parse text with html tags into a mutable attributed string
    [mutableAttributedString beginEditing];
    // html tags cause font ranges to be created, for example "This text is <b>bold</b> now." creates three font ranges: "This text is " , "bold" , " now."
    [mutableAttributedString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, mutableAttributedString.length) options:0 usingBlock:^(id value, NSRange range, BOOL* stop)
    { // iterate every font range, change every font to new font but preserve symbolic traits such as bold and italic (underline and strikethorugh are preserved automatically), set font color
        if (value)
        {
            UIFont* oldFont = (UIFont*)value;
            UIFontDescriptor* fontDescriptor = [font.fontDescriptor fontDescriptorWithSymbolicTraits:oldFont.fontDescriptor.symbolicTraits];
            UIFont* newFont = [UIFont fontWithDescriptor:fontDescriptor size:font.pointSize];
            [mutableAttributedString removeAttribute:NSFontAttributeName range:range]; // remove the old font attribute from this range
            [mutableAttributedString addAttribute:NSFontAttributeName value:newFont range:range]; // add the new font attribute to this range
            [mutableAttributedString addAttribute:NSForegroundColorAttributeName value:fontColor range:range]; // set the font color for this range
        }
    }];
    [mutableAttributedString endEditing];
    return mutableAttributedString;
}

@end
Richie Hyatt
  • 2,244
  • 2
  • 21
  • 14
2

Swift 5 Solution for UILabel and UITextView

extension UITextView {
func setHTMLFromString(htmlText: String) {
    let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>", htmlText)

    let attrStr = try! NSAttributedString(
        data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
        options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue],
        documentAttributes: nil)

    self.attributedText = attrStr
}

}

extension UILabel {
func setHTMLFromString(htmlText: String) {
    let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%@</span>", htmlText)

    let attrStr = try! NSAttributedString(
        data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
        options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue],
        documentAttributes: nil)

    self.attributedText = attrStr
}

}

Usage for UILabel

self.label.setHTMLFromString(htmlText: htmlString) 

Usage for UITextView

self.textView.setHTMLFromString(htmlText: htmlString) 

Output

enter image description here

Naveed Ahmad
  • 6,627
  • 2
  • 58
  • 83
1

Swift 3 String extension including a nil font. The property without font is taken from other SO question, do not remember which one :(

extension String {
    var html2AttributedString: NSAttributedString? {
        guard let data = data(using: .utf8) else {
            return nil
        }

        do {
            return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil)
        }
        catch {
            print(error.localizedDescription)
            return nil
        }
    }

    public func getHtml2AttributedString(font: UIFont?) -> NSAttributedString? {
        guard let font = font else {
            return html2AttributedString
        }

        let modifiedString = "<style>body{font-family: '\(font.fontName)'; font-size:\(font.pointSize)px;}</style>\(self)";

        guard let data = modifiedString.data(using: .utf8) else {
            return nil
        }

        do {
            return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil)
        }
        catch {
            print(error)
            return nil
        }
    }
}
shelll
  • 3,234
  • 3
  • 33
  • 67
1
  • Swift Solution

The below approach works. You can very well provide the font family, font size, and color in this approach. Feel free to suggest changes or any better way of doing this.

extension UILabel {

func setHTMLFromString(htmlText: String,fontFamily:String,fontColor:String) {

    let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', \(fontFamily); font-size: \(self.font!.pointSize); color: \(fontColor) ; \">%@</span>", htmlText)

    do{
        if let valData = modifiedFont.data(using: .utf8){
            let attrStr = try NSAttributedString(data: valData, options: [NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html.rawValue], documentAttributes: nil)
            self.attributedText = attrStr
        }
    }catch{
        print("Conversion failed with \(error)")
        self.attributedText = nil
    }
}
Saeed Zhiany
  • 2,051
  • 9
  • 30
  • 41
Sagar
  • 11
  • 2
-3

Actually, an even easier and cleanr way exists. Just set the font after parsing the HTML:

 NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
                                                                     options:@{
                                                                               NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                                                               NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
                                                          documentAttributes:nil error:nil];
    [text addAttributes:@{NSFontAttributeName: [UIFont fontWithName:@"Lato-Regular" size:20]} range:NSMakeRange(0, text.length)];
Erik
  • 11,944
  • 18
  • 87
  • 126