163

我正在使用一个实例UIWebView来处理一些文本并正确着色,它以 HTML 形式给出结果,而不是在UIWebView我想Core TextNSAttributedString.

我能够创建和绘制,NSAttributedString但我不确定如何将 HTML 转换和映射到属性字符串。

我知道在 Mac OS X 下NSAttributedString有一种initWithHTML:方法,但这只是 Mac 的附加功能,不适用于 iOS。

我也知道有一个类似的问题,但它没有答案,但我会再试一次,看看是否有人创造了一种方法来做到这一点,如果有,他们是否可以分享。

4

17 回答 17

305

在 iOS 7 中,UIKit 添加了一个可以使用 HTMLinitWithData:options:documentAttributes:error:初始化的方法,例如:NSAttributedString

[[NSAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding] 
                                 options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                           NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)} 
                      documentAttributes:nil error:nil];

在斯威夫特:

let htmlData = NSString(string: details).data(using: String.Encoding.unicode.rawValue)
let options = [NSAttributedString.DocumentReadingOptionKey.documentType:
        NSAttributedString.DocumentType.html]
let attributedString = try? NSMutableAttributedString(data: htmlData ?? Data(),
                                                          options: options,
                                                          documentAttributes: nil)
于 2013-09-19T05:21:28.340 回答
47

Github 上的 Oliver Drobnik 正在对 NSAttributedString进行开源补充。它使用 NSScanner 进行 HTML 解析。

于 2011-01-10T23:03:42.270 回答
32

从 HTML 创建 NSAttributedString 必须在主线程上完成!

更新:事实证明 NSAttributedString HTML 渲染依赖于 WebKit,并且必须在主线程上运行, 否则它偶尔会使用 SIGTRAP 使应用程序崩溃

新遗物崩溃日志:

在此处输入图像描述

下面是更新的线程安全Swift 2 String 扩展:

extension String {
    func attributedStringFromHTML(completionBlock:NSAttributedString? ->()) {
        guard let data = dataUsingEncoding(NSUTF8StringEncoding) else {
            print("Unable to decode data from html string: \(self)")
            return completionBlock(nil)
        }

        let options = [NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
                   NSCharacterEncodingDocumentAttribute: NSNumber(unsignedInteger:NSUTF8StringEncoding)]

        dispatch_async(dispatch_get_main_queue()) {
            if let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) {
                completionBlock(attributedString)
            } else {
                print("Unable to create attributed string from html string: \(self)")
                completionBlock(nil)
            }
        }
    }
}

用法:

let html = "<center>Here is some <b>HTML</b></center>"
html.attributedStringFromHTML { attString in
    self.bodyLabel.attributedText = attString
}

输出:

在此处输入图像描述

于 2015-12-09T23:25:40.787 回答
22

NSAttributedString 上的 Swift 初始化程序扩展

我的倾向是将其添加为扩展NSAttributedString而不是String. 我尝试将其作为静态扩展和初始化程序。我更喜欢下面包含的初始化程序。

斯威夫特 4

internal convenience init?(html: String) {
    guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else {
        return nil
    }

    guard let attributedString = try?  NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) else {
        return nil
    }

    self.init(attributedString: attributedString)
}

斯威夫特 3

extension NSAttributedString {

internal convenience init?(html: String) {
    guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else {
        return nil
    }

    guard let attributedString = try? NSMutableAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) else {
        return nil
    }

    self.init(attributedString: attributedString)
}
}

例子

let html = "<b>Hello World!</b>"
let attributedString = NSAttributedString(html: html)
于 2017-03-06T19:00:25.703 回答
12

这是一个String用 Swift 编写的扩展,用于将 HTML 字符串返回为NSAttributedString.

extension String {
    func htmlAttributedString() -> NSAttributedString? {
        guard let data = self.dataUsingEncoding(NSUTF16StringEncoding, allowLossyConversion: false) else { return nil }
        guard let html = try? NSMutableAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil) else { return nil }
        return html
    }
}

要使用,

label.attributedText = "<b>Hello</b> \u{2022} babe".htmlAttributedString()

在上面,我特意添加了一个 unicode \u2022 来表明它正确地呈现了 unicode。

一个小问题:使用的默认编码NSAttributedStringNSUTF16StringEncoding(不是 UTF8!)。

于 2016-05-25T03:23:33.170 回答
6

对Andrew的解决方案进行了一些修改,并将代码更新为 Swift 3:

此代码现在使用 UITextView 作为self并能够继承其原始字体、字体大小和文本颜色

注意:是从这里toHexString()扩展

extension UITextView {
    func setAttributedStringFromHTML(_ htmlCode: String, completionBlock: @escaping (NSAttributedString?) ->()) {
        let inputText = "\(htmlCode)<style>body { font-family: '\((self.font?.fontName)!)'; font-size:\((self.font?.pointSize)!)px; color: \((self.textColor)!.toHexString()); }</style>"

        guard let data = inputText.data(using: String.Encoding.utf16) else {
            print("Unable to decode data from html string: \(self)")
            return completionBlock(nil)
        }

        DispatchQueue.main.async {
            if let attributedString = try? NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil) {
                self.attributedText = attributedString
                completionBlock(attributedString)
            } else {
                print("Unable to create attributed string from html string: \(self)")
                completionBlock(nil)
            }
        }
    }
}

示例用法:

mainTextView.setAttributedStringFromHTML("<i>Hello world!</i>") { _ in }
于 2017-01-07T07:52:52.997 回答
5

Swift 3.0 Xcode 8 版本

func htmlAttributedString() -> NSAttributedString? {
    guard let data = self.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
    guard let html = try? NSMutableAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil) else { return nil }
    return html
}
于 2016-09-28T18:19:48.267 回答
5

斯威夫特 4


  • NSAttributedString 便利初始化器
  • 没有额外的警卫
  • 抛出错误

extension NSAttributedString {

    convenience init(htmlString html: String) throws {
        try self.init(data: Data(html.utf8), options: [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
        ], documentAttributes: nil)
    }

}

用法

UILabel.attributedText = try? NSAttributedString(htmlString: "<strong>Hello</strong> World!")
于 2017-11-30T19:28:48.487 回答
3

您现在唯一的解决方案是解析 HTML,建立一些具有给定点/字体/等属性的节点,然后将它们组合成一个 NSAttributedString。这是很多工作,但如果做得正确,将来可以重复使用。

于 2010-11-18T17:42:03.743 回答
3

NSHTMLTextDocumentType 的使用速度很慢,而且很难控制样式。我建议你试试我的名为 Atributika 的图书馆。它有自己非常快速的 HTML 解析器。您也可以有任何标签名称并为它们定义任何样式。

例子:

let str = "<strong>Hello</strong> World!".style(tags:
    Style("strong").font(.boldSystemFont(ofSize: 15))).attributedString

label.attributedText = str

你可以在这里找到它https://github.com/psharanda/Atributika

于 2017-02-22T19:51:58.220 回答
2

上述解决方案是正确的。

[[NSAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding] 
                                 options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
                                           NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)} 
                      documentAttributes:nil error:nil];

但是,如果您在 ios 8.1、2 或 3 上运行该应用程序,它将崩溃。

为避免崩溃,您可以做的是:在队列中运行它。这样它总是在主线程上。

于 2015-12-30T07:00:02.320 回答
2

斯威夫特 3
试试这个

extension String {
    func htmlAttributedString() -> NSAttributedString? {
        guard let data = self.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
        guard let html = try? NSMutableAttributedString(
            data: data,
            options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
            documentAttributes: nil) else { return nil }
        return html
    }
}  

并用于:

let str = "<h1>Hello bro</h1><h2>Come On</h2><h3>Go sis</h3><ul><li>ME 1</li><li>ME 2</li></ul> <p>It is me bro , remember please</p>"

self.contentLabel.attributedText = str.htmlAttributedString()
于 2017-02-26T22:46:50.497 回答
1

内置转换总是将文本颜色设置为 UIColor.black,即使您传递了一个将 .forgroundColor 设置为其他内容的属性字典。要在 iOS 13 上支持 DARK 模式,请在 NSAttributedString 上尝试此版本的扩展。

extension NSAttributedString {
    internal convenience init?(html: String)                    {
        guard 
            let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }

        let options : [DocumentReadingOptionKey : Any] = [
            .documentType: NSAttributedString.DocumentType.html,
            .characterEncoding: String.Encoding.utf8.rawValue
        ]

        guard
            let string = try? NSMutableAttributedString(data: data, options: options,
                                                 documentAttributes: nil) else { return nil }

        if #available(iOS 13, *) {
            let colour = [NSAttributedString.Key.foregroundColor: UIColor.label]
            string.addAttributes(colour, range: NSRange(location: 0, length: string.length))
        }

        self.init(attributedString: string)
    }
}
于 2020-04-06T16:21:54.117 回答
1

这是Mobile Dan 的答案Swift 5版本:

public extension NSAttributedString {
    convenience init?(_ html: String) {
        guard let data = html.data(using: .unicode) else {
                return nil
        }

        try? self.init(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
    }
}
于 2020-09-27T11:34:46.807 回答
0

有用的扩展

受此线程、一个 pod 和 Erica Sadun 在 iOS Gourmet Cookbook p.80 中的 ObjC 示例的启发,我编写了一个扩展程序,StringNSAttributedStringHTML 纯字符串和 NSAttributedStrings 之间来回切换,反之亦然——在 GitHub上我发现很有帮助。

签名是(同样,Gist 中的完整代码,上面的链接):

extension NSAttributedString {
    func encodedString(ext: DocEXT) -> String?
    static func fromEncodedString(_ eString: String, ext: DocEXT) -> NSAttributedString? 
    static func fromHTML(_ html: String) -> NSAttributedString? // same as above, where ext = .html
}

extension String {
    func attributedString(ext: DocEXT) -> NSAttributedString?
}

enum DocEXT: String { case rtfd, rtf, htm, html, txt }
于 2016-09-18T03:57:52.720 回答
0

尊重字体家族,动态字体我编造了这个可憎的:

extension NSAttributedString
{
    convenience fileprivate init?(html: String, font: UIFont? = Font.dynamic(style: .subheadline))
    {
        guard let data = html.data(using: String.Encoding.utf8, allowLossyConversion: true) else {
        var totalString = html
        /*
         https://stackoverflow.com/questions/32660748/how-to-use-apples-new-san-francisco-font-on-a-webpage
            .AppleSystemUIFont I get in font.familyName does not work
         while -apple-system does:
         */
        var ffamily = "-apple-system"
        if let font = font {
            let lLDBsucks = font.familyName
            if !lLDBsucks.hasPrefix(".appleSystem") {
                ffamily = font.familyName
            }
            totalString = "<style>\nhtml * {font-family: \(ffamily) !important;}\n            </style>\n" + html
        }
        guard let data = totalString.data(using: String.Encoding.utf8, allowLossyConversion: true) else {
            return nil
        }
        assert(Thread.isMainThread)
        guard let attributedText = try?  NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) else {
            return nil
        }
        let mutable = NSMutableAttributedString(attributedString: attributedText)
        if let font = font {
        do {
            var found = false
            mutable.beginEditing()
            mutable.enumerateAttribute(NSAttributedString.Key.font, in: NSMakeRange(0, attributedText.length), options: NSAttributedString.EnumerationOptions(rawValue: 0)) { (value, range, stop) in
                    if let oldFont = value as? UIFont {
                        let newsize = oldFont.pointSize * 15 * Font.scaleHeruistic / 12
                        let newFont = oldFont.withSize(newsize)
                        mutable.addAttribute(NSAttributedString.Key.font, value: newFont, range: range)
                        found = true
                    }
                }
                if !found {
                    // No font was found - do something else?
                }

            mutable.endEditing()
            
//            mutable.addAttribute(.font, value: font, range: NSRange(location: 0, length: mutable.length))
        }
        self.init(attributedString: mutable)
    }

}

或者,您可以使用派生出来的版本,并在设置属性字符串后在 UILabel 上设置字体

虽然这会破坏封装在属性字符串中的大小和粗体

感谢您阅读所有答案。你是一个非常有耐心的男人女人或孩子。

于 2019-05-31T08:57:08.153 回答
0

一个将 html 转换为属性 NSAttributedString 的函数,它将适应动态大小 + 适应文本的可访问性。

static func convertHtml(string: String?) -> NSAttributedString? {
    
    guard let string = string else {return nil}
    
    guard let data = string.data(using: .utf8) else {
        return nil
    }
    
    do {
        let attrStr = try NSAttributedString(data: data,
                                      options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue],
                                      documentAttributes: nil)
        let range = NSRange(location: 0, length: attrStr.length)
        let str = NSMutableAttributedString(attributedString: attrStr)
        
        str.enumerateAttribute(NSAttributedString.Key.font, in: NSMakeRange(0, str.length), options: .longestEffectiveRangeNotRequired) {
            (value, range, stop) in
            if let font = value as? UIFont {
                
                let userFont =  UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title2)
                let pointSize = userFont.withSize(font.pointSize)
                let customFont = UIFont.systemFont(ofSize: pointSize.pointSize)
                let dynamicText = UIFontMetrics.default.scaledFont(for: customFont)
                str.addAttribute(NSAttributedString.Key.font,
                                         value: dynamicText,
                                         range: range)
            }
        }

        str.addAttribute(NSAttributedString.Key.underlineStyle, value: 0, range: range)
        
        return NSAttributedString(attributedString: str.attributedSubstring(from: range))
    } catch {}
    return nil
    
}

要使用:

let htmToStringText = convertHtml(string: html)
            
  self.bodyTextView.isEditable = false
  self.bodyTextView.isAccessibilityElement = true
  self.bodyTextView.adjustsFontForContentSizeCategory = true
  self.bodyTextView.attributedText = htmToStringText
  self.bodyTextView.accessibilityAttributedLabel = htmToStringText
于 2022-02-17T19:10:59.747 回答