Chuyển đổi HTML sang NSAttributionString trong iOS


151

Tôi đang sử dụng một ví dụ UIWebViewđể xử lý một số văn bản và tô màu chính xác, nó cho kết quả dưới dạng HTML nhưng thay vì hiển thị nó trong phần UIWebViewtôi muốn hiển thị nó bằng Core Texta NSAttributedString.

Tôi có thể tạo và vẽ NSAttributedStringnhưng tôi không chắc làm thế nào tôi có thể chuyển đổi và ánh xạ HTML thành chuỗi được gán.

Tôi hiểu rằng trong Mac OS X NSAttributedStringcó một initWithHTML:phương pháp nhưng đây chỉ là một bổ sung cho Mac và không có sẵn cho iOS.

Tôi cũng biết rằng có một câu hỏi tương tự với câu hỏi này nhưng nó không có câu trả lời, mặc dù tôi sẽ thử lại và xem liệu có ai đã tạo ra một cách để làm điều này không và nếu có, nếu họ có thể chia sẻ nó.


2
Thư viện NSAttributionString-Additions-for-HTML đã được đổi tên và đưa vào khung bởi cùng một tác giả. Bây giờ nó được gọi là DTCoreText và bao gồm một loạt các lớp bố cục Core Text. Bạn có thể tìm thấy nó ở đây
Brian Douglas Moakley

Câu trả lời:


290

Trong iOS 7, UIKit đã thêm một initWithData:options:documentAttributes:error:phương thức có thể khởi tạo NSAttributedStringHTML bằng cách sử dụng, ví dụ:

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

Trong 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
Vì một số lý do, tùy chọn NSDocumentTypeDocumentAttribution: NSHTMLTextDocumentType đang khiến quá trình mã hóa mất một thời gian thực sự rất dài :(
Arie Litovsky

14
Quá tệ NSHTMLTextDocumentType là (chậm) ~ 1000 lần so với cài đặt các thuộc tính với NSRange. (Cấu hình một nhãn ngắn với một thẻ in đậm.)
Jason Moore

6
Xin lưu ý rằng nếu bạn không thể NSHTMLTextDocumentType với phương thức này nếu bạn muốn sử dụng nó từ một luồng nền. Ngay cả với ios 7, nó sẽ không sử dụng TextKit để kết xuất HTML. Hãy xem thư viện DTCoreText được đề xuất bởi Ingve.
TJez

2
Tuyệt vời. Chỉ cần một suy nghĩ, bạn có thể có thể làm [NSNumber numberWithInt: NSUTF8StringEncoding] là @ (NSUTF8StringEncoding), không?
Jarsen

15
Tôi đã làm điều này, nhưng hãy cẩn thận trên iOS 8. Nó rất chậm, gần một giây cho vài trăm ký tự. (Trong iOS 7, nó gần như ngay lập tức.)
Norman

43

Có một bổ sung nguồn mở đang thực hiện cho NSAttributionString của Oliver Drobnik tại Github. Nó sử dụng NSScanner để phân tích cú pháp HTML.


Yêu cầu triển khai tối thiểu iOS 4.3 :( Không hơn không kém, rất ấn tượng
Oh Danny Boy

3
@Lirik Overkill đối với bạn có thể nhưng hoàn hảo cho người khác, nghĩa là bình luận của bạn không hữu ích chút nào.
wuf810

3
Xin lưu ý rằng dự án này yêu cầu là nguồn mở và được bao phủ bởi giấy phép BSD 2 điều khoản tiêu chuẩn. Điều đó có nghĩa là bạn phải đề cập đến Cocoanetic là tác giả ban đầu của mã này và sao chép văn bản LICENSE bên trong ứng dụng của bạn.
dulgan

28

Tạo NSAttributionString từ HTML phải được thực hiện trên luồng chính!

Cập nhật: Hóa ra kết xuất HTML NSAttributionString phụ thuộc vào WebKit dưới mui xe và phải được chạy trên luồng chính hoặc đôi khi nó sẽ làm hỏng ứng dụng với SIGTRAP .

Nhật ký sự cố di tích mới:

nhập mô tả hình ảnh ở đây

Dưới đây là tiện ích mở rộng Chuỗi 2 Swift an toàn được cập nhật :

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

Sử dụng:

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

Đầu ra:

nhập mô tả hình ảnh ở đây


Andrew. Điều này đang làm việc tốt. Tôi muốn biết tất cả những sự kiện ngắn mà tôi phải xử lý trong UITextView của mình nếu tôi sẽ thực hiện theo phương pháp này. Nó có thể xử lý sự kiện Lịch, Cuộc gọi, Email, Liên kết trang web, v.v. có sẵn trong HTML không? Tôi hy vọng UITextView có thể xử lý các sự kiện so với UILabel.
khắc nghiệt2811

Cách tiếp cận trên chỉ tốt cho định dạng. Tôi sẽ khuyên bạn nên sử dụng TTTAttributionLabel nếu bạn cần xử lý sự kiện.
Andrew Schreiber

Mã hóa mặc định mà NSAttributionString sử dụng là NSUTF16StringEncoding (không phải UTF8!). Đó là lý do tại sao điều này sẽ không hoạt động. Ít nhất là trong trường hợp của tôi!
Umit Kaya

Đây phải là giải pháp được chấp nhận. Thực hiện một cuộc hội thoại chuỗi HTML trên một luồng nền cuối cùng sẽ gặp sự cố và khá thường xuyên trong khi chạy thử nghiệm.
ratsimihah

21

Phần mở rộng trình khởi chạy Swift trên NSAttributionString

Xu hướng của tôi là thêm phần này như một phần mở rộng NSAttributedStringhơn là String. Tôi đã thử nó như một phần mở rộng tĩnh và một trình khởi tạo. Tôi thích trình khởi tạo, đó là những gì tôi đã đưa vào dưới đây.

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

Thí dụ

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

tôi muốn hello world là như thế này <p> <b> <i> hello </ i> </ b> <i> thế giới </ i> </ p>
Uma Madhavi

Lưu một số LỘC và thay thế guard ... NSMutableAttributedString(data:...bằng try self.init(data:...(và thêm throwsvào init)
nyg

và cuối cùng nó không hoạt động - văn bản tăng kích thước phông chữ ngẫu nhiên
Vyachaslav Gerchicov

2
Bạn đang giải mã dữ liệu bằng UTF-8 nhưng bạn đã mã hóa dữ liệu bằng UTF-16
Shyam Bhat

11

Đây là một Stringtiện ích mở rộng được viết bằng Swift để trả về chuỗi HTML dưới dạng 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
    }
}

Để sử dụng,

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

Trong phần trên, tôi đã cố tình thêm một unicode \ u2022 để cho thấy rằng nó hiển thị unicode chính xác.

Một tầm thường: Mã hóa mặc định NSAttributedStringsử dụng là NSUTF16StringEncoding(không phải UTF8!).


UTF16 đã cứu ngày của tôi, Cảm ơn samwize!
Yueyu

UTF16 đã cứu ngày của tôi, Cảm ơn samwize!
Yueyu

6

Thực hiện một số sửa đổi về giải pháp của Andrew và cập nhật mã lên Swift 3:

Mã này hiện sử dụng UITextView như self và có thể kế thừa phông chữ gốc, kích thước phông chữ và màu văn bản của nó

Lưu ý: toHexString()là phần mở rộng từ đây

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

Ví dụ sử dụng:

mainTextView.setAttributedStringFromHTML("<i>Hello world!</i>") { _ in }

5

Phiên bản 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

Swift 4


  • Trình khởi tạo tiện lợi NSAttributionString
  • Không có thêm vệ sĩ
  • ném lỗi

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

}

Sử dụng

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

Bạn tiết kiệm ngày của tôi. Cảm ơn bạn.
pkc456

@ pkc456 meta.stackexchange.com/questions/5234/ Khăn , làm upvote :) cảm ơn!
AamirR

Làm cách nào tôi có thể đặt kích thước phông chữ và họ phông chữ?
kirqe

Điều đó tốt hơn nhiều so với đề xuất của Mobile Dan, vì nó không liên quan đến một bản sao dự phòng với self.init (AttributionString: attributionString)
cyanide

4

Giải pháp duy nhất bạn có ngay bây giờ là phân tích HTML, xây dựng một số nút với các thuộc tính point / font / etc đã cho, sau đó kết hợp chúng lại với nhau thành NSAttributionString. Đó là rất nhiều công việc, nhưng nếu được thực hiện chính xác, có thể được tái sử dụng trong tương lai.


1
Nếu HTML là XHTML-Strict, bạn có thể sử dụng NSXMLDOcument và bạn bè để trợ giúp phân tích cú pháp.
Dylan Lukes

Làm thế nào bạn có thể đề nghị tôi đi xây dựng các nút với các thuộc tính nhất định?
Joshua

2
Đó là một chi tiết thực hiện. Tuy nhiên, bạn phân tích HTML, bạn có quyền truy cập vào từng thuộc tính cho từng thẻ, trong đó chỉ định những thứ như tên phông chữ, kích thước, v.v. Bạn có thể sử dụng thông tin này để lưu trữ các chi tiết có liên quan bạn cần thêm vào văn bản được gán làm thuộc tính . Nói chung, bạn cần làm quen với việc phân tích cú pháp trước khi giải quyết một nhiệm vụ như vậy.
giật

2

Giải pháp trên là chính xác.

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

Nhưng ứng dụng wioll gặp sự cố nếu bạn đang chạy nó trên ios 8.1,2 hoặc 3.

Để tránh sự cố, điều bạn có thể làm là: chạy cái này trong hàng đợi. Vì vậy, nó luôn luôn là chủ đề chính.


@alecex Mình đã gặp vấn đề tương tự! ứng dụng sẽ sập trên iOS 8.1, 2, 3. Nhưng sẽ ổn trên iOS 8.4 trở lên. Bạn có thể giải thích chi tiết làm thế nào để tránh nó? hoặc có bất kỳ công việc xung quanh, hoặc phương pháp có thể được sử dụng thay thế?
Mạnh

Tôi đã tạo một danh mục nhanh để xử lý việc này, sao chép các phương thức từ AppKit, cách này rất dễ dàng và trực quan để thực hiện việc này. Tại sao Apple không thêm nó nằm ngoài tôi: github.com/cguess/NSMutableAttributionString-HTML
CGuess 2/2/2016

2

Việc sử dụng NSHTMLTextDocumentType rất chậm và khó kiểm soát các kiểu. Tôi đề nghị bạn thử thư viện của tôi được gọi là Atributika. Nó có trình phân tích cú pháp HTML rất nhanh. Ngoài ra, bạn có thể có bất kỳ tên thẻ và xác định bất kỳ phong cách cho chúng.

Thí dụ:

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

label.attributedText = str

Bạn có thể tìm thấy nó ở đây https://github.com/psharanda/Atributika


2

Swift 3 :
Hãy thử điều này :

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

Và để sử dụng:

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

Tiện ích mở rộng hữu ích

Lấy cảm hứng từ chủ đề này, một pod, và ví dụ ObjC Erica Sadun trong iOS Gourmet Cookbook p.80, tôi đã viết một phần mở rộng trên Stringvà trên NSAttributedStringđể đi lại giữa đồng bằng HTML-strings và NSAttributedStrings và ngược lại - trên GitHub đây , mà Tôi đã tìm thấy hữu ích.

Các chữ ký là (một lần nữa, mã đầy đủ trong Gist, liên kết ở trên):

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

với phông chữ

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

Ngoài ra, bạn có thể sử dụng các phiên bản được lấy từ và đặt phông chữ trên UILabel sau khi đặt AttributionString


0

Chuyển đổi tích hợp luôn đặt màu văn bản thành UIColor.black, ngay cả khi bạn chuyển từ điển thuộc tính với .forgroundColor được đặt thành thứ khác. Để hỗ trợ chế độ DARK trên iOS 13, hãy thử phiên bản tiện ích mở rộng này trên NSAttributionString.

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)
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.