Converti HTML in NSAttributedString in iOS


151

Sto usando un'istanza di UIWebViewper elaborare un po 'di testo e colorarlo correttamente, dà il risultato come HTML ma invece di visualizzarlo nel UIWebViewvoglio visualizzarlo usando Core Textcon a NSAttributedString.

Sono in grado di creare e disegnare il NSAttributedStringma non sono sicuro di come posso convertire e mappare l'HTML nella stringa attribuita.

Capisco che sotto Mac OS X NSAttributedStringha un initWithHTML:metodo ma questa era un'aggiunta solo per Mac e non è disponibile per iOS.

So anche che esiste una domanda simile a questa, ma non ha avuto risposte, anche se proverei di nuovo e vedrei se qualcuno ha creato un modo per farlo e, in tal caso, se potesse condividerlo.


2
La libreria NSAttributedString-Additions-for-HTML è stata rinominata e inserita in un framework dallo stesso autore. Ora si chiama DTCoreText e include un gruppo di classi di layout di testo principale. Puoi trovarlo qui
Brian Douglas Moakley il

Risposte:


290

In iOS 7, UIKit ha aggiunto un initWithData:options:documentAttributes:error:metodo che può inizializzare un NSAttributedStringutilizzo di HTML, ad esempio:

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

In 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
Per qualche motivo, l'opzione NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType sta facendo in modo che la codifica impieghi molto, molto tempo :(
Arie Litovsky

14
Peccato che NSHTMLTextDocumentType sia (letteralmente) ~ 1000 volte più lento dell'impostazione degli attributi con NSRange. (Ha profilato un'etichetta corta con un tag in grassetto.)
Jason Moore,

6
Tenere presente che se non è possibile NSHTMLTextDocumentType con questo metodo, se si desidera utilizzarlo da un thread in background. Anche con iOS 7, non utilizzerà TextKit per il rendering HTML. Dai un'occhiata alla libreria DTCoreText consigliata da Ingve.
TJez,

2
Eccezionale. Solo un pensiero, potresti probabilmente fare [NSNumber numberWithInt: NSUTF8StringEncoding] come @ (NSUTF8StringEncoding), no?
Jarsen,

15
Lo stavo facendo, ma fai attenzione su iOS 8. È dolorosamente lento, vicino a un secondo per alcune centinaia di caratteri. (In iOS 7 è stato quasi istantaneo.)
Norman

43

C'è un'aggiunta open source work-in-progress a NSAttributedString di Oliver Drobnik a Github. Utilizza NSScanner per l'analisi HTML.


Richiede una distribuzione minima di iOS 4.3 :( Ciononostante, molto impressionante.
Oh Danny Boy,

3
@Lirik Overkill per te forse ma perfetto per qualcun altro, cioè il tuo commento non è affatto utile.
wuf810,

3
Si noti che questo progetto richiede che sia open source e coperto da una licenza BSD standard a 2 clausole. Ciò significa che devi citare Cocoanetics come autore originale di questo codice e riprodurre il testo della LICENZA all'interno della tua app.
dulgan,

28

La creazione di un NSAttributedString da HTML deve essere eseguita sul thread principale!

Aggiornamento: Si scopre che il rendering HTML di NSAttributedString dipende da WebKit sotto il cofano e deve essere eseguito sul thread principale o occasionalmente si blocca l'app con un SIGTRAP .

Nuovo registro degli arresti reliquia:

inserisci qui la descrizione dell'immagine

Di seguito è riportata un'estensione della stringa Swift 2 thread-safe aggiornata :

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
}

Produzione:

inserisci qui la descrizione dell'immagine


Andrea. Funziona benissimo. Volevo sapere quali eventi a corto di eventi devo gestire nel mio UITextView se seguirò questo approccio. Può gestire eventi di calendario, chiamate, e-mail, collegamenti a siti Web ecc. Disponibili in HTML? Spero che UITextView sia in grado di gestire gli eventi rispetto a UILabel.
harshit2811,

L'approccio sopra è buono solo per la formattazione. Consiglierei di usare TTTAttributedLabel se hai bisogno di una gestione degli eventi.
Andrew Schreiber,

La codifica predefinita utilizzata da NSAttributedString è NSUTF16StringEncoding (non UTF8!). Ecco perché questo non funzionerà. Almeno nel mio caso!
Umit Kaya,

Questa dovrebbe essere la soluzione accettata. Effettuare una conversazione con stringa HTML su un thread in background alla fine si arresterà in modo anomalo e abbastanza spesso durante l'esecuzione dei test.
ratsimihah,

21

Estensione di inizializzazione rapida su NSAttributedString

La mia inclinazione era quella di aggiungere questo come estensione NSAttributedStringpiuttosto che String. L'ho provato come estensione statica e inizializzatore. Preferisco l'inizializzatore che è quello che ho incluso di seguito.

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

Esempio

let html = "<b>Hello World!</b>"
let attributedString = NSAttributedString(html: html)

voglio che il mondo ciao sia così <p><b><i> ciao</i> </b> <i> mondo</i> </p>
Uma Madhavi,

Salva un po 'di LOC e sostituiscilo guard ... NSMutableAttributedString(data:...con try self.init(data:...(e aggiungilo throwsa init)
nyg

e infine non funziona - il testo ottiene una dimensione del carattere casuale
Vyachaslav Gerchicov

2
Stai decodificando i dati con UTF-8 ma li hai codificati con UTF-16
Shyam Bhat,

11

Questa è Stringun'estensione scritta in Swift per restituire una stringa HTML come 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
    }
}

Usare,

label.attributedText = "<b>Hello</b> \u{2022} babe".htmlAttributedString()

In quanto sopra, ho volutamente aggiunto un unicode \ u2022 per mostrare che rende unicode correttamente.

Un banale: la codifica predefinita che NSAttributedStringutilizza è NSUTF16StringEncoding(non UTF8!).


UTF16 mi ha salvato la giornata, grazie samwize!
Yueyu,

UTF16 mi ha salvato la giornata, grazie samwize!
Yueyu,

6

Apportate alcune modifiche alla soluzione di Andrew e aggiorna il codice a Swift 3:

Questo codice ora utilizza UITextView come selfe in grado di ereditare il carattere originale, la dimensione del carattere e il colore del testo

Nota: toHexString()è l'estensione da qui

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

Esempio di utilizzo:

mainTextView.setAttributedStringFromHTML("<i>Hello world!</i>") { _ in }

5

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
}

5

Swift 4


  • Inizializzatore convenienza NSAttributedString
  • Senza protezioni extra
  • genera errore

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

Mi salvi la giornata. Grazie.
pkc456,

@ pkc456 meta.stackexchange.com/questions/5234/… , fai un voto :) grazie!
AamirR

Come posso impostare la dimensione e la famiglia di caratteri?
Kirir

È molto meglio di quanto suggerito da Mobile Dan, dal momento che non comporta una copia ridondante con self.init (attributoString: attribuitoString)
cianuro

4

L'unica soluzione che hai in questo momento è analizzare l'HTML, creare alcuni nodi con determinati attributi point / font / etc, quindi combinarli insieme in un NSAttributedString. È un sacco di lavoro, ma se fatto correttamente, può essere riutilizzabile in futuro.


1
Se l'HTML è XHTML-Strict, è possibile utilizzare NSXMLDOcument e gli amici per aiutare con l'analisi.
Dylan Lukes,

Come mi consiglieresti di costruire i nodi con determinati attributi?
Giosuè,

2
Questo è un dettaglio di implementazione. Tuttavia, se si analizza l'HTML, si ha accesso a ciascun attributo per ciascun tag, che specifica elementi come il nome del carattere, la dimensione, ecc. È possibile utilizzare queste informazioni per memorizzare i dettagli rilevanti che è necessario aggiungere al testo attribuito come attributi . In generale, è necessario acquisire familiarità con l'analisi prima di affrontare un tale compito.
Jer

2

La soluzione sopra è corretta.

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

Ma l'app si bloccherà se la stai eseguendo su iOS 8.1,2 o 3.

Per evitare il crash ciò che puoi fare è: eseguilo in una coda. In modo che sia sempre sul thread principale.


@alecex Ho riscontrato lo stesso problema! l'app andrà in crash su iOS 8.1, 2, 3. Ma andrà bene su iOS 8.4 o versioni successive. Puoi spiegarci in dettaglio come evitarlo? o c'è qualche soluzione o si possono usare metodi invece?
Forte

Ho creato una rapida categoria per gestirlo, copiando i metodi da AppKit, che ha un modo molto semplice e intuitivo per farlo. Perché Apple non l'ha aggiunto è oltre me: github.com/cguess/NSMutableAttributedString-HTML
CGuess

2

L'uso di NSHTMLTextDocumentType è lento ed è difficile controllare gli stili. Ti suggerisco di provare la mia biblioteca che si chiama Atributika. Ha un proprio parser HTML molto veloce. Inoltre puoi avere qualsiasi nome di tag e definire qualsiasi stile per loro.

Esempio:

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

label.attributedText = str

Puoi trovarlo qui https://github.com/psharanda/Atributika


2

Swift 3 :
prova questo :

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 per l'utilizzo:

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

Estensioni utili

Ispirato da questo thread, un pod e dall'esempio ObjC di Erica Sadun in iOS Gourmet Cookbook p.80, ho scritto un'estensione avanti Stringe NSAttributedStringindietro per andare avanti e indietro tra le stringhe HTML e NSAttributedStrings e viceversa - su GitHub qui , che Ho trovato utile.

Le firme sono (di nuovo, codice completo in un Gist, link sopra):

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 carattere

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

in alternativa puoi usare le versioni da cui è stato derivato e impostare font su UILabel dopo aver impostato gli attributiString


0

La conversione integrata imposta sempre il colore del testo su UIColor.black, anche se si passa un dizionario di attributi con .forgroundColor impostato su qualcos'altro. Per supportare la modalità SCURO su iOS 13, prova questa versione dell'estensione su 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)
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.