Swift- 更改具有自己样式的 HTML 字符串的字体

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

我正在动态地从 Wordpress API 获取一个 HTML 字符串并将其解析为属性字符串以在我的应用程序中显示它。由于字符串有自己的样式,它显示不同的字体和大小,这影响了我们的设计选择。

我想做的是更改整个属性字符串的字体及其大小。

我尝试在属性字符串的选项中这样做,但它什么也没做:

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

有人对如何实现这个有任何想法吗?

P.S。我知道我可以在字符串的开头或结尾添加一个 CSS 标记,但这会覆盖其中的其他样式吗?另外,如果这是一个有效的解决方案,您能否提供一个示例来说明如何做到这一点?

基本上,您要做的是将 NSAttributedString 转换为 NSMutableAttributedString。

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

现在您可以调用 addAttributes 将属性(例如不同的字体)应用于任何所需范围(例如整个范围)。

然而不幸的是,没有斜体等符号特征的字体与具有该符号特征的字体不同的字体。因此,您将需要一个实用程序来从一种字体复制现有的符号特征并将它们应用到另一种字体:

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
}

好的,有了这个实用程序,我们来试试吧。我将从一些简单的 HTML 开始并将其转换为属性字符串,就像您正在做的那样:

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)

如您所见,我已按照我的建议转换为 NSMutableAttributedString。现在我将根据字体循环运行样式,在使用我的实用程序应用现有特征时更改为不同的字体:

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)
        }
    }

结果如下:

显然,您可以根据您的设计需要将此过程调整得更加复杂。

setAttributes 将重置 HTML 中的所有属性。我写了一个扩展方法来避免这种情况:

Swift 4

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

    guard let data = html.data(using: .utf8, allowLossyConversion: true) else {
        throw NSError(domain: "Parse Error", code: 0, userInfo: nil)
    }

    if let font = font {
        guard let attr = try? NSMutableAttributedString(data: data, options: options, documentAttributes: nil) else {
            throw NSError(domain: "Parse Error", code: 0, userInfo: nil)
        }
        var attrs = attr.attributes(at: 0, effectiveRange: nil)
        attrs[NSAttributedStringKey.font] = font
        attr.setAttributes(attrs, range: NSRange(location: 0, length: attr.length))
        self.init(attributedString: attr)
    } else {
        try? self.init(data: data, options: options, documentAttributes: nil)
    }
}

测试样本:

let html = "<html><body><h1 style=\"color:red;\">html text here</h1></body></html>"
let font = UIFont.systemFont(ofSize: 16)

var attr = try NSMutableAttributedString(HTMLString: html, font: nil)
var attrs = attr?.attributes(at: 0, effectiveRange: nil)
attrs?[NSAttributedStringKey.font] as? UIFont
// print: <UICTFont: 0x7ff19fd0a530> font-family: "TimesNewRomanPS-BoldMT"; font-weight: bold; font-style: normal; font-size: 24.00pt

attr = try NSMutableAttributedString(HTMLString: html, font: font)
attrs = attr?.attributes(at: 0, effectiveRange: nil)
attrs?[NSAttributedStringKey.font] as? UIFont
// print: <UICTFont: 0x7f8c0cc04620> font-family: ".SFUIText"; font-weight: normal; font-style: normal; font-size: 16.00pt
let font = UIFont(name: fontName, size: fontSize)
textAttributes[NSFontAttributeName] = font
self.attributedText = NSAttributedString(string: self.text, attributes: textAttributes)

Swift4


  • NSAttributedString 带有便利初始值设定项的扩展
  • 枚举属性字符串(HTML文档)字体属性,并替换为提供的UIFont
  • 保留原始 HTML 字体大小,或使用提供的 UIFont 中的字体大小,@参见 useDocumentFontSize 参数
  • 这个方法可以简单的把HTML转成NSAttributedString,没有字体操作的重载,直接跳过字体参数,@see guard语句

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(替换字体)

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

用法-2(NSMutableAttributedString示例)

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

用法-3(只将HTML转换为NSAttributedString)

let attr = try? NSAttributedString(htmlString: "<strong>Hello</strong> World!")

Swift 3 我之前的版本 (Swift 4) 解决方案


extension NSAttributedString {

    convenience init(htmlString html: String, font: UIFont? = nil, useDocumentFontSize: Bool = true) throws {
        let options: [String : Any] = [
            NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
            NSCharacterEncodingDocumentAttribute: 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(NSFontAttributeName, 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(NSFontAttributeName, value: UIFont(descriptor: descrip, size: fontSize ?? htmlFont.pointSize), range: range)
            }
        }

        self.init(attributedString: attr)
    }

}
let font = "<font face='Montserrat-Regular' size='13' color= 'black'>%@"
let html = String(format: font, yourhtmlstring)
webView.loadHTMLString(html, baseURL: nil)

只是想感谢@AamirR 的回复,并警告其他未来的用户注意代码中的一个小错误。

如果您使用它,您可能会遇到粗体和斜体字符串的问题,其中最后只使用了其中一个特征。这是修复了该错误的相同代码,希望对您有所帮助:

extension NSAttributedString {

convenience init(htmlString html: String, font: UIFont? = nil, useDocumentFontSize: Bool = true) throws {
    let options: [String : Any] = [
        NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
        NSCharacterEncodingDocumentAttribute: 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(NSFontAttributeName, in: range, options: .longestEffectiveRangeNotRequired) { attrib, range, _ in
        if let htmlFont = attrib as? UIFont {
            var traits = htmlFont.fontDescriptor.symbolicTraits
            var descrip = htmlFont.fontDescriptor.withFamily(fontFamily)

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

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

            descrip = descrip.withSymbolicTraits(traits)!
            attr.addAttribute(NSFontAttributeName, value: UIFont(descriptor: descrip, size: fontSize ?? htmlFont.pointSize), range: range)
        }
    }

    self.init(attributedString: attr)
  }
}

感谢@AamirR post。另外我认为没有必要添加额外的“useDocumentFontSize”。如果你不想改变字体,只发送字体参数 nil。

Swift 5 版本:

extension NSAttributedString { convenience init(htmlString html: String, font: UIFont? = nil) 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? = font == nil ? 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 & UIFontDescriptor.SymbolicTraits.traitBold.rawValue) != 0 {
                descrip = descrip.withSymbolicTraits(.traitBold)!
            }

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

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

    self.init(attributedString: attr)
}