Konvertieren Sie HTML in NSAttributedString in iOS


151

Ich verwende eine Instanz von UIWebView, um Text zu verarbeiten und ihn korrekt zu färben. Er gibt das Ergebnis als HTML aus, aber anstatt es in dem anzuzeigen, in dem UIWebViewich es Core Textmit einem anzeigen möchte NSAttributedString.

Ich kann das erstellen und zeichnen NSAttributedString, bin mir aber nicht sicher, wie ich den HTML-Code konvertieren und in die zugewiesene Zeichenfolge abbilden kann.

Ich verstehe, dass unter Mac OS X NSAttributedStringeine initWithHTML:Methode hat, aber dies war eine Ergänzung nur für Mac und ist nicht für iOS verfügbar.

Ich weiß auch, dass es eine ähnliche Frage gibt, aber sie hatte keine Antworten. Ich würde jedoch erneut versuchen, herauszufinden, ob jemand einen Weg gefunden hat, dies zu tun, und wenn ja, ob er sie teilen könnte.


2
Die NSAttributedString-Additions-for-HTML-Bibliothek wurde vom selben Autor umbenannt und in ein Framework gerollt. Es heißt jetzt DTCoreText und enthält eine Reihe von Core Text-Layoutklassen. Sie finden es hier
Brian Douglas Moakley

Antworten:


290

In iOS 7 hat UIKit eine initWithData:options:documentAttributes:error:Methode hinzugefügt, NSAttributedStringmit der HTML mithilfe von HTML initialisiert werden kann, z.

[[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
Aus irgendeinem Grund führt die Option NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType dazu, dass die Codierung sehr, sehr lange
Arie Litovsky

14
Schade, dass NSHTMLTextDocumentType (buchstäblich) ~ 1000x langsamer ist als das Setzen von Attributen mit NSRange. (Profiliert ein kurzes Etikett mit einem fett gedruckten Tag.)
Jason Moore

6
Beachten Sie, dass Sie NSHTMLTextDocumentType mit dieser Methode nicht verwenden können, wenn Sie sie aus einem Hintergrundthread verwenden möchten. Selbst mit ios 7 wird TextKit nicht für das HTML-Rendering verwendet. Schauen Sie sich die von Ingve empfohlene DTCoreText-Bibliothek an.
TJez

2
Genial. Nur ein Gedanke, Sie könnten wahrscheinlich [NSNumber numberWithInt: NSUTF8StringEncoding] als @ (NSUTF8StringEncoding) ausführen, nein?
Jarsen

15
Ich habe das getan, aber sei vorsichtig mit iOS 8. Es ist schmerzhaft langsam, für ein paar hundert Zeichen fast eine Sekunde. (In iOS 7 war es fast augenblicklich.)
Norman

43

Zu NSAttributedString von Oliver Drobnik bei Github gibt es eine Open-Source-Erweiterung in Arbeit . Es verwendet NSScanner für die HTML-Analyse.


Erfordert eine minimale Bereitstellung von iOS 4.3 :( Trotzdem sehr beeindruckend.
Oh Danny Boy

3
@Lirik Overkill für Sie vielleicht, aber perfekt für jemand anderen, dh Ihr Kommentar ist nicht im geringsten hilfreich.
Wuf810

3
Bitte beachten Sie, dass dieses Projekt Open Source erfordert und durch eine Standard-BSD-Lizenz mit zwei Klauseln abgedeckt ist. Das bedeutet, dass Sie Cocoanetics als ursprünglichen Autor dieses Codes erwähnen und den LIZENZTExt in Ihrer App reproduzieren müssen.
Dulgan

28

Das Erstellen eines NSAttributedString aus HTML muss im Hauptthread erfolgen!

Update: Es stellt sich heraus, dass das HTML-Rendering von NSAttributedString von WebKit unter der Haube abhängt und im Hauptthread ausgeführt werden muss . Andernfalls stürzt die App gelegentlich mit einem SIGTRAP ab .

Neues Relic-Absturzprotokoll:

Geben Sie hier die Bildbeschreibung ein

Unten finden Sie eine aktualisierte thread-sichere Swift 2 String-Erweiterung:

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

Verwendung:

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

Ausgabe:

Geben Sie hier die Bildbeschreibung ein


Andrew. Das funktioniert gut. Ich wollte wissen, was für kurze Ereignisse ich in meinem UITextView behandeln muss, wenn ich diesen Ansatz wählen möchte. Kann es Kalenderereignisse, Anrufe, E-Mails, Website-Links usw. verarbeiten, die in HTML verfügbar sind? Ich hoffe, UITextView kann Ereignisse im Vergleich zu UILabel verarbeiten.
hardit2811

Der obige Ansatz eignet sich nur zum Formatieren. Ich würde die Verwendung von TTTAttributedLabel empfehlen, wenn Sie eine Ereignisbehandlung benötigen.
Andrew Schreiber

Die Standardcodierung, die NSAttributedString verwendet, ist NSUTF16StringEncoding (nicht UTF8!). Deshalb wird das nicht funktionieren. Zumindest in meinem Fall!
Umit Kaya

Dies sollte die akzeptierte Lösung sein. Ein HTML - String - Gespräch auf einem Hintergrund - Thread tun wird schließlich abstürzen, und ziemlich häufig während Tests ausgeführt werden .
Ratsimihah

21

Schnelle Initialisierungserweiterung auf NSAttributedString

Meine Neigung war es, dies NSAttributedStringeher als Erweiterung als als hinzuzufügen String. Ich habe es als statische Erweiterung und als Initialisierer versucht. Ich bevorzuge den Initialisierer, den ich unten aufgeführt habe.

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

Beispiel

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

Ich möchte, dass die Hallo Welt so ist <p> <b> <i> Hallo </ i> </ b> <i> Welt </ i> </ p>
Uma Madhavi

Speichern Sie etwas LOC und ersetzen Sie es guard ... NSMutableAttributedString(data:...durch try self.init(data:...(und fügen Sie throwses dem Init hinzu)
Nyg

und schließlich funktioniert es nicht - Text gewinnt zufällige Schriftgröße
Vyachaslav Gerchicov

2
Sie decodieren die Daten mit UTF-8, aber Sie codieren sie mit UTF-16
Shyam Bhat

11

Dies ist eine StringErweiterung, die in Swift geschrieben wurde, um eine HTML-Zeichenfolge als zurückzugeben 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
    }
}

Benutzen,

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

Oben habe ich absichtlich einen Unicode \ u2022 hinzugefügt, um zu zeigen, dass er Unicode korrekt wiedergibt.

Ein Trivial: Die Standardcodierung, die NSAttributedStringverwendet wird , ist NSUTF16StringEncoding(nicht UTF8!).


UTF16 hat mir den Tag gerettet, danke samwize!
Yueyu

UTF16 hat mir den Tag gerettet, danke samwize!
Yueyu

6

Nehmen Sie einige Änderungen an Andrews Lösung vor und aktualisieren Sie den Code auf Swift 3:

Dieser Code verwendet jetzt UITextView als selfund kann seine ursprüngliche Schriftart, Schriftgröße und Textfarbe erben

Hinweis: toHexString()ist eine Erweiterung von hier

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

Anwendungsbeispiel:

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


  • NSAttributedString Convenience Initialisierer
  • Ohne zusätzliche Wachen
  • wirft Fehler

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

}

Verwendung

UILabel.attributedText = try? NSAttributedString(htmlString: "<strong>Hello</strong> World!")

Du rettest meinen Tag. Danke dir.
pkc456

@ pkc456 meta.stackexchange.com/questions/5234/… , stimme zu :) danke!
AamirR

Wie kann ich Schriftgröße und Schriftfamilie einstellen?
Kirqe

Das ist viel besser als von Mobile Dan vorgeschlagen, da es sich nicht um eine redundante Kopie mit self.init handelt (zugeschriebener String: zugeschriebener String)
Cyanid

4

Die einzige Lösung, die Sie derzeit haben, besteht darin, den HTML-Code zu analysieren, einige Knoten mit bestimmten Attributen point / font / etc aufzubauen und sie dann zu einem NSAttributedString zu kombinieren. Es ist viel Arbeit, aber wenn es richtig gemacht wird, kann es in Zukunft wiederverwendbar sein.


1
Wenn der HTML-Code XHTML-streng ist, können Sie NSXMLDOcument und Freunde verwenden, um beim Parsen zu helfen.
Dylan Lukes

Wie würden Sie vorschlagen, dass ich die Knoten mit bestimmten Attributen aufbaue?
Joshua

2
Das ist ein Implementierungsdetail. Unabhängig davon, wie Sie den HTML-Code analysieren, haben Sie Zugriff auf jedes Attribut für jedes Tag, das beispielsweise einen Schriftnamen, eine Schriftgröße usw. angibt. Mithilfe dieser Informationen können Sie die relevanten Details speichern, die Sie dem zugeordneten Text als Attribute hinzufügen müssen . Im Allgemeinen müssen Sie sich zuerst mit dem Parsen vertraut machen, bevor Sie eine solche Aufgabe angehen.
jer

2

Die obige Lösung ist korrekt.

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

Aber die App wird abstürzen, wenn Sie sie auf ios 8.1.2 oder 3 ausführen.

Um den Absturz zu vermeiden, können Sie Folgendes tun: Führen Sie dies in einer Warteschlange aus. Damit es immer am Hauptfaden ist.


@alecex Ich habe das gleiche Problem getroffen! Die App stürzt unter iOS 8.1, 2, 3 ab. Unter iOS 8.4 oder höher ist sie jedoch in Ordnung. Können Sie im Detail erklären, wie Sie dies vermeiden können? oder gibt es irgendwelche Umgehungsmöglichkeiten oder können stattdessen Methoden verwendet werden?
Stark

Ich habe eine kurze Kategorie erstellt, um dies zu handhaben, indem ich die Methoden aus AppKit kopiert habe, was eine sehr einfache und intuitive Möglichkeit bietet, dies zu tun. Warum Apple es nicht hinzugefügt hat, ist mir ein
Rätsel

2

Die Verwendung von NSHTMLTextDocumentType ist langsam und es ist schwierig, Stile zu steuern. Ich empfehle Ihnen, meine Bibliothek mit dem Namen Atributika auszuprobieren. Es hat einen eigenen sehr schnellen HTML-Parser. Sie können auch beliebige Tag-Namen haben und einen beliebigen Stil für sie definieren.

Beispiel:

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

label.attributedText = str

Sie finden es hier https://github.com/psharanda/Atributika


2

Swift 3 :
Versuchen Sie Folgendes :

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

Und für die Verwendung:

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

Hilfreiche Erweiterungen

Angeregt durch diesen Thread, eine Hülse, und Erica Sadun des ObjC Beispiel in iOS Gourmet - Kochbuch S.80, schrieb ich eine Erweiterung auf Stringund NSAttributedStringgehe hin und her zwischen HTML Plain-Strings und NSAttributedStrings und umgekehrt - auf GitHub hier , die Ich habe hilfreich gefunden.

Die Signaturen sind (wieder vollständiger Code in einem Gist, Link oben):

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

mit Schriftart

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

Alternativ können Sie die Versionen verwenden, von denen dies abgeleitet wurde, und die Schriftart auf UILabel festlegen, nachdem attribatedString festgelegt wurde


0

Durch die integrierte Konvertierung wird die Textfarbe immer auf UIColor.black festgelegt, auch wenn Sie ein Attributwörterbuch übergeben, bei dem .forgroundColor auf etwas anderes festgelegt ist. Probieren Sie diese Version der Erweiterung unter NSAttributedString aus, um den DARK-Modus unter iOS 13 zu unterstützen.

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)
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.