Converter HTML para NSAttributedString no iOS

Eu estou usando uma instância do UIWebView para processar algum texto e colori-lo corretamente, ele dá o resultado como HTML, mas ao invés de exibi-lo no UIWebView eu quero exibi-lo usando o Core Text com um NSAttributedString .

Eu sou capaz de criar e desenhar o NSAttributedString mas não tenho certeza de como posso converter e mapear o HTML para a cadeia atribuída.

Eu entendo que, no Mac OS X, o NSAttributedString tem um método initWithHTML: mas essa é uma adição somente para Mac e não está disponível para iOS.

Eu também sei que há uma questão semelhante a isso, mas não tinha respostas, embora eu tente novamente e veja se alguém criou uma maneira de fazer isso e, em caso afirmativo, se eles poderiam compartilhá-lo.

No iOS 7, o UIKit adicionou um método initWithData: options: documentAttributes: error: que pode inicializar um NSAtttributedString usando HTML, por exemplo:

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

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

Há uma adição de código aberto em andamento para o NSAttributedString de Oliver Drobnik no Github. Ele usa o NSScanner para análise de HTML.

Criar um NSAttributedString de HTML deve ser feito no thread principal!

Atualização: Acontece que a renderização de NSAttributedString HTML depende do WebKit sob o capô e deve ser executada no thread principal ou, ocasionalmente, travará o aplicativo com um SIGTRAP .

Registro de falhas da New Relic:

insira a descrição da imagem aqui

Abaixo está uma extensão Swift 2 String atualizada para thread-safe :

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

Uso:

 let html = "
Here is some HTML
" html.attributedStringFromHTML { attString in self.bodyLabel.attributedText = attString }

Saída:

insira a descrição da imagem aqui

Extensão do inicializador Swift no NSAttributedString

Minha inclinação era adicionar isso como uma extensão para NSAttributedString vez de String . Eu tentei como uma extensão estática e um inicializador. Eu prefiro o inicializador, que é o que incluí abaixo.

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

Exemplo

 let html = "Hello World!" let attributedString = NSAttributedString(html: html) 

Esta é uma extensão de String escrita no Swift para retornar uma string HTML como 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 } } 

Usar,

 label.attributedText = "Hello \u{2022} babe".htmlAttributedString() 

Acima, adicionei propositalmente um unicode \ u2022 para mostrar que ele renderiza corretamente o unicode.

Um trivial: A codificação padrão usada pelo NSUTF16StringEncoding é NSUTF16StringEncoding (não UTF8!).

Swift 3.0 Xcode 8 versão

 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 } 

A única solução que você tem agora é analisar o HTML, criar alguns nós com determinados atributos de ponto / fonte / etc e combiná-los em um NSAttributedString. É muito trabalho, mas se feito corretamente, pode ser reutilizável no futuro.

Fiz algumas modificações na solução de Andrew e atualizei o código para o Swift 3:

Este código agora usa o UITextView como self e capaz de herdar sua fonte original, tamanho da fonte e cor do texto.

Nota: toHexString() é extensão daqui

 extension UITextView { func setAttributedStringFromHTML(_ htmlCode: String, completionBlock: @escaping (NSAttributedString?) ->()) { let inputText = "\(htmlCode)" 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) } } } } 

Exemplo de uso:

 mainTextView.setAttributedStringFromHTML("Hello world!") { _ in } 

Swift 4


  • Inicializador de conveniência NSAttributedString
  • Sem guardas extras
  • lança erro

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

Uso

 UILabel.attributedText = try? NSAttributedString(htmlString: "Hello World!") 

A solução acima está correta.

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

Mas o aplicativo wioll falhará se você o estiver executando no ios 8.1.2 ou 3.

Para evitar a falha, o que você pode fazer é: executar isso em uma fila. De modo que seja sempre no thread principal.

O uso de NSHTMLTextDocumentType é lento e é difícil controlar estilos. Eu sugiro que você tente minha biblioteca que é chamada Atributika. Ele tem seu próprio analisador de HTML muito rápido. Além disso, você pode ter qualquer nome de tag e definir qualquer estilo para eles.

Exemplo:

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

Você pode encontrá-lo aqui https://github.com/psharanda/Atributika

Swift 3 :
Tente isto :

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

E para usar:

 let str = "

Hello bro

Come On

Go sis

  • ME 1
  • ME 2

It is me bro , remember please

" self.contentLabel.attributedText = str.htmlAttributedString()

Extensões Úteis

Inspirado por este tópico, um pod e o exemplo ObjC de Erica Sadun no iOS Gourmet Cookbook p.80, eu escrevi uma extensão em String e em NSAttributedString para NSAttributedString entre HTML simples e NSAttributedStrings e vice-versa – no GitHub aqui , o que achei útil.

As assinaturas são (novamente, código completo em uma essência, link acima):

 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 }