在iOS中将HTML转换为NSAttributedString


151

我正在使用的实例UIWebView来处理一些文本并正确为其着色,它以HTML形式提供结果,而不是将其显示在UIWebView我想使用来显示Core Text的结果中NSAttributedString

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

我了解在Mac OS X下NSAttributedString有一种initWithHTML:方法,但这只是Mac的一种补充,不适用于iOS。

我也知道有一个与此类似的问题,但没有答案,尽管我会再试一次,看看是否有人创建了一种方法来进行此操作,如果可以,则是否可以共享。


2
HTML的NSAttributedString-Additions-for-HTML库已由同一作者重命名并放入框架中。现在称为DTCoreText,其中包括许多Core Text布局类。你可以找到它在这里
布赖恩·道格拉斯Moakley

Answers:


290

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

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

在Swift中:

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)

28
由于某种原因,选项NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType导致编码花费了非常长的时间:(
Arie

14
糟糕的是,NSHTMLTextDocumentType(实际上)比使用NSRange设置属性要慢1000倍。(配置了带有一个大胆标签的短标签。)
Jason Moore

6
请注意,如果您想从后台线程使用此方法,则无法使用此方法使用NSHTMLTextDocumentType。即使使用ios 7,它也不会使用TextKit进行HTML渲染。看看Ingve推荐的DTCoreText库。
TJez

2
太棒了 只是想一想,您可以将[NSNumber numberWithInt:NSUTF8StringEncoding]用作@(NSUTF8StringEncoding),不是吗?
Jarsen 2013年

15
我正在这样做,但是在iOS 8上要小心。它的速度非常慢,几百个字符接近一秒钟。(在iOS 7中几乎是瞬时的。)
Norman

43

Github的Oliver Drobnik 向NSAttributedString添加了一个正在进行中的开源程序。它使用NSScanner进行HTML解析。


需要分钟部署的iOS 4.3 :(无-的少,非常可观的。
哦,丹尼男孩

3
@Lirik Overkill对您来说也许很完美,但对其他人来说却是完美的,即您的评论丝毫没有帮助。
wuf810 2014年

3
请注意,该项目要求是开源的,并包含标准的2条款BSD许可证。这意味着您必须提到Cocoanetics是此代码的原始作者,并在您的应用程序内重现LICENSE文本。
dulgan

28

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

更新:事实证明NSAttributedString HTML呈现取决于引擎盖下的WebKit,并且必须在主线程上运行, 否则有时会因SIGTRAP而使应用程序崩溃

新的Relic崩溃日志:

在此处输入图片说明

以下是更新的线程安全的 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
}

输出:

在此处输入图片说明


安德鲁。一切正常。我想知道如果我要采用这种方法,那么我必须在UITextView中处理的所有短事件是什么。它可以处理HTML中可用的日历事件,电话,电子邮件,网站链接等吗?我希望UITextView能够处理与UILabel相比的事件。
harshit2811 '02

上面的方法仅适用于格式化。如果需要事件处理,我建议使用TTTAttributedLabel
Andrew Schreiber

NSAttributedString使用的默认编码是NSUTF16StringEncoding(不是UTF8!)。这就是为什么这行不通的原因。至少在我看来!
Umit Kaya

这应该是公认的解决方案。在后台线程上进行HTML字符串对话最终崩溃,并且在运行测试时非常频繁。
ratsimihah

21

Swift在NSAttributedString上的初始化程序扩展

我倾向于将其添加为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)

我希望世界变得像这样<p> <b> <i>你好</ i> </ b> <i>世界</ i> </ p>
Uma Madhavi

保存一些LOC并替换guard ... NSMutableAttributedString(data:...try self.init(data:...(并添加throws到init中)
nyg

最后它不起作用-文本获得随机字体大小
Vyachaslav Gerchicov

2
您解码与UTF-8的数据,但使用UTF-16编码它
希亚姆铢

11

这是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!)。


UTF16拯救了我的一天,谢谢samwize!

UTF16拯救了我的一天,谢谢samwize!

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 }

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
}

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!")

你救了我的日子。谢谢。
pkc456,19年

@ pkc456 meta.stackexchange.com/questions/5234/...,请给予好评:)谢谢!
AamirR

如何设置字体大小和字体系列?
kirqe

这比Mobile Dan的建议要好得多,因为它不涉及带有self.init(attributedString:attributedString)的冗余副本
氰化物

4

您现在唯一的解决方案是解析HTML,使用给定的point / font / etc属性构建一些节点,然后将它们组合在一起成为NSAttributedString。这是很多工作,但是如果做得正确,将来可以重用。


1
如果HTML是XHTML-Strict,则可以使用NSXMLDOcument和好友来帮助进行解析。
迪伦·卢克斯

您如何建议我去构建具有给定属性的节点?
约书亚

2
这是一个实现细节。无论您解析HTML是什么,都可以访问每个标签的每个属性,这些属性指定诸如字体名称,大小等之类的信息。您可以使用此信息存储需要添加到属性文本中的相关详细信息作为属性。通常,在处理此类任务之前,您需要先熟悉解析。
jer

2

上述解决方案是正确的。

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

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

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


@alecex我确实遇到了同样的问题!应用程序将在iOS 8.1、2、3上崩溃。但是在iOS 8.4或更高版本上会很好。您能详细说明如何避免吗?还是有任何解决方法,或者可以使用方法代替?
强劲,

我做了一个快速的类别来处理此问题,它复制了AppKit中的方法,该方法非常简单直观。为什么苹果不加它不属于我。:github.com/cguess/NSMutableAttributedString-HTML
CGuess

2

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


2

Swift 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()

0

有用的扩展

此线程,一个吊舱,并在iOS的美食食谱第80页埃里卡Sadun的ObjC例子启发,我写了一个扩展StringNSAttributedString去来回HTML纯字符串和NSAttributedStrings和副反之亦然之间-在GitHub上这里,我发现有帮助。

签名是(再次,在吉斯特完整的代码,上面的链接):

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 }

0

带字体

extension NSAttributedString
{
internal convenience init?(html: String, font: UIFont? = nil) {
    guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else {
        return nil
    }
    assert(Thread.isMainThread)
    guard let attributedString = try?  NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) else {
        return nil
    }
    let mutable = NSMutableAttributedString(attributedString: attributedString)
    if let font = font {
        mutable.addAttribute(.font, value: font, range: NSRange(location: 0, length: mutable.length))
    }
    self.init(attributedString: mutable)
}
}

或者,可以在设置attributedString之后使用它的派生版本并在UILabel上设置字体


0

内置的转换始终将文本颜色设置为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)
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.