Ubah HTML menjadi NSAttributedString di iOS

151

Saya menggunakan contoh UIWebViewuntuk memproses beberapa teks dan warna dengan benar, itu memberikan hasilnya sebagai HTML tetapi bukan menampilkannya di UIWebViewSaya ingin menampilkannya menggunakan Core Textdengan NSAttributedString.

Saya dapat membuat dan menggambar NSAttributedStringtetapi saya tidak yakin bagaimana saya dapat mengkonversi dan memetakan HTML ke string yang dikaitkan.

Saya mengerti bahwa di bawah Mac OS X NSAttributedStringmemiliki initWithHTML:metode tetapi ini hanya tambahan Mac dan tidak tersedia untuk iOS.

Saya juga tahu bahwa ada pertanyaan serupa dengan ini tetapi tidak punya jawaban, saya pikir saya akan mencoba lagi dan melihat apakah ada yang telah menciptakan cara untuk melakukan ini dan jika demikian, jika mereka dapat membagikannya.

Joshua
sumber
2
Pustaka NSAttributedString-Additions-for-HTML telah diubah namanya dan digulirkan ke dalam kerangka kerja oleh penulis yang sama. Sekarang disebut DTCoreText dan mencakup banyak kelas tata letak Teks Inti. Anda dapat menemukannya di sini
Brian Douglas Moakley

Jawaban:

290

Di iOS 7, UIKit menambahkan initWithData:options:documentAttributes:error:metode yang dapat menginisialisasi NSAttributedStringmenggunakan HTML, misalnya:

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

Dalam 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
sumber
28
Untuk beberapa alasan, opsi NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType menyebabkan pengodean memakan waktu yang sangat, sangat lama :(
Arie Litovsky
14
NSHTMLTextDocumentType yang sangat buruk (secara harfiah) ~ 1000x lebih lambat daripada mengatur atribut dengan NSRange. (Diprofilkan label pendek dengan satu tag tebal.)
Jason Moore
6
Perlu diketahui bahwa jika Anda tidak dapat NSHTMLTextDocumentType dengan metode ini jika Anda ingin menggunakannya dari utas latar belakang. Bahkan dengan ios 7, itu tidak akan menggunakan TextKit untuk rendering HTML. Lihatlah perpustakaan DTCoreText yang direkomendasikan oleh Ingve.
TJez
2
Luar biasa. Hanya sebuah pemikiran, Anda mungkin bisa melakukan [NSNomor numberWithInt: NSUTF8StringEncoding] sebagai @ (NSUTF8StringEncoding), bukan?
Jarsen
15
Saya melakukan ini, tetapi berhati-hatilah pada iOS 8. Sangat lambat, hampir satu detik untuk beberapa ratus karakter. (Di iOS 7 itu hampir instan.)
Norman
43

Ada tambahan open source yang sedang dikerjakan untuk NSAttributedString oleh Oliver Drobnik di Github. Ini menggunakan NSScanner untuk penguraian HTML.

Ingve
sumber
Membutuhkan minimal penerapan iOS 4.3 :( Tidak ada yang kurang, sangat mengesankan.
Oh Danny Boy
3
@Lirik Overkill untuk Anda mungkin tetapi sempurna untuk orang lain yaitu komentar Anda tidak sedikit membantu.
wuf810
3
Harap dicatat bahwa proyek ini membutuhkan open source dan dicakup oleh lisensi BSD 2 klausa standar. Itu berarti Anda harus menyebutkan Cocoanetics sebagai penulis asli kode ini dan mereproduksi teks LISENSI di dalam aplikasi Anda.
dulgan
28

Membuat NSAttributedString dari HTML harus dilakukan di utas utama!

Pembaruan: Ternyata rendering NSAttributedString HTML bergantung pada WebKit di bawah tenda, dan harus dijalankan pada utas utama atau terkadang aplikasi itu akan macet dengan SIGTRAP .

Log kecelakaan baru:

masukkan deskripsi gambar di sini

Di bawah ini adalah ekstensi Swift 2 String yang aman untuk keamanan :

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

Pemakaian:

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

Keluaran:

masukkan deskripsi gambar di sini

Andrew Schreiber
sumber
Andrew. Ini bekerja dengan baik. Saya ingin tahu semua kejadian singkat yang harus saya tangani di UITextView saya jika saya akan menggunakan pendekatan ini. Bisakah ia menangani acara Kalender, Panggilan, Email, tautan Situs web, dll. Tersedia dalam HTML? Saya harap UITextView dapat menangani acara dibandingkan dengan UILabel.
harshit2811
Pendekatan di atas hanya baik untuk pemformatan. Saya akan merekomendasikan menggunakan TTTAttributedLabel jika Anda perlu penanganan acara.
Andrew Schreiber
Pengkodean default yang digunakan NSAttributedString adalah NSUTF16StringEncoding (bukan UTF8!). Itu sebabnya ini tidak akan berhasil. Setidaknya dalam kasus saya!
Umit Kaya
Ini harus menjadi solusi yang diterima. Melakukan percakapan string HTML pada utas latar belakang pada akhirnya akan macet, dan cukup sering saat menjalankan tes.
ratsimihah
21

Ekstensi penginisialisasi cepat di NSAttributedString

Kecenderungan saya adalah menambahkan ini sebagai ekstensi NSAttributedStringdaripada String. Saya mencobanya sebagai ekstensi statis dan penginisialisasi. Saya lebih suka initializer yang saya sertakan di bawah.

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

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

Contoh

let html = "<b>Hello World!</b>"
let attributedString = NSAttributedString(html: html)
Mobile Dan
sumber
saya ingin dunia halo menjadi seperti ini <p> <b> <i> halo </ i> </ b> <i> dunia </ i> </ p>
Uma Madhavi
Simpan beberapa LOC dan ganti guard ... NSMutableAttributedString(data:...dengan try self.init(data:...(dan tambahkan throwske init)
nyg
dan akhirnya tidak berfungsi - teks mendapatkan ukuran font acak
Vyachaslav Gerchicov
2
Anda mendekode data dengan UTF-8 tetapi Anda menyandikannya dengan UTF-16
Shyam Bhat
11

Ini adalah Stringekstensi yang ditulis dalam Swift untuk mengembalikan string HTML sebagai 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
    }
}

Menggunakan,

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

Di atas, saya sengaja menambahkan unicode \ u2022 untuk menunjukkan bahwa ia membuat unicode dengan benar.

Sepele: Pengkodean default yang NSAttributedStringdigunakan adalah NSUTF16StringEncoding(bukan UTF8!).

samwize
sumber
UTF16 menyelamatkan hari saya, Terima kasih samwize!
Yueyu
UTF16 menyelamatkan hari saya, Terima kasih samwize!
Yueyu
6

Membuat beberapa modifikasi solusi Andrew dan perbarui kode ke Swift 3:

Kode ini sekarang menggunakan UITextView sebagai self dan dapat mewarisi font aslinya, ukuran font dan warna teks

Catatan: toHexString()adalah ekstensi dari sini

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

Contoh penggunaan:

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

Versi 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
sumber
5

Cepat 4


  • Penginisialisasi kenyamanan NSAttributedString
  • Tanpa penjaga tambahan
  • melempar kesalahan

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

}

Pemakaian

UILabel.attributedText = try? NSAttributedString(htmlString: "<strong>Hello</strong> World!")
AamirR
sumber
Anda menghemat hari saya. Terima kasih.
pkc456
@ pkc456 meta.stackexchange.com/questions/5234/… , lakukan upvote :) terima kasih!
AamirR
Bagaimana saya bisa mengatur ukuran font dan keluarga font?
kirqe
Itu jauh lebih baik daripada yang disarankan oleh Mobile Dan, karena itu tidak melibatkan salinan yang berlebihan dengan self.init (attributString: attributedString)
sianida
4

Satu-satunya solusi yang Anda miliki saat ini adalah mem-parsing HTML, membangun beberapa node dengan atribut point / font / etc yang diberikan, kemudian menggabungkannya menjadi NSAttributedString. Ini banyak pekerjaan, tetapi jika dilakukan dengan benar, dapat digunakan kembali di masa depan.

jer
sumber
1
Jika HTMLnya XHTML-Strict, Anda bisa menggunakan NSXMLDOcument dan teman-teman untuk membantu penguraian.
Dylan Lukes
Bagaimana Anda menyarankan saya untuk membangun node dengan atribut yang diberikan?
Yosua
2
Itu detail implementasi. Bagaimanapun Anda mem-parsing HTML, Anda memiliki akses ke setiap atribut untuk setiap tag, yang menentukan hal-hal seperti nama font, ukuran, dll. Anda dapat menggunakan informasi ini untuk menyimpan detail yang relevan yang perlu Anda tambahkan ke teks yang dikaitkan sebagai atribut . Secara umum, Anda harus terbiasa dengan parsing terlebih dahulu sebelum menangani tugas seperti itu.
jer
2

Solusi di atas benar.

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

Tetapi aplikasi wioll crash jika Anda menjalankannya di ios 8.1,2 atau 3.

Untuk menghindari kerusakan yang dapat Anda lakukan adalah: jalankan ini dalam antrian. Sehingga selalu di utas utama.

Nitesh Kumar Singh
sumber
@ alecex saya memang menemui masalah yang sama! aplikasi akan macet di iOS 8.1, 2, 3. Tetapi akan baik-baik saja di iOS 8.4 atau lebih baru. Bisakah Anda menjelaskan secara terperinci bagaimana cara menghindarinya? atau apakah ada cara lain, atau metode dapat digunakan sebagai gantinya?
Kuat
Saya membuat kategori cepat untuk menangani ini, menyalin metode dari AppKit, yang memiliki cara yang sangat mudah dan intuitif untuk melakukan ini. Mengapa Apple tidak menambahkannya di luar saya. Github.com/cguess/NSMutableAttributedString-HTML
CGuess
2

Penggunaan NSHTMLTextDocumentType lambat dan sulit untuk mengontrol gaya. Saya sarankan Anda untuk mencoba perpustakaan saya yang disebut Atributika. Ini memiliki parser HTML yang sangat cepat. Anda juga dapat memiliki nama tag dan menentukan gaya apa pun untuknya.

Contoh:

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

label.attributedText = str

Anda dapat menemukannya di sini https://github.com/psharanda/Atributika

Pavel Sharanda
sumber
2

Swift 3 :
Coba ini :

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

Dan untuk menggunakan:

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
sumber
0

Ekstensi yang Bermanfaat

Terinspirasi oleh thread ini, pod, dan contoh ObjC Erica Sadun di iOS Gourmet Cookbook p.80, saya menulis perpanjangan Stringdan NSAttributedStringuntuk pergi bolak-balik antara HTML biasa-string dan NSAttributedStrings dan sebaliknya - di GitHub sini , yang Saya menemukan bermanfaat.

The tanda tangan yang (sekali lagi, kode penuh dalam Intisari sebuah, link di atas):

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
sumber
0

dengan font

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

sebagai alternatif Anda dapat menggunakan versi-versi ini dari mana dan mengatur font pada UILabel setelah pengaturan attributedString

Anton Tropashko
sumber
0

Konversi bawaan selalu menetapkan warna teks ke UIColor.black, bahkan jika Anda meneruskan kamus atribut dengan .forgroundColor diatur ke yang lain. Untuk mendukung mode DARK di iOS 13, coba versi ekstensi ini di 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
sumber