Convierta HTML a NSAttributedString en iOS


151

Estoy usando una instancia de UIWebViewpara procesar algo de texto y colorearlo correctamente, da el resultado como HTML, pero en lugar de mostrarlo en el UIWebViewquiero mostrarlo usando Core Texta NSAttributedString.

Puedo crear y dibujar el, NSAttributedStringpero no estoy seguro de cómo puedo convertir y asignar el HTML a la cadena atribuida.

Entiendo que bajo Mac OS X NSAttributedStringtiene un initWithHTML:método, pero esta fue una adición de Mac y no está disponible para iOS.

También sé que hay una pregunta similar a esto, pero no tenía respuestas, pensé que volvería a intentarlo y vería si alguien ha creado una forma de hacerlo y, de ser así, si podrían compartirlo.


2
La biblioteca NSAttributedString-Additions-for-HTML ha sido renombrada y puesta en un marco por el mismo autor. Ahora se llama DTCoreText e incluye un montón de clases de diseño de Core Text. Lo puedes encontrar aquí
Brian Douglas Moakley

Respuestas:


290

En iOS 7, UIKit agregó un initWithData:options:documentAttributes:error:método que puede inicializar NSAttributedStringusando 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)

28
Por alguna razón, la opción NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType está causando que la codificación tarde mucho, mucho tiempo :(
Arie Litovsky

14
Lástima que NSHTMLTextDocumentType sea (literalmente) ~ 1000 veces más lento que establecer atributos con NSRange. (Perfilado una etiqueta corta con una etiqueta en negrita.)
Jason Moore

66
Tenga en cuenta que si no puede NSHTMLTextDocumentType con este método, si desea usarlo desde un hilo de fondo. Incluso con ios 7, no usará TextKit para la representación HTML. Eche un vistazo a la biblioteca DTCoreText recomendada por Ingve.
TJez

2
Increíble. Solo un pensamiento, probablemente podría hacer [NSNumber numberWithInt: NSUTF8StringEncoding] como @ (NSUTF8StringEncoding), ¿no?
Jarsen

15
Estaba haciendo esto, pero tenga cuidado con iOS 8. Es dolorosamente lento, cerca de un segundo para unos cientos de caracteres. (En iOS 7 fue casi instantáneo.)
Norman

43

Hay una adición de código abierto de trabajo en progreso a NSAttributedString por Oliver Drobnik en Github. Utiliza NSScanner para el análisis HTML.


Requiere un despliegue mínimo de iOS 4.3 :( Sin embargo, muy impresionante.
Oh Danny Boy

3
@Lirik Overkill para ti tal vez pero perfecto para otra persona, es decir, tu comentario no es en lo más mínimo útil.
wuf810

3
Tenga en cuenta que este proyecto requiere es de código abierto y está cubierto por una licencia BSD estándar de 2 cláusulas. Eso significa que debe mencionar a Cocoanetics como el autor original de este código y reproducir el texto de LICENCIA dentro de su aplicación.
dulgan

28

¡La creación de una NSAttributedString desde HTML debe hacerse en el hilo principal!

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

Nuevo registro de bloqueo de reliquias:

ingrese la descripción de la imagen aquí

A continuación se muestra una extensión de cadena Swift 2 segura para subprocesos actualizada :

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:

ingrese la descripción de la imagen aquí


Andrés. Esto está funcionando bien. Quería saber qué eventos cortos tengo que manejar en mi UITextView si voy a seguir este enfoque. ¿Puede manejar eventos de calendario, llamadas, correos electrónicos, enlaces a sitios web, etc. disponibles en HTML? Espero que UITextView pueda manejar eventos comparados con UILabel.
harshit2811

El enfoque anterior solo es bueno para formatear. Recomendaría usar TTTAttributedLabel si necesita manejo de eventos.
Andrew Schreiber

La codificación predeterminada que usa NSAttributedString es NSUTF16StringEncoding (¡no UTF8!). Es por eso que esto no funcionará. ¡Al menos en mi caso!
Umit Kaya

Esta debería ser la solución aceptada. Haciendo una conversación cadena HTML en un subproceso de fondo será finalmente estrellarse, y con bastante frecuencia durante la ejecución de las pruebas.
ratsimihah

21

Extensión de inicializador Swift en NSAttributedString

Mi inclinación era agregar esto como una extensión en NSAttributedStringlugar de String. Lo probé 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 = "<b>Hello World!</b>"
let attributedString = NSAttributedString(html: html)

Quiero que Hello World sea así <p><b><i>hello</i> </b> <i>world</i> </p>
Uma Madhavi

Guarde algo de LOC y reemplácelo guard ... NSMutableAttributedString(data:...por try self.init(data:...(y agréguelo throwsal init)
nyg

y finalmente no funciona - el texto gana un tamaño de fuente aleatorio
Vyachaslav Gerchicov

2
Está decodificando los datos con UTF-8 pero los codificó con UTF-16
Shyam Bhat

11

Esta es una Stringextensión 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 = "<b>Hello</b> \u{2022} babe".htmlAttributedString()

En lo anterior, he agregado a propósito unicode \ u2022 para mostrar que procesa unicode correctamente.

Un trivial: la codificación predeterminada que NSAttributedStringusa es NSUTF16StringEncoding(¡no UTF8!).


UTF16 me salvó el día, ¡Gracias samwize!
Yueyu

UTF16 me salvó el día, ¡Gracias samwize!
Yueyu

6

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

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

Nota: toHexString()es la extensión desde 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 }

5

Versión 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

Swift 4


  • Inicializador de conveniencia NSAttributedString
  • Sin guardias adicionales
  • arroja 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!")

Me salvas el día. Gracias.
pkc456

@ pkc456 meta.stackexchange.com/questions/5234/… , haga una votación :) ¡gracias!
AamirR

¿Cómo puedo configurar el tamaño de fuente y la familia de fuentes?
kirqe

Eso es mucho mejor de lo sugerido por Mobile Dan, ya que no implica una copia redundante con self.init (attributeString: attributeString)
cianuro

4

La única solución que tiene en este momento es analizar el HTML, construir algunos nodos con atributos de punto / fuente / etc., y luego combinarlos en una NSAttributedString. Es mucho trabajo, pero si se hace correctamente, puede ser reutilizable en el futuro.


1
Si el HTML es XHTML-Strict, puede usar NSXMLDOcument y amigos para ayudar con el análisis.
Dylan Lukes

¿Cómo sugeriría que vaya a construir los nodos con atributos dados?
Joshua

2
Ese es un detalle de implementación. Independientemente de cómo analice el HTML, tiene acceso a cada atributo para cada etiqueta, que especifica cosas como el nombre de la fuente, el tamaño, etc. Puede usar esta información para almacenar los detalles relevantes que necesitaría agregar al texto atribuido como atributos . Por lo general, primero debe familiarizarse con el análisis antes de abordar dicha tarea.
jer

2

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 está ejecutando en iOS 8.1,2 o 3.

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


@alecex ¡Encontré el mismo problema! la aplicación se bloqueará en iOS 8.1, 2, 3. Pero estará bien en iOS 8.4 o posterior. ¿Puedes explicar en detalle cómo evitarlo? o hay alguna solución o se pueden usar métodos en su lugar?
Fuerte

Hice una categoría rápida para manejar esto, copiando los métodos de AppKit, que tiene una forma muy fácil e intuitiva de hacerlo. ¿Por qué Apple no añadió que es más allá de mí .: github.com/cguess/NSMutableAttributedString-HTML
CGuess

2

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 = "<strong>Hello</strong> World!".style(tags:
    Style("strong").font(.boldSystemFont(ofSize: 15))).attributedString

label.attributedText = str

Lo puedes encontrar aquí https://github.com/psharanda/Atributika


2

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 = "<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

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 una Stringy otra vez NSAttributedStringpara ir y venir entre cadenas simples HTML y NSAttributedStrings y viceversa, aquí en GitHub , que He encontrado útil

Las firmas son (nuevamente, 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 }

0

con 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 usar las versiones de las que se deriva y establecer la fuente en UILabel después de configurartribuString


0

La conversión integrada siempre establece el color del texto en UIColor.black, incluso si pasa un diccionario de atributos con .forgroundColor establecido en otra cosa. Para admitir el modo OSCURO en iOS 13, pruebe esta versión de la extensión en 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)
    }
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.