Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift- Change font on an HTML string that has its own Styles

I am dinamically getting an HTML string from a Wordpress API and parsing it into an Attributed String to show it in my app. Since the string has its own styles, it shows different fonts and sizes, something that is affecting our design choices.

What I want to do is change the font and its size on the whole attributed string.

I tried doing so in the options of the attributed string, but it does nothing:

let attributedT = try! NSAttributedString(
            data: nContent!.decodeHTML().data(using: String.Encoding.unicode, allowLossyConversion: true)!,
            options: [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSFontAttributeName: UIFont(name: "Helvetica", size: 16.0)!],
            documentAttributes: nil)
        contentLbl.attributedText = attributedT

Does anybody have any ideas on how to achieve this?

P.S. I know that I could add a CSS tag to the beginning or end of the string, but would this override other styles in it? Also, if this is a valid solution, could you please provide a sample on how to do it?

like image 871
Jacobo Koenig Avatar asked Jan 01 '17 02:01

Jacobo Koenig


People also ask

How to set custom font for a text view in SwiftUI?

Text is a view that displays one or more lines of text in SwiftUI. By default a text view draws a string using a system font with the body text style. Text("Hello, world!") Text view with body text style. We can override this default font with a custom one. We can set a custom font for a text view using font (_:) modifier.

How to set font-weight in Java?

There are many ways to set font-weight. You can do it through both Font and Text instance methods. Set font-weight via weight (_:) modifier. This is a Font instance method, so we use this on a font, not text. Text("Hello, world!")

How do I change the font size of text in CSS?

The font size of text represents how big that text is. To change the font size of some text, you need to use the font-size property and then specify the value in pixels ( px ), rem, or em. To get rid of the default white background and center the text both horizontally and vertically, I wrote this CSS:

How do I add a custom font to my iOS app?

Including fonts in iOS apps has gotten a lot easier in iOS8 since you can now preview them in your xibs & storyboards in Interface Builder. First we’ll add the custom font to the Xcode project so we can use it in Interface Builder. Then we’ll set up the custom font programmatically in Swift code.


2 Answers

Swift 4 solution


  • NSAttributedString extension with convenience initializer
  • Enumerates through the attributed string (HTML document) font attributes, and replaces with the provided UIFont
  • Preserves original HTML font sizes, or uses font-size from provided UIFont, @see useDocumentFontSize parameter
  • This method can simply convert HTML to NSAttributedString, without the overload of manipulating with fonts, just skip the font parameter, @see guard statement

extension NSAttributedString {

    convenience init(htmlString html: String, font: UIFont? = nil, useDocumentFontSize: Bool = true) throws {
        let options: [NSAttributedString.DocumentReadingOptionKey : Any] = [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
        ]

        let data = html.data(using: .utf8, allowLossyConversion: true)
        guard (data != nil), let fontFamily = font?.familyName, let attr = try? NSMutableAttributedString(data: data!, options: options, documentAttributes: nil) else {
            try self.init(data: data ?? Data(html.utf8), options: options, documentAttributes: nil)
            return
        }

        let fontSize: CGFloat? = useDocumentFontSize ? nil : font!.pointSize
        let range = NSRange(location: 0, length: attr.length)
        attr.enumerateAttribute(.font, in: range, options: .longestEffectiveRangeNotRequired) { attrib, range, _ in
            if let htmlFont = attrib as? UIFont {
                let traits = htmlFont.fontDescriptor.symbolicTraits
                var descrip = htmlFont.fontDescriptor.withFamily(fontFamily)

                if (traits.rawValue & UIFontDescriptorSymbolicTraits.traitBold.rawValue) != 0 {
                    descrip = descrip.withSymbolicTraits(.traitBold)!
                }

                if (traits.rawValue & UIFontDescriptorSymbolicTraits.traitItalic.rawValue) != 0 {
                    descrip = descrip.withSymbolicTraits(.traitItalic)!
                }

                attr.addAttribute(.font, value: UIFont(descriptor: descrip, size: fontSize ?? htmlFont.pointSize), range: range)
            }
        }

        self.init(attributedString: attr)
    }

}

Usage-1 (Replace font)

let attr = try? NSAttributedString(htmlString: "<strong>Hello</strong> World!", font: UIFont.systemFont(ofSize: 34, weight: .thin))

Usage-2 (NSMutableAttributedString example)

let attr = try! NSMutableAttributedString(htmlString: "<strong>Hello</strong> World!", font: UIFont.systemFont(ofSize: 34, weight: .thin))
attr.append(NSAttributedString(string: " MINIMIZE", attributes: [.link: "@m"]))

Usage-3 (Only convert HTML to NSAttributedString)

let attr = try? NSAttributedString(htmlString: "<strong>Hello</strong> World!")
like image 53
AamirR Avatar answered Sep 28 '22 03:09

AamirR


What you want to do, basically, is turn the NSAttributedString into an NSMutableAttributedString.

let attributedT = // ... attributed string
let mutableT = NSMutableAttributedString(attributedString:attributedT)

Now you can call addAttributes to apply attributes, such as a different font, over any desired range, such as the whole thing.

Unfortunately, however, a font without a symbolic trait such as italic is a different font from a font with that symbolic trait. Therefore, you're going to need a utility that copies the existing symbolic traits from a font and applies them to another font:

func applyTraitsFromFont(_ f1: UIFont, to f2: UIFont) -> UIFont? {
    let t = f1.fontDescriptor.symbolicTraits
    if let fd = f2.fontDescriptor.withSymbolicTraits(t) {
        return UIFont.init(descriptor: fd, size: 0)
    }
    return nil
}

Okay, so, armed with that utility, let's try it. I'll start with some simple HTML and convert it to an attributed string, just as you are doing:

let html = "<p>Hello <i>world</i>, hello</p>"
let data = html.data(using: .utf8)!
let att = try! NSAttributedString.init(
    data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
    documentAttributes: nil)
let matt = NSMutableAttributedString(attributedString:att)

As you can see, I've converted to an NSMutableAttributedString, as I advised. Now I'll cycle thru the style runs in terms of font, altering to a different font while using my utility to apply the existing traits:

matt.enumerateAttribute(
    NSFontAttributeName,
    in:NSMakeRange(0,matt.length),
    options:.longestEffectiveRangeNotRequired) { value, range, stop in
        let f1 = value as! UIFont
        let f2 = UIFont(name:"Georgia", size:20)!
        if let f3 = applyTraitsFromFont(f1, to:f2) {
            matt.addAttribute(
                NSFontAttributeName, value:f3, range:range)
        }
    }

Here's the result:

enter image description here

Obviously you could tweak this procedure to be even more sophisticated, depending on your design needs.

like image 31
matt Avatar answered Sep 28 '22 02:09

matt