Convierte HTML a NSAttributedString en iOS

Estoy usando una instancia de UIWebView para procesar texto y colorearlo correctamente, da el resultado como HTML pero en lugar de mostrarlo en UIWebView , quiero mostrarlo usando Core Text con NSAttributedString .

Puedo crear y dibujar NSAttributedString pero no estoy seguro de cómo puedo convertir y mapear el HTML en la cadena atribuida.

Entiendo que bajo Mac OS X NSAttributedString tiene un método initWithHTML: pero esto fue solo una adición de Mac y no está disponible para iOS.

También sé que hay una pregunta similar a esta, pero no tenía respuestas, aunque volvería a intentar y ver si alguien ha creado una forma de hacerlo y, de ser así, si podrían compartirla.

En iOS 7, UIKit agregó un método initWithData: options: documentAttributes: error: que puede inicializar un NSAtttributedString usando HTML, por ejemplo:

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

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

Hay una adición de fuente abierta de trabajo en progreso a NSAttributedString por Oliver Drobnik en Github. Utiliza NSScanner para analizar HTML.

¡Crear una cadena NSAttributedString desde HTML debe hacerse en el hilo principal!

Actualización: resulta que la representación HTML NSAttributedString depende de WebKit bajo el capó, y debe ejecutarse en el hilo principal o de vez en cuando se bloqueará la aplicación con un SIGTRAP .

Nuevo registro de locking de Relic:

enter image description here

A continuación, se encuentra una extensión actualizada de Swift 2 String segura para subprocesos :

 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 }

Salida:

enter image description here

Extensión de inicializador Swift en NSAttributedString

Mi inclinación era agregar esto como una extensión a NSAttributedString lugar de String . Lo intenté como una extensión estática y un inicializador. Prefiero el inicializador, que es lo que he incluido a continuación.

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

Ejemplo

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

Esta es una extensión de String escrita en Swift para devolver una cadena 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() 

En lo anterior, he agregado deliberadamente un unicode \ u2022 para mostrar que representa unicode correctamente.

Una NSAttributedString trivial: la encoding predeterminada que utiliza NSUTF16StringEncoding es NSUTF16StringEncoding (¡no UTF8!).

Swift 3.0 Xcode 8 Version

 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 } 

La única solución que tiene ahora es analizar el HTML, crear algunos nodos con atributos de puntos / fonts / etc. dados, luego combinarlos en un NSAttributedString. Es mucho trabajo, pero si se hace correctamente, puede ser reutilizable en el futuro.

Hizo algunas modificaciones en la solución de Andrew y actualizó el código a Swift 3:

Este código ahora usa UITextView como self y es capaz de heredar su fuente original, tamaño de fuente y color de texto

Nota: toHexString() es una extensión desde aquí

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

Ejemplo de uso:

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

Swift 4


  • Inicializador de conveniencia NSAttributedString
  • Sin guardias adicionales
  • lanza un error

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

La solución anterior es correcta.

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

Pero la aplicación se bloqueará si la ejecuta en ios 8.1.2 o 3.

Para evitar el locking, lo que puede hacer es: ejecutar esto en una cola. Para que siempre esté en el hilo principal.

El uso de NSHTMLTextDocumentType es lento y es difícil controlar los estilos. Te sugiero que pruebes mi biblioteca, que se llama Atributika. Tiene su propio analizador de HTML muy rápido. También puede tener cualquier nombre de etiqueta y definir cualquier estilo para ellos.

Ejemplo:

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

Puede encontrarlo aquí https://github.com/psharanda/Atributika

Swift 3 :
Prueba esto :

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

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

Extensiones útiles

Inspirado por este hilo, un pod y el ejemplo ObjC de Erica Sadun en iOS Gourmet Cookbook p.80, escribí una extensión en String y en NSAttributedString para ir y venir entre HTML strings simples y NSAttributedStrings y viceversa – en GitHub aquí , que he encontrado útil.

Las firmas son (nuevamente, código completo en un enlace Gist, enlace arriba):

 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 }