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ć.


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)

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.


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


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)

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


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 }

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
}

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

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.


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.


@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


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

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 }

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


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)
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.