Konwertuj HTML na NSAttributedString w iOS

151

Używam wystąpienie UIWebViewprzetworzyć tekst i kolor to poprawnie, to daje wynik w postaci HTML, ale zamiast wyświetlania go w UIWebViewchcę, aby wyświetlić go przy użyciu Core Textz NSAttributedString.

Jestem w stanie utworzyć i narysować, NSAttributedStringale nie jestem pewien, jak mogę przekonwertować i zmapować kod HTML na przypisany ciąg.

Rozumiem, że pod Mac OS X NSAttributedStringma initWithHTML:metodę, ale był to tylko dodatek do Maca i nie jest dostępny dla iOS.

Wiem też, że jest podobne pytanie, ale nie ma na nie odpowiedzi, pomyślałem, że spróbuję jeszcze raz i zobaczę, czy ktoś stworzył sposób, aby to zrobić, a jeśli tak, to czy mógłby się nim podzielić.

Joshua
źródło
2
Biblioteka NSAttributedString-Additions-for-HTML została zmieniona i wprowadzona do struktury przez tego samego autora. Teraz nazywa się DTCoreText i zawiera kilka klas układu Core Text. Możesz go znaleźć tutaj
Brian Douglas Moakley

Odpowiedzi:

290

W iOS 7 UIKit dodał initWithData:options:documentAttributes:error:metodę, która może zainicjować za NSAttributedStringpomocą HTML, np:

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

W 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)
szkatułka
źródło
28
Z jakiegoś powodu opcja NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType powoduje, że kodowanie trwa naprawdę, bardzo długo :(
Arie Litovsky
14
Szkoda, że ​​NSHTMLTextDocumentType jest (dosłownie) ~ 1000x wolniejsze niż ustawienie atrybutów z NSRange. (Profilowana krótka etykieta z jednym pogrubionym tagiem.)
Jason Moore,
6
Pamiętaj, że jeśli nie możesz NSHTMLTextDocumentType z tą metodą, jeśli chcesz użyć jej z wątku w tle. Nawet z ios 7 nie będzie używać TextKit do renderowania HTML. Zapoznaj się z biblioteką DTCoreText polecaną przez Ingve.
TJez
2
Niesamowite. Tylko myśl, prawdopodobnie mógłbyś wykonać [NSNumber numberWithInt: NSUTF8StringEncoding] jako @ (NSUTF8StringEncoding), nie?
Jarsen,
15
Robiłem to, ale uważaj na iOS 8. Jest boleśnie powolny, blisko sekundy dla kilkuset znaków. (W iOS 7 było to prawie natychmiastowe.)
Norman,
43

Istnieje dodatek open source w toku do NSAttributedString autorstwa Olivera Drobnika na Github. Używa NSScanner do analizy HTML.

Ingve
źródło
Wymaga minimalnego wdrożenia iOS 4.3 :( Mimo wszystko, bardzo imponujące.
Oh Danny Boy,
3
@Lirik Overkill dla ciebie może, ale jest idealny dla kogoś innego, tj. Twój komentarz nie jest w najmniejszym stopniu pomocny.
wuf810
3
Należy pamiętać, że ten projekt wymaga oprogramowania typu open source i jest objęty standardową 2-klauzulową licencją BSD. Oznacza to, że musisz wspomnieć Cocoanetics jako oryginalnego autora tego kodu i odtworzyć tekst LICENCJI w swojej aplikacji.
dulgan
28

Tworzenie NSAttributedString z HTML musi być wykonane w głównym wątku!

Aktualizacja: Okazuje się, że renderowanie HTML przez NSAttributedString zależy od WebKit pod maską i musi być uruchomione w głównym wątku lub czasami powoduje awarię aplikacji z SIGTRAPem .

Dziennik awarii nowego reliktu:

wprowadź opis obrazu tutaj

Poniżej znajduje się zaktualizowane, bezpieczne wątkowo rozszerzenie ciągów Swift 2:

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

Stosowanie:

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

Wynik:

wprowadź opis obrazu tutaj

Andrew Schreiber
źródło
Andrzej. To działa dobrze. Chciałem wiedzieć, jakie wszystkie krótkie zdarzenia muszę obsłużyć w moim UITextView, jeśli zdecyduję się na to podejście. Czy obsługuje wydarzenia z kalendarza, połączenia, e-maile, łącza do witryn internetowych itp. Dostępne w formacie HTML? Mam nadzieję, że UITextView jest w stanie obsłużyć zdarzenia w porównaniu do UILabel.
harshit2811
Powyższe podejście jest dobre tylko do formatowania. Poleciłbym użycie TTTAttributedLabel, jeśli potrzebujesz obsługi zdarzeń.
Andrew Schreiber,
Domyślnym kodowaniem używanym przez NSAttributedString jest NSUTF16StringEncoding (nie UTF8!). Dlatego to nie zadziała. Przynajmniej w moim przypadku!
Umit Kaya,
To powinno być przyjęte rozwiązanie. Robi ciąg rozmowy HTML na wątek tła będzie ostatecznie upaść, i dość często podczas uruchamiania testów.
ratsimihah
21

Szybkie rozszerzenie inicjatora na NSAttributedString

Chciałem dodać to NSAttributedStringraczej jako rozszerzenie niż String. Wypróbowałem to jako rozszerzenie statyczne i inicjator. Wolę inicjalizator, który zamieściłem poniżej.

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

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

Przykład

let html = "<b>Hello World!</b>"
let attributedString = NSAttributedString(html: html)
Mobile Dan
źródło
chcę, aby hello world był taki <p><b><i>hello</i> </b> <i>world</i> </p>
Uma Madhavi
Zapisz trochę LOC i zamień guard ... NSMutableAttributedString(data:...na try self.init(data:...(i dodaj throwsdo init)
nyg
i wreszcie to nie działa - wynik losowy tekst rozmiar czcionki
Vyachaslav Gerchicov
2
Dekodujesz dane za pomocą UTF-8, ale zakodowałeś je w UTF-16
Shyam Bhat
11

To jest Stringrozszerzenie napisane w Swift, aby zwrócić ciąg HTML jako 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
    }
}

Używać,

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

W powyższym celowo dodałem Unicode \ u2022, aby pokazać, że renderuje on kod Unicode poprawnie.

Proste: domyślne kodowanie NSAttributedStringto NSUTF16StringEncoding(nie UTF8!).

samwize
źródło
UTF16 uratował mi dzień, dzięki samwize!
Yueyu
UTF16 uratował mi dzień, dzięki samwize!
Yueyu
6

Dokonałem pewnych modyfikacji w rozwiązaniu Andrew i zaktualizowałem kod do Swift 3:

Ten kod używa teraz UITextView jako selfi może dziedziczyć oryginalną czcionkę, rozmiar czcionki i kolor tekstu

Uwaga: toHexString()to rozszerzenie stąd

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

Przykładowe użycie:

mainTextView.setAttributedStringFromHTML("<i>Hello world!</i>") { _ in }
He Yifei 何 一 非
źródło
5

Wersja 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
źródło
5

Szybki 4


  • Wygodny inicjator NSAttributedString
  • Bez dodatkowych osłon
  • zgłasza błąd

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

}

Stosowanie

UILabel.attributedText = try? NSAttributedString(htmlString: "<strong>Hello</strong> World!")
AamirR
źródło
Ratujesz mój dzień. Dziękuję Ci.
pkc456
@ pkc456 meta.stackexchange.com/questions/5234/… , zagłosuj za :) dzięki!
AamirR
Jak ustawić rozmiar i rodzinę czcionek?
kirqe
To znacznie lepsze niż sugerowane przez Mobile Dan, ponieważ nie obejmuje zbędnej kopii z self.init (atrybuty: ciąg: atrybutyString)
cyjanek
4

Jedynym rozwiązaniem, jakie masz teraz, jest przeanalizowanie kodu HTML, zbudowanie kilku węzłów z podanymi atrybutami point / font / etc, a następnie połączenie ich razem w NSAttributedString. To dużo pracy, ale jeśli zostanie wykonane poprawnie, może być ponownie wykorzystane w przyszłości.

jer
źródło
1
Jeśli HTML jest XHTML-Strict, możesz użyć NSXMLDOcument i przyjaciół, aby pomóc w parsowaniu.
Dylan Lukes
Jak sugerowałbyś, abym zajął się budowaniem węzłów o podanych atrybutach?
Joshua,
2
To szczegół implementacji. Jakkolwiek analizujesz kod HTML, masz dostęp do każdego atrybutu dla każdego tagu, który określa takie rzeczy jak nazwa czcionki, rozmiar itp. Możesz użyć tych informacji do przechowywania odpowiednich szczegółów, które musisz dodać do przypisanego tekstu jako atrybuty . Ogólnie rzecz biorąc, musisz najpierw zapoznać się z analizowaniem, zanim podejmiesz takie zadanie.
jer
2

Powyższe rozwiązanie jest poprawne.

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

Ale aplikacja ulegnie awarii, jeśli używasz jej na iOS 8.1, 2 lub 3.

Aby uniknąć awarii, możesz zrobić: uruchom to w kolejce. Aby zawsze był w głównym wątku.

Nitesh Kumar Singh
źródło
@alecex Spotkałem ten sam problem! app ulegnie awarii na iOS 8.1, 2, 3. Ale będzie dobrze na iOS 8.4 lub nowszym. Czy możesz szczegółowo wyjaśnić, jak tego uniknąć? czy jest jakieś obejście lub zamiast tego można zastosować metody?
Silne
Stworzyłem szybką kategorię, aby sobie z tym poradzić, kopiując metody z AppKit, który ma bardzo łatwy i intuicyjny sposób na zrobienie tego. Dlaczego Apple tego nie dodał, jest poza mną: github.com/cguess/NSMutableAttributedString-HTML
CGuess
2

Używanie NSHTMLTextDocumentType jest powolne i trudno jest kontrolować style. Proponuję wypróbować moją bibliotekę, która nazywa się Atributika. Posiada własny, bardzo szybki parser HTML. Możesz także mieć dowolne nazwy tagów i zdefiniować dla nich dowolny styl.

Przykład:

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

label.attributedText = str

Możesz go znaleźć tutaj https://github.com/psharanda/Atributika

Pavel Sharanda
źródło
2

Swift 3 :
Spróbuj tego :

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

I do użycia:

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
źródło
0

Pomocne rozszerzenia

Zainspirowany tym wątku, podcastów i objc przykład Erica Sadun w iOS Gourmet Cookbook str.80, pisałem na przedłużenie Stringi NSAttributedStringprzejść tam iz powrotem pomiędzy HTML zwykły łańcuchów i NSAttributedStrings i odwrotnie - na GitHub tutaj , które Znalazłem pomocne.

Te podpisy są (znowu pełny kod w GIST linku powyżej):

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
źródło
0

z czcionką

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

alternatywnie możesz użyć wersji, z których pochodzi, i ustawić czcionkę na UILabel po ustawieniu atrybututributedString

Anton Tropashko
źródło
0

Wbudowana konwersja zawsze ustawia kolor tekstu na UIColor.black, nawet jeśli przekazujesz słownik atrybutów z ustawieniem .forgroundColor na coś innego. Aby obsługiwać tryb DARK w systemie iOS 13, wypróbuj tę wersję rozszerzenia na 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
źródło