แปลง HTML เป็น NSAttributedString ใน iOS


151

ฉันใช้อินสแตนซ์ของUIWebViewการประมวลผลข้อความและสีอย่างถูกต้องมันให้ผลลัพธ์เป็น HTML แต่แทนที่จะแสดงในUIWebViewฉันต้องการแสดงโดยใช้Core TextNSAttributedStringกับ

ฉันสามารถสร้างและวาด NSAttributedStringแต่ฉันไม่แน่ใจว่าฉันสามารถแปลงและแมป HTML ลงในสตริงที่ประกอบ

ฉันเข้าใจว่าภายใต้ Mac OS X NSAttributedStringมีinitWithHTML:วิธีการ แต่นี่เป็น Mac เท่านั้นที่เพิ่มเติมและไม่สามารถใช้ได้กับ iOS

ฉันก็รู้ว่ามีคำถามที่คล้ายกันนี้ แต่ไม่มีคำตอบฉันจะลองอีกครั้งและดูว่ามีใครสร้างวิธีการทำเช่นนี้หรือไม่ถ้าพวกเขาสามารถแบ่งปันได้


2
ไลบรารี NSAttributedString-Additions-for-HTML ถูกเปลี่ยนชื่อและรีดเป็นกรอบโดยผู้เขียนคนเดียวกัน ตอนนี้เรียกว่า DTCoreText และรวมคลาสโครงร่างข้อความหลัก คุณสามารถค้นหาได้ที่นี่
Brian Douglas Moakley

คำตอบ:


290

ใน iOS 7 UIKit เพิ่มinitWithData:options:documentAttributes:error:วิธีการที่สามารถเริ่มต้นการNSAttributedStringใช้ HTML เช่น:

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

ในสวิฟต์:

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
ด้วยเหตุผลบางอย่างตัวเลือก NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType ทำให้การเข้ารหัสใช้เวลานานมากจริงๆ :(
Arie Litovsky

14
น่าเสียดายที่ NSHTMLTextDocumentType นั้นช้ากว่าการตั้งค่าแอตทริบิวต์ด้วย NSRange ประมาณ 1,000 เท่า (ทำป้ายกำกับสั้น ๆ พร้อมกับแท็กตัวหนาหนึ่งตัว)
Jason Moore

6
โปรดระวังว่าหากคุณไม่สามารถ NSHTMLTextDocumentType ด้วยวิธีนี้หากคุณต้องการใช้จากเธรดพื้นหลัง แม้กับ ios 7 มันจะไม่ใช้ TextKit สำหรับการเรนเดอร์ HTML ลองดูที่ห้องสมุด DTCoreText ที่ Ingve แนะนำ
TJez

2
น่ากลัว แค่คิดคุณอาจทำ [หมายเลข NSNumber: NSUTF8StringEncoding] เป็น @ (NSUTF8StringEncoding) ไม่ได้?
Jarsen

15
ฉันทำสิ่งนี้ แต่ต้องระวังใน iOS 8 มันช้าลงอย่างเจ็บปวดใกล้กับวินาทีสำหรับตัวละครสองสามร้อยตัว (ใน iOS 7 มันเกือบจะทันที)
นอร์แมน

43

มีโอเพ่นซอร์สที่กำลังดำเนินการอยู่นอกจากนี้ NSAttributedStringโดย Oliver Drobnik ที่ Github มันใช้ NSScanner สำหรับการแยกวิเคราะห์ HTML


ต้องมีการปรับใช้ iOS 4.3 :( อย่างน้อยที่สุด, น่าประทับใจมาก
โอ้ Danny Boy

3
@Lirik Overkill สำหรับคุณ แต่อาจจะสมบูรณ์แบบสำหรับคนอื่นเช่นความคิดเห็นของคุณไม่ได้มีประโยชน์น้อยที่สุด
wuf810

3
โปรดทราบว่าโครงการนี้ต้องใช้เป็นโอเพ่นซอร์สและครอบคลุมด้วยใบอนุญาต BSD มาตรฐาน 2 ข้อ นั่นหมายความว่าคุณต้องพูดถึง Cocoanetics ในฐานะผู้เขียนต้นฉบับของรหัสนี้และสร้างข้อความลิขสิทธิ์ในแอปของคุณ
dulgan

28

การสร้าง NSAttributedString จาก HTML จะต้องทำบนเธรดหลัก!

ปรับปรุง: ปรากฎว่าการแสดงผล NSAttributedString HTML ขึ้นอยู่กับ WebKit ภายใต้ประทุนและต้องทำงานบนหัวข้อหลัก หรือบางครั้งจะผิดพลาด app กับ SIGTRAP

บันทึกข้อผิดพลาดที่ระลึกใหม่:

ป้อนคำอธิบายรูปภาพที่นี่

ด้านล่างนี้เป็นส่วนขยายของ Swift 2 String ที่ปลอดภัยต่อเธรด

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

การใช้งาน:

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

เอาท์พุท:

ป้อนคำอธิบายรูปภาพที่นี่


แอนดรู มันใช้งานได้ดี ฉันอยากรู้ว่าเหตุการณ์ทั้งหมดที่ฉันต้องจัดการใน UITextView ของฉันคืออะไรถ้าฉันจะใช้วิธีนี้ สามารถจัดการกิจกรรมในปฏิทินโทรอีเมลลิงค์เว็บไซต์และอื่น ๆ เป็น HTML ได้หรือไม่ ฉันหวังว่า UITextView สามารถจัดการเหตุการณ์เปรียบเทียบกับ UILabel
harshit2811

วิธีการดังกล่าวเป็นสิ่งที่ดีสำหรับการจัดรูปแบบเท่านั้น ฉันอยากจะแนะนำให้ใช้TTTAttributedLabelหากคุณต้องการจัดการเหตุการณ์
Andrew Schreiber

การเข้ารหัสเริ่มต้นที่ NSAttributedString ใช้คือ NSUTF16StringEncoding (ไม่ใช่ UTF8!) นั่นเป็นเหตุผลที่สิ่งนี้จะไม่ทำงาน อย่างน้อยก็ในกรณีของฉัน!
Umit Kaya

นี่ควรเป็นทางออกที่ยอมรับได้ การสนทนาสตริง HTML บนเธรดพื้นหลังจะล้มเหลวในที่สุดและค่อนข้างบ่อยขณะเรียกใช้การทดสอบ
ratsimihah

21

ส่วนขยายเริ่มต้น Swift บน NSAttributedString

ความโน้มเอียงของฉันคือการเพิ่มนี้เป็นส่วนขยายไปมากกว่าNSAttributedString Stringฉันพยายามเป็นส่วนขยายแบบคงที่และเป็นเครื่องมือเริ่มต้น ฉันชอบตัวเริ่มต้นซึ่งเป็นสิ่งที่ฉันได้รวมไว้ด้านล่าง

สวิฟต์ 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)
}

สวิฟท์ 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)
}
}

ตัวอย่าง

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

ฉันอยากให้ Hello world เป็นแบบนี้ <p><min<i>hello</i> </b> <i>world</i> </p>
Uma Madhavi

บันทึก LOC และแทนที่guard ... NSMutableAttributedString(data:...ด้วยtry self.init(data:...(และเพิ่มthrowsไปยัง init)
nyg

และในที่สุดมันก็ใช้งานไม่ได้ - ข้อความจะเพิ่มขนาดตัวอักษรแบบสุ่ม
Vyachaslav Gerchicov

2
คุณกำลังถอดรหัสข้อมูลด้วย UTF-8 แต่คุณเข้ารหัสด้วย UTF-16
Shyam Bhat

11

นี้เป็นStringส่วนขยายที่เขียนในสวิฟท์ที่จะกลับสตริง HTML 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
    }
}

ใช้,

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

ในข้างต้นฉันได้เพิ่มยูนิโคด \ u2022 เพื่อแสดงว่ายูนิโคดทำให้ถูกต้อง

เรื่องเล็กน้อย: การเข้ารหัสเริ่มต้นที่NSAttributedStringใช้คือNSUTF16StringEncoding(ไม่ใช่ UTF8!)


UTF16 บันทึกวันของฉันขอบคุณ samwize!
Yueyu

UTF16 บันทึกวันของฉันขอบคุณ samwize!
Yueyu

6

ทำการปรับเปลี่ยนบางอย่างในแอนดรูโซลูชันของและอัปเดตรหัสเป็น Swift 3:

รหัสนี้ใช้ UITextView เป็นselfและสามารถสืบทอดแบบอักษรดั้งเดิมขนาดแบบอักษรและสีข้อความ

หมายเหตุ: toHexString()เป็นส่วนขยายจากที่นี่

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

ตัวอย่างการใช้งาน:

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

5

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

สวิฟต์ 4


  • NSAttributedString initializer ความสะดวกสบาย
  • ไม่มียามพิเศษ
  • พ่นผิดพลาด

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

}

การใช้

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

คุณบันทึกวันของฉัน ขอบคุณ.
pkc456

@ pkc456 meta.stackexchange.com/questions/5234/… , ทำ upvote :) ขอบคุณ!
AamirR

ฉันจะตั้งค่าขนาดฟอนต์และตระกูลฟอนต์ได้อย่างไร
kirqe

นั่นดีกว่าที่แนะนำโดย Mobile Dan เนื่องจากไม่เกี่ยวข้องกับสำเนาซ้ำซ้อนที่มีตัวเอง (มาประกอบกับ String: นำมารวมกัน)
ไซยาไนด์

4

โซลูชันเดียวที่คุณมีในขณะนี้คือการแยกวิเคราะห์ HTML สร้างบางโหนดด้วยแอตทริบิวต์ point / font / etc ที่กำหนดจากนั้นรวมเข้าด้วยกันเป็น NSAttributedString มันทำงานมาก แต่ถ้าทำอย่างถูกต้องสามารถนำมาใช้ซ้ำได้ในอนาคต


1
หาก HTML เป็น XHTML-Strict คุณสามารถใช้ NSXMLDOcument และเพื่อน ๆ เพื่อช่วยในการแยกวิเคราะห์
Dylan Lukes

คุณจะแนะนำฉันให้สร้างโหนดด้วยคุณสมบัติที่กำหนดได้อย่างไร
Joshua

2
นั่นคือรายละเอียดการใช้งาน อย่างไรก็ตามคุณแยกวิเคราะห์ HTML คุณสามารถเข้าถึงแต่ละแอตทริบิวต์สำหรับแต่ละแท็กซึ่งระบุสิ่งต่าง ๆ เช่นชื่อแบบอักษรขนาด ฯลฯ คุณสามารถใช้ข้อมูลนี้เพื่อเก็บรายละเอียดที่เกี่ยวข้องที่คุณต้องการเพิ่มลงในข้อความประกอบเป็นแอตทริบิวต์ . โดยทั่วไปคุณต้องทำความคุ้นเคยกับการวิเคราะห์คำก่อนก่อนที่จะแก้ปัญหาดังกล่าว
jer

2

การแก้ปัญหาข้างต้นถูกต้อง

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

แต่แอปพลิเคชั่น Wii จะพังหากคุณใช้งานบน iOS 8.1,2 หรือ 3

เพื่อหลีกเลี่ยงความผิดพลาดที่คุณสามารถทำได้คือให้รันสิ่งนี้ในคิว เพื่อที่จะเป็นหัวข้อหลักเสมอ


@alecex ฉันพบปัญหาเดียวกัน! แอพจะขัดข้องใน iOS 8.1, 2, 3 แต่จะใช้ได้ใน iOS 8.4 หรือใหม่กว่า คุณสามารถอธิบายรายละเอียดวิธีการหลีกเลี่ยงได้อย่างละเอียดหรือไม่? หรือมีวิธีแก้ไขปัญหาใด ๆ หรือสามารถใช้วิธีการแทนได้หรือไม่
แข็งแกร่ง

ฉันทำหมวดหมู่อย่างรวดเร็วเพื่อจัดการสิ่งนี้คัดลอกวิธีจาก AppKit ซึ่งมีวิธีที่ง่ายและใช้งานง่ายในการทำเช่นนี้ ทำไม Apple ไม่เพิ่มมันเกินฉัน: github.com/cguess/NSMutableAttributedString-HTML
CGuess

2

การใช้ NSHTMLTextDocumentType ช้าและยากที่จะควบคุมสไตล์ ฉันแนะนำให้คุณลองใช้ห้องสมุดของฉันซึ่งชื่อ Atributika มีตัวแยกวิเคราะห์ HTML ที่รวดเร็วมากของตัวเอง นอกจากนี้คุณสามารถมีชื่อแท็กและกำหนดสไตล์ใด ๆ สำหรับพวกเขา

ตัวอย่าง:

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

label.attributedText = str

คุณสามารถค้นหาได้ที่นี่https://github.com/psharanda/Atributika


2

Swift 3 :
ลองทำสิ่งนี้ :

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

และสำหรับการใช้งาน:

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

ส่วนขยายที่เป็นประโยชน์

แรงบันดาลใจจากกระทู้นี้พ็อดและตัวอย่าง ObjC ของ Erica Sadun ใน iOS Gourmet Cookbook p.80 ฉันเขียนส่วนขยายStringและต่อNSAttributedStringไประหว่าง HTML ธรรมดาสตริงและ NSAttributedStrings และในทางกลับกัน - บน GitHub ที่นี่ซึ่ง ฉันพบว่ามีประโยชน์

ลายเซ็นเป็น (อีกครั้งรหัสเต็มรูปแบบในกระทู้ที่ลิงค์ด้านบน):

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

ด้วยตัวอักษร

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

หรือคุณสามารถใช้รุ่นที่ได้รับมาจากและตั้งค่าแบบอักษรบน UILabel หลังจากการตั้งค่า


0

การแปลงในตัวจะตั้งค่าสีข้อความเป็น UIColor.black เสมอแม้ว่าคุณจะผ่านพจนานุกรมแอตทริบิวต์ด้วยการตั้งค่า. colorColor เป็นอย่างอื่น เพื่อรองรับโหมด DARK บน iOS 13 ลองใช้ส่วนขยายรุ่นนี้บน 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)
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.