Convertir HTML para NSAttributedString en IOS
-
26-09-2019 - |
Pregunta
Estoy utilizando una instancia de UIWebView
para procesar un texto y el color correctamente, da el resultado como HTML, pero en vez de mostrarla en el UIWebView
quiero mostrarlo usando Core Text
con un NSAttributedString
.
Soy capaz de crear y dibujar la NSAttributedString
pero no estoy seguro de cómo puedo convertir y mapear el HTML en la cadena con atributos.
Me entender que en Mac OS X NSAttributedString
tiene un método initWithHTML:
pero esto fue una única Mac suma y no está disponible para iOS.
también sé que hay una pregunta similar a esta pero no tenía respuestas, aunque yo lo intentaría de nuevo y ver si alguien ha creado una manera de hacer esto y si es así, si pudieran compartirlo.
Solución
En iOS 7, UIKit añade un initWithData:options:documentAttributes:error:
método que puede inicializar un NSAtttributedString
el uso de 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)
Otros consejos
Hay una obra en curso adición de fuente abierta a NSAttributedString por Oliver Drobnik en Github. Utiliza NSScanner para el análisis de HTML.
Creación de un NSAttributedString de HTML se debe hacer en el hilo principal!
Actualización: Resulta que NSAttributedString de representación HTML depende de WebKit bajo el capó, y se debe ejecutar en el hilo principal o de vez en cuando bloquee la aplicación con un SIGTRAP .
New Relic registro de bloqueo:
A continuación se muestra un thread-safe Swift 2 extensión actualizado de la secuencia:
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 = "<center>Here is some <b>HTML</b></center>"
html.attributedStringFromHTML { attString in
self.bodyLabel.attributedText = attString
}
Salida:
Swift inicializador extensión en NSAttributedString
Mi inclinación era añadir esto como una extensión a NSAttributedString
en lugar de String
. Lo probé como una extensión y un inicializador estático. Yo 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 = "<b>Hello World!</b>"
let attributedString = NSAttributedString(html: html)
Esta es una extensión 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
}
}
Para utilizar,
label.attributedText = "<b>Hello</b> \u{2022} babe".htmlAttributedString()
En lo anterior, he añadido un propósito Unicode \ u2022 para demostrar que hace Unicode correctamente.
Un trivial:. El valor por defecto de codificación que es NSAttributedString
usos NSUTF16StringEncoding
(! No UTF8)
Swift 3,0 Xcode 8 Versión
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
}
Swift 4
- NSAttributedString conveniencia inicializador
- Sin guardias adicionales
- lanza de 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: "<strong>Hello</strong> World!")
La única solución que tenemos ahora es analizar el código HTML, construir algunos nodos con atributos dados Punto / fuente / etc, y luego combinarlos en un NSAttributedString. Es mucho trabajo, pero si se hace correctamente, puede ser reutilizable en el futuro.
hecho algunas modificaciones en la solución Andrew 's y actualizar el código para Swift 3:
Este código utilizan ahora como UITextView self
y capaces de heredar de su fuente original, tamaño de fuente y color del texto
Nota: toHexString()
es la extensión del aquí
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)
}
}
}
}
Ejemplo de uso:
mainTextView.setAttributedStringFromHTML("<i>Hello world!</i>") { _ in }
La solución anterior es correcta.
[[NSAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
documentAttributes:nil error:nil];
embargo, la aplicación wioll bloquee si está ejecutando en ios 8.1,2 ó 3.
Para evitar el choque de lo que puede hacer es: ejecutar esto en una cola. De modo que siempre sea el hilo principal.
El uso de NSHTMLTextDocumentType es lento y es difícil de estilos de control. Le sugiero que pruebe mi biblioteca que se llama Atributika. Tiene su propio analizador de HTML muy rápido. También se puede tener cualquier nombre de etiqueta y definir cualquier estilo para ellos.
Ejemplo:
let str = "<strong>Hello</strong> World!".style(tags:
Style("strong").font(.boldSystemFont(ofSize: 15))).attributedString
label.attributedText = str
Se puede encontrar aquí https://github.com/psharanda/Atributika
Swift 3 :
Probar
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 = "<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()
Extensiones útiles
Inspirado en este hilo, una vaina, y el ejemplo ObjC de Erica Sadun en IOS Gourmet Cookbook p.80, me escribió una extensión en String
y en NSAttributedString
ir y venir entre HTML plano-secuencias y NSAttributedStrings y viceversa - en GitHub aquí , que he encontrado útiles.
La firmas son (de nuevo, el código completo en un Gist, enlace de 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 }
con la fuente
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)
}
}
Alternativamente, puede utilizar las versiones este se deriva de y conjunto fuente en UILabel después de establecer attributedString