Перетворити HTML в NSAttributedString в iOS


151

Я використовую екземпляр, UIWebViewщоб обробити якийсь текст і розфарбувати його правильно, він дає результат як HTML, але замість того, щоб відображати його у UIWebViewя хочу відобразити за Core Textдопомогою а NSAttributedString.

Я вмію створювати та малювати, NSAttributedStringале я не впевнений, як я можу конвертувати та відображати HTML в атрибутованій рядку.

Я розумію, що в Mac OS X NSAttributedStringє initWithHTML:метод, але це був лише додаток для Mac і не доступний для iOS.

Я також знаю, що питання є подібним до цього, але на нього не було відповідей, я хоч би спробував ще раз і побачив, чи створив хтось спосіб це зробити, і якщо так, якщо вони могли б поділитися ним.


2
Бібліотека NSAttributedString-Additions-for-HTML була перейменована і зведена в рамки тим же автором. Зараз він називається DTCoreText і включає купу класів верстки Core Text. Ви можете знайти його тут
Брайан Дуглас Моуклі

Відповіді:


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 (буквально) ~ 1000 разів повільніше, ніж встановлення атрибутів з NSRange. (Профілював коротку мітку з одним жирним тегом.)
Джейсон Мур

6
Майте на увазі, що якщо ви не можете NSHTMLTextDocumentType цим методом, якщо ви хочете використовувати його з фонової нитки. Навіть з ios 7 він не використовуватиме TextKit для візуалізації HTML. Погляньте на бібліотеку DTCoreText, яку рекомендує Ingve.
TJez

2
Дивовижно. Просто думка, ви, ймовірно, могли зробити [NSNumber numberWithInt: NSUTF8StringEncoding] як @ (NSUTF8StringEncoding), ні?
Ярсен

15
Я робив це, але будьте обережні на iOS 8. Це болісно повільно, близько секунди на кілька сотень символів. (У iOS 7 це було майже миттєво.)
Норман

43

В Github є додаток з відкритим кодом, що не працює, до NSAttributedString від Олівера Дробника. Він використовує NSScanner для розбору HTML.


Потрібна мінімальна розгортання iOS 4.3 :( Не менш, дуже вражаюче
О Danny Boy,

3
@Lirik Overkill для вас, можливо, але ідеально підходить для когось іншого, тобто ваш коментар не є ні в чому корисним.
wuf810

3
Зверніть увагу, що цей проект вимагає відкритого коду та охоплюється стандартною ліцензією BSD на 2 пункти. Це означає, що ви повинні згадати Cocoanetics як оригінального автора цього коду та відтворити текст ЛІЦЕНЗІЇ у вашому додатку.
dulgan

28

Створення NSAttributedString з HTML необхідно виконати на головній темі!

Оновлення: виявляється, що HTML-рендерінг NSAttributedString залежить від WebKit під кришкою, і його потрібно запускати на головній темі, або це час від часу збій програми з SIGTRAP .

Новий журнал аварій Relic:

введіть тут опис зображення

Нижче представлено оновлене розширення Swift 2 Strift, захищене від потоків :

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, якщо вам потрібно обробляти події.
Ендрю Шрайбер

Кодування за замовчуванням, яке використовує NSAttributedString, це NSUTF16StringEncoding (не UTF8!). Ось чому це не вийде. Принаймні в моєму випадку!
Umit Kaya

Це має бути прийнятим рішенням. Ведуча HTML-розмова на фоновому потоці з часом вийде з ладу, і досить часто під час запуску тестів.
раціміха

21

Швидке розширення ініціалізатора на 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)

Я хочу, щоб світ привіт був подібним <p><b>< <i>hello</i> </b> <i>world</i> </p>
Ума Мадхаві

Збережіть місце LOC та замініть guard ... NSMutableAttributedString(data:...його try self.init(data:...(і додайте throwsдо init)
nyg

і нарешті це не працює - текст набирає випадковий розмір шрифту
В’ячаслав Герчич

2
Ви декодуєте дані за допомогою UTF-8, але кодуєте їх за допомогою UTF-16
Shyam Bhat

11

Це Stringрозширення, написане на Swift, щоб повернути 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()

У вищесказаному я спеціально додав unicode \ u2022, щоб показати, що він відображає unicode правильно.

Тривіальне: Кодування, яке 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
  • Без зайвих охоронців
  • кидає помилку

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, оскільки він не передбачає надмірної копії з self.init (attributedString: attributedString)
ціанід

4

Єдине рішення, яке ви маєте зараз, - це проаналізувати HTML, створити кілька вузлів із заданими атрибутами point / font / etc, а потім об'єднати їх у NSAttributedString. Це велика робота, але якщо виконати правильно, то можна буде скористатися повторно.


1
Якщо HTML є XHTML-Strict, ви можете використовувати NSXMLDOcument та друзів, щоб допомогти в розборі.
Ділан Лукес

Як би ви запропонували розпочати створення вузлів із заданими атрибутами?
Джошуа

2
Це деталізація реалізації. Однак, якщо ви розбираєте HTML, у вас є доступ до кожного атрибуту для кожного тегу, який визначає такі речі, як ім'я шрифту, розмір тощо. Ви можете використовувати цю інформацію для зберігання відповідних даних, які вам потрібно буде додати до атрибутованого тексту як атрибути . Як правило, вам потрібно спочатку ознайомитися з розбором, перш ніж вирішувати таке завдання.
jer

2

Вищенаведене рішення є правильним.

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

Але програма вийде з ладу, якщо ви працюєте на ios 8.1,2 або 3.

Щоб уникнути аварії, ви можете зробити це: запустити це в черзі. Так що вона завжди буде на головній нитці.


@alecex Я зіткнувся з тією ж проблемою! додаток вийде з ладу на iOS 8.1, 2, 3. Але буде добре в iOS 8.4 або новіших версіях. Чи можете ви детально пояснити, як цього уникнути? чи є якась робота навколо, або замість цього можна використовувати методи?
Сильний

Я зробив швидку категорію для вирішення цього питання, скопіювавши методи з AppKit, який має дуже простий та інтуїтивний спосіб зробити це. Чому Apple не додала, це не за мене: github.com/cguess/NSMutableAttributedString-HTML
CGuess

2

Використання NSHTMLTextDocumentType повільне, і важко керувати стилями. Я пропоную вам спробувати мою бібліотеку, яка називається Атрибутика. У нього є власний дуже швидкий парсер 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 Еріки Sadun в IOS Gourmet Cookbook с.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 після встановлення attributedString


0

Вбудована конверсія завжди встановлює колір тексту на UIColor.black, навіть якщо ви передаєте словник атрибутів із .forgroundColor, встановленим на щось інше. Щоб підтримати режим 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)
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.