Преобразование HTML в NSAttributedString в iOS
Я использую экземпляр UIWebView
для обработки некоторого текста и его корректного цвета, он дает результат как HTML, а не выводит его в UIWebView
, я хочу отобразить его с помощью Core Text
с помощью NSAttributedString
.
Я могу создать и нарисовать NSAttributedString
, но я не уверен, как я могу преобразовать и отобразить HTML в атрибутированную строку.
Я понимаю, что в Mac OS X NSAttributedString
есть метод initWithHTML:
, но это было добавление только для Mac и недоступно для iOS.
Я также знаю, что есть аналогичный вопрос, но ответа у него нет, хотя я бы попробовал еще раз и посмотрел, создал ли кто-нибудь способ сделать это, и если да, то могли бы поделиться им.
Ответы
Ответ 1
В iOS 7 UIKit добавил метод initWithData:options:documentAttributes:error:
, который может инициализировать NSAtttributedString
с использованием 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)
Ответ 2
В Github находится Оливер Дробник, работающий без изменений с открытым исходным кодом в NSAttributedString. Он использует NSScanner для разбора HTML.
Ответ 3
Создание NSAttributedString из HTML должно выполняться в основном потоке!
Обновление. Оказывается, что рендеринг HTML NSAttributedString зависит от 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
}
Вывод:
![введите описание изображения здесь]()
Ответ 4
Расширение инициализации Swift на NSAttributedString
Моя склонность заключалась в том, чтобы добавить это как расширение к NSAttributedString
, а не к String
. Я пробовал его как статическое расширение и инициализатор. Я предпочитаю инициализатор, что и было ниже.
Swift 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)
}
Swift 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)
Ответ 5
Это расширение 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, чтобы показать, что он правильно отображает юникод.
Тривиально: по умолчанию используется NSAttributedString
NSUTF16StringEncoding
(не UTF8!).
Ответ 6
версия 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
}
Ответ 7
Swift 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!")
Ответ 8
Единственное решение, которое у вас есть сейчас, - это разобрать HTML-код, создать некоторые узлы с заданными атрибутами point/font/etc, а затем объединить их в NSAttributedString. Это большая работа, но если все сделано правильно, можно будет повторно использовать в будущем.
Ответ 9
Сделал некоторые изменения в 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 }
Ответ 10
Вышеупомянутое решение является правильным.
[[NSAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
documentAttributes:nil error:nil];
Но сбой приложения wioll, если вы используете его на ios 8.1,2 или 3.
Чтобы избежать сбоя, вы можете: запустить его в очереди. Так что он всегда будет в основном потоке.
Ответ 11
Использование 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
Ответ 12
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()
Ответ 13
Полезные расширения
Вдохновленный этой нитью, pod и примером Erica Sadun ObjC в iOS Gourmet Cookbook p.80, я написал расширение на String
и NSAttributedString
, чтобы идти туда и обратно между стандартными строками HTML и 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 }
Ответ 14
Есть ли кокоапод, который осуществляет десериализацию строки nsattributed из htmldata?
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)
Я использую это в textView, но десериализованная attributeString возвращается с дополнительным пробелом и другими забавными ошибками!
Ответ 15
со шрифтом
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)
}
}
в качестве альтернативы вы можете использовать версии, которые были получены и установлены
шрифт на UILabel после установки attributeString