Convertir le HTML en NSAttributedString dans iOS

151

J'utilise une instance de UIWebViewpour traiter du texte et le colorier correctement, cela donne le résultat au format HTML mais plutôt que de l'afficher dans le UIWebViewJe veux l'afficher en utilisant Core Textavec un NSAttributedString.

Je suis capable de créer et de dessiner le NSAttributedStringmais je ne sais pas comment je peux convertir et mapper le HTML dans la chaîne attribuée.

Je comprends que sous Mac OS X NSAttributedStringa une initWithHTML:méthode, mais il s'agissait d'un ajout uniquement Mac et n'est pas disponible pour iOS.

Je sais aussi qu'il y a une question similaire à celle-ci, mais elle n'a pas de réponse, je pensais que j'essaierais à nouveau et voir si quelqu'un a créé un moyen de le faire et si oui, s'il pouvait le partager.

Joshua
la source
2
La bibliothèque NSAttributedString-Additions-for-HTML a été renommée et intégrée dans un framework par le même auteur. Il s'appelle maintenant DTCoreText et comprend un tas de classes de disposition Core Text. Vous pouvez le trouver ici
Brian Douglas Moakley

Réponses:

290

Dans iOS 7, UIKit a ajouté une initWithData:options:documentAttributes:error:méthode qui peut initialiser un en NSAttributedStringutilisant HTML, par exemple:

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

Dans 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)
pix
la source
28
Pour une raison quelconque, l'option NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType fait que l'encodage prend vraiment très longtemps :(
Arie Litovsky
14
Dommage que NSHTMLTextDocumentType soit (littéralement) ~ 1000x plus lent que la définition des attributs avec NSRange. (Profil d'une étiquette courte avec une étiquette en gras.)
Jason Moore
6
Sachez que si vous ne pouvez pas NSHTMLTextDocumentType avec cette méthode, si vous souhaitez l'utiliser à partir d'un thread d'arrière-plan. Même avec ios 7, il n'utilisera pas TextKit pour le rendu HTML. Jetez un œil à la bibliothèque DTCoreText recommandée par Ingve.
TJez
2
Impressionnant. Juste une pensée, vous pourriez probablement faire [NSNumber numberWithInt: NSUTF8StringEncoding] comme @ (NSUTF8StringEncoding), non?
Jarsen
15
Je faisais cela, mais attention sur iOS 8. C'est douloureusement lent, proche d'une seconde pour quelques centaines de caractères. (Dans iOS 7, c'était presque instantané.)
Norman
43

Il existe un ajout open source en cours de développement à NSAttributedString par Oliver Drobnik sur Github. Il utilise NSScanner pour l'analyse HTML.

Ingve
la source
Nécessite un déploiement minimum d'iOS 4.3: (Néanmoins, très impressionnant.
Oh Danny Boy
3
@Lirik Overkill pour vous peut-être mais parfait pour quelqu'un d'autre, c'est-à-dire que votre commentaire n'est pas du tout utile.
wuf810
3
Veuillez noter que ce projet nécessite qu'il soit open source et couvert par une licence BSD standard à 2 clauses. Cela signifie que vous devez mentionner Cocoanetics comme l'auteur original de ce code et reproduire le texte de la LICENCE dans votre application.
dulgan le
28

La création d'un NSAttributedString à partir de HTML doit être effectuée sur le thread principal!

Mise à jour: Il s'avère que le rendu HTML de NSAttributedString dépend de WebKit sous le capot et doit être exécuté sur le thread principal, sinon il plantera parfois l'application avec un SIGTRAP .

Journal des plantages de New Relic:

entrez la description de l'image ici

Vous trouverez ci-dessous une extension Swift 2 String sécurisée pour les threads :

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

Usage:

let html = "<center>Here is some <b>HTML</b></center>"
html.attributedStringFromHTML { attString in
    self.bodyLabel.attributedText = attString
}

Production:

entrez la description de l'image ici

Andrew Schreiber
la source
Andrew. Cela fonctionne bien. Je voulais savoir ce que tout sauf les événements que je dois gérer dans mon UITextView si je vais suivre cette approche. Peut-il gérer les événements du calendrier, les appels, les e-mails, les liens vers le site Web, etc. disponibles en HTML? J'espère qu'UITextView est capable de gérer les événements par rapport à UILabel.
harshit2811
L'approche ci-dessus n'est bonne que pour le formatage. Je recommanderais d'utiliser TTTAttributedLabel si vous avez besoin de la gestion des événements.
Andrew Schreiber
Le codage par défaut utilisé par NSAttributedString est NSUTF16StringEncoding (pas UTF8!). C'est pourquoi cela ne fonctionnera pas. Au moins dans mon cas!
Umit Kaya
Cela devrait être la solution acceptée. Faire une conversation de chaîne HTML sur un thread d'arrière-plan finira par planter, et assez fréquemment pendant l'exécution des tests.
ratsimihah
21

Extension d'initialisation Swift sur NSAttributedString

Mon inclination était d'ajouter ceci comme une extension à NSAttributedStringplutôt que String. Je l'ai essayé en tant qu'extension statique et initialiseur. Je préfère l'initialiseur qui est ce que j'ai inclus ci-dessous.

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

Exemple

let html = "<b>Hello World!</b>"
let attributedString = NSAttributedString(html: html)
Mobile Dan
la source
je veux que bonjour le monde soit comme ça <p><b><i> bonjour</i> </b> <i>world</i> </p>
Uma Madhavi
Enregistrez quelques LOC et remplacez-les guard ... NSMutableAttributedString(data:...par try self.init(data:...(et ajoutez-les throwsà l'init)
nyg
et finalement cela ne fonctionne pas - le texte gagne une taille de police aléatoire
Vyachaslav Gerchicov
2
Vous décodez les données avec UTF-8 mais vous les avez encodées avec UTF-16
Shyam Bhat
11

Il s'agit d'une Stringextension écrite en Swift pour renvoyer une chaîne HTML sous la forme 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
    }
}

Utiliser,

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

Dans ce qui précède, j'ai volontairement ajouté un unicode \ u2022 pour montrer qu'il restitue correctement unicode.

Un trivial: l'encodage par défaut qui NSAttributedStringutilise est NSUTF16StringEncoding(pas UTF8!).

samwize
la source
UTF16 m'a sauvé la journée, merci samwize!
Yueyu
UTF16 m'a sauvé la journée, merci samwize!
Yueyu
6

Fait quelques modifications sur Andrew la solution d' et mise à jour du code vers Swift 3:

Ce code utilise maintenant UITextView comme selfet peut hériter de sa police d'origine, de sa taille de police et de sa couleur de texte

Remarque: toHexString()est l'extension d' ici

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

Exemple d'utilisation:

mainTextView.setAttributedStringFromHTML("<i>Hello world!</i>") { _ in }
He Yifei 何 一 非
la source
5

Version de 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
}
fssilva
la source
5

Swift 4


  • Initialiseur de commodité NSAttributedString
  • Sans gardes supplémentaires
  • jette une erreur

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

}

Usage

UILabel.attributedText = try? NSAttributedString(htmlString: "<strong>Hello</strong> World!")
AamirR
la source
Vous sauvez ma journée. Je vous remercie.
pkc456
@ pkc456 meta.stackexchange.com/questions/5234/… , faites un vote positif :) merci!
AamirR
Comment puis-je définir la taille et la famille de police?
kirqe
C'est beaucoup mieux que suggéré par Mobile Dan, car il n'implique pas une copie redondante avec self.init (AttributeString: AttributeString)
cyanure
4

La seule solution que vous avez actuellement est d'analyser le HTML, de créer des nœuds avec des attributs point / font / etc donnés, puis de les combiner dans un NSAttributedString. C'est beaucoup de travail, mais s'il est fait correctement, il peut être réutilisable à l'avenir.

secousse
la source
1
Si le code HTML est XHTML-Strict, vous pouvez utiliser NSXMLDOcument et des amis pour aider à l'analyse.
Dylan Lukes
Comment suggéreriez-vous que je construise les nœuds avec des attributs donnés?
Joshua
2
C'est un détail de mise en œuvre. Quelle que soit la manière dont vous analysez le code HTML, vous avez accès à chaque attribut pour chaque balise, qui spécifie des éléments tels qu'un nom de police, une taille, etc. Vous pouvez utiliser ces informations pour stocker les détails pertinents que vous devez ajouter au texte attribué en tant qu'attributs . En général, vous devez d'abord vous familiariser avec l'analyse syntaxique avant de vous attaquer à une telle tâche.
jer
2

La solution ci-dessus est correcte.

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

Mais l'application wioll plante si vous l'exécutez sur iOS 8.1, 2 ou 3.

Pour éviter le plantage, ce que vous pouvez faire est: exécutez ceci dans une file d'attente. Pour qu'il soit toujours sur le fil principal.

Nitesh Kumar Singh
la source
@alecex j'ai rencontré le même problème! l'application plantera sur iOS 8.1, 2, 3. Mais ce sera bien sur iOS 8.4 ou version ultérieure. Pouvez-vous expliquer en détail comment l'éviter? ou y a-t-il une solution, ou des méthodes peuvent-elles être utilisées à la place?
Strong
J'ai créé une catégorie rapide pour gérer cela, en copiant les méthodes d'AppKit, qui offre un moyen très simple et intuitif de le faire. Pourquoi Apple ne l'a pas ajouté me dépasse: github.com/cguess/NSMutableAttributedString-HTML
CGuess
2

L'utilisation de NSHTMLTextDocumentType est lente et il est difficile de contrôler les styles. Je vous suggère d'essayer ma bibliothèque qui s'appelle Atributika. Il possède son propre analyseur HTML très rapide. Vous pouvez également avoir n'importe quel nom de balise et définir n'importe quel style pour eux.

Exemple:

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

label.attributedText = str

Vous pouvez le trouver ici https://github.com/psharanda/Atributika

Pavel Sharanda
la source
2

Swift 3 :
Essayez ceci :

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

Et pour utiliser:

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()
reza_khalafi
la source
0

Extensions utiles

Inspiré par ce fil, un pod et l'exemple ObjC d'Erica Sadun dans iOS Gourmet Cookbook p.80, j'ai écrit une extension encore Stringet encore NSAttributedStringpour aller et venir entre les chaînes HTML simples et NSAttributedStrings et vice versa - sur GitHub ici , qui J'ai trouvé utile.

Les signatures sont (encore une fois, le code complet dans un Gist, lien ci-dessus):

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 }
AmitaiB
la source
0

avec police

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

Vous pouvez également utiliser les versions dont il a été dérivé et définir la police sur UILabel après avoir défini attribuéString

Anton Tropashko
la source
0

La conversion intégrée définit toujours la couleur du texte sur UIColor.black, même si vous passez un dictionnaire d'attributs avec .forgroundColor défini sur autre chose. Pour prendre en charge le mode DARK sur iOS 13, essayez cette version de l'extension sur 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)
    }
}
Stephen Orr
la source