Конвертировать 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 приводит к тому, что кодирование занимает действительно очень много времени :(
Ари Литовский,
14
Жаль, что NSHTMLTextDocumentType (буквально) примерно в 1000 раз медленнее, чем установка атрибутов с помощью NSRange. (Профилированный короткий ярлык с одним жирным тегом.)
Джейсон Мур
6
Имейте в виду, что если вы не можете NSHTMLTextDocumentType с этим методом, если вы хотите использовать его из фонового потока. Даже с ios 7 он не будет использовать TextKit для рендеринга HTML. Взгляните на библиотеку DTCoreText, рекомендованную Ingve.
TJez
2
Потрясающие. Просто подумайте, вы могли бы сделать [NSNumber numberWithInt: NSUTF8StringEncoding] как @ (NSUTF8StringEncoding), нет?
Ярсен
15
Я делал это, но будьте осторожны на iOS 8. Это мучительно медленно, близко к секунде для нескольких сотен символов. (В iOS 7 это было почти мгновенно.)
Норман,
43

Существует работу в прогрессе с открытым исходным кодом дополнение к NSAttributedString Оливера Drobnik на Github. Он использует NSScanner для разбора HTML.

Ingve
источник
Требуется минимальное развертывание iOS 4.3 :( Тем не менее, очень впечатляет.
О, Дэнни Бой,
3
@Lirik Overkill для вас, может быть, но идеально подходит для кого-то другого, т. Е. Ваш комментарий ни в коей мере не поможет.
wuf810
3
Обратите внимание, что для этого проекта требуется открытый исходный код, и на него распространяется стандартная лицензия BSD с 2 пунктами. Это означает, что вы должны упомянуть Cocoanetics как первоначального автора этого кода и воспроизвести текст LICENSE внутри вашего приложения.
Дулган
28

Создание NSAttributedString из HTML должно быть сделано в главном потоке!

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

Новый журнал ошибок Relic:

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

Ниже представлено обновленное потоковое расширение 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, если вам нужна обработка событий.
Эндрю Шрайбер
Кодировка по умолчанию, которую использует NSAttributedString, - NSUTF16StringEncoding (не UTF8!). Вот почему это не будет работать. По крайней мере, в моем случае!
Умит Кая
Это должно быть принятым решением. Взаимодействие с HTML-строкой в ​​фоновом потоке может привести к сбою, и довольно часто при выполнении тестов.
ratsimihah
21

Расширение инициализатора Swift для NSAttributedString

Я хотел добавить это как расширение, NSAttributedStringа не String. Я попробовал это как статическое расширение и инициализатор. Я предпочитаю инициализатор, который я включил ниже.

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

пример

let html = "<b>Hello World!</b>"
let attributedString = NSAttributedString(html: html)
Мобильный дан
источник
я хочу, чтобы привет мир был таким <p> <b> <i> привет </ i> </ b> <i> мир </ i> </ p>
Ума Мадхави,
Сохраните немного LOC и замените guard ... NSMutableAttributedString(data:...его try self.init(data:...(и добавьте throwsв init)
nyg
и, наконец, это не работает - текст набирает произвольный размер шрифта
Вячеслав Герчиков
2
Вы декодируете данные с помощью UTF-8, но кодируете их с помощью UTF-16
Шиам Бхат,
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()

Выше я специально добавил юникод, чтобы показать, что он правильно отображает юникод.

Тривиальное: кодировка по умолчанию, которая NSAttributedStringиспользует NSUTF16StringEncoding(не UTF8!).

samwize
источник
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
}
fssilva
источник
5

Swift 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!")
AamirR
источник
Ты спас мой день. Спасибо.
pkc456
@ pkc456 meta.stackexchange.com/questions/5234/… , upvote :) спасибо!
AamirR
Как я могу установить размер шрифта и семейство шрифтов?
Кирк
Это гораздо лучше, чем предлагал Mobile Dan, поскольку в нем не используется избыточная копия с self.init (attribuStString: attribuStString)
цианид,
4

Единственное решение, которое у вас есть сейчас, - это анализ HTML, создание некоторых узлов с заданными атрибутами point / font / etc, а затем объединение их вместе в NSAttributedString. Это много работы, но если все сделано правильно, может быть многоразовым в будущем.

жер
источник
1
Если HTML-код XHTML-Strict, вы можете использовать NSXMLDOcument и друзей, чтобы помочь с анализом.
Дилан Люк
Как бы вы предложили мне создать узлы с заданными атрибутами?
Джошуа
2
Это деталь реализации. Тем не менее, вы анализируете HTML, у вас есть доступ к каждому атрибуту для каждого тега, который определяет такие вещи, как имя шрифта, размер и т. Д. Вы можете использовать эту информацию для хранения соответствующих деталей, которые вам необходимо добавить к атрибутивному тексту, в качестве атрибутов , Как правило, вы должны ознакомиться с синтаксическим анализом, прежде чем приступать к такой задаче.
жер
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 является медленным и трудно управлять стилями. Я предлагаю вам попробовать мою библиотеку, которая называется 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

Свифт 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()
reza_khalafi
источник
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 }
AmitaiB
источник
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 после установки attribute.String.

Антон Тропашко
источник
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)
    }
}
Стивен Орр
источник