HTML в NSAttributedString и NSAttributedString в HTML

Я хочу преобразовать строку html в NSAttributedString, а затем поработать со строкой, например (изменить цвета, размеры шрифта, семейство шрифтов, background-, foreground-color...), а затем преобразовать строку обратно в обычный html. из NSAttributedString.

Преобразование не проблема, но каждый раз, когда я конвертирую html в NSAS и обратно, размер шрифта становится все больше и больше...

Пример детской площадки:

// Playground - noun: a place where people can play
// NSAS: - NSAttributedString

import UIKit

class Wrapper {

    //MARK: fields
    let apiHtml = "<div style='font-size: 18px'><span style='font-family:&#039;andale mono&#039;, times;'>Dies</span> <span style='font-family:&#039;comic sans ms&#039;, sans-serif;'>ist</span> <strong><span style='font-family:&#039;andale mono&#039;, sans-serif;';>eine</span></strong> <em>formatierte</em> <span style='text-decoration:underline;'>Karte</span>&#160;<span style='font-size:16px;'>die</span> <span style='background-color:#ffff00;'>es</span> zu &#220;bernehmen gilt</div>"

    var newGeneratedHtml = ""
    var textView : UITextView!

    //MARK: constructor
    init() {
        //init textview
        textView = UITextView(frame: CGRectMake(0, 0, 500, 300))

        //convert html into NSAS and set it to textview
        if let attributedText = getAttributedTextFromApiHtmlString(apiHtml) {
            textView.attributedText = attributedText
        }

        //get html text from textfields NSAS
        if let htmlText = getHtmlTextFromTextView() {
            newGeneratedHtml = htmlText
            println(htmlText)
        }

        //set the converted html from textfields NSAS
        if let attributedText = getAttributedTextFromApiHtmlString(newGeneratedHtml) {
            textView.attributedText = attributedText
        }

        //get html text from textfields NSAS
        if let htmlText = getHtmlTextFromTextView() {
            newGeneratedHtml = htmlText
            println(htmlText)
        }
    }

    //MARK: methods
    func getAttributedTextFromApiHtmlString(text : String) -> NSAttributedString? {
        if let attributedText = NSAttributedString(data: text.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!, options: [NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType], documentAttributes: nil, error: nil) {
            return attributedText
        }
        return nil
    }

    func getHtmlTextFromTextView() -> String? {
        let attributedTextFromTextView = textView.attributedText
        if let htmlData = attributedTextFromTextView.dataFromRange(NSMakeRange(0, attributedTextFromTextView.length), documentAttributes: [NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType], error: nil) {
            if let htmlString = NSString(data: htmlData, encoding: NSUTF8StringEncoding) {
                return htmlString
            }
        }
        return nil
    }
}

var w = Wrapper()

Это результат игровой площадки. Вы можете видеть, что второй текст больше первого, но я нигде не менял размер шрифта.

Это ошибка или мне нужно было установить фиксированный размер шрифта?

Вот результат игровой площадки, вы можете видеть, что второй текст больше, чем первый текст, но я не изменил  размер шрифта в любом месте.

ОБНОВЛЕНИЕ:

Я принимаю ответ @Lou Franco. Я не знаю, почему NSAS конвертирует px в pt и обратно, но вот мой обходной путь:

func getAttributedTextFromApiHtmlString(text : String) -> NSAttributedString? {
        if let attributedText = NSAttributedString(data: text.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, options: [NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType], documentAttributes: nil, error: nil) {

            var res : NSMutableAttributedString = attributedText.mutableCopy() as NSMutableAttributedString
            res.beginEditing()
            var found : Bool = false;
            res.enumerateAttribute(NSFontAttributeName, inRange:NSMakeRange(0, res.length) ,options:NSAttributedStringEnumerationOptions.allZeros, usingBlock: {(value:AnyObject!, range:NSRange, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
                if ((value) != nil) {
                    let oldFont = value as UIFont;
                    let newFont = oldFont.fontWithSize(15)
                    res.removeAttribute(NSFontAttributeName, range:range)
                    res.addAttribute(NSFontAttributeName, value: newFont, range: range)
                    found = true
                    }
                })
            if !found {
                // No font was found - do something else?
            }
            res.endEditing()
            return res
        }
        return nil
    }

Единственным недостатком этого является то, что вы теряете разную высоту текста в NSAS....

Если у кого-то есть решение или лучшая работа, не стесняйтесь публиковать свой ответ.


person Konstantin Heinrich    schedule 10.02.2015    source источник
comment
Интересно, что если вы прокачаете сгенерированный HTML обратно через цикл, вы получите еще одно увеличение размера на 1/3. Это повторяется до тошноты.   -  person David Berry    schedule 11.02.2015
comment
Почему это непонятно или бесполезно?   -  person Konstantin Heinrich    schedule 11.02.2015


Ответы (3)


Возможно, это ошибки округления при двустороннем обходе. Попробуйте использовать целые размеры точек (с pt вместо px)

Хорошо, глядя на вывод вашей консоли, она переводит px в pt, так что, возможно, вы сможете взломать ее, взяв HTML-код, полученный в результате преобразования, и изменив pt обратно на px.

person Lou Franco    schedule 10.02.2015
comment
Делает то же самое с pts. - person David Berry; 11.02.2015
comment
Изменение размера шрифта html с px на pt ничего ни на что не влияет, в следующий раз он увеличится - person Konstantin Heinrich; 11.02.2015

Я решил эту проблему, применив коэффициент 0,75 к каждому размеру фонда в вашей строке. Скажем, если у вас есть несколько шрифтов в вашей атрибутированной строке, когда вы зацикливаете их все, просто примените соотношение, и тогда все готово. Вот мой код в Swift 3.0:

yourAttrStr.beginEditing()
yourAttrStr.enumerateAttribute(NSFontAttributeName, in: NSMakeRange(0, yourAttrStr.length), options: .init(rawValue: 0)) { 
        (value, range, stop) in
        if let font = value as? UIFont {
            let resizedFont = font.withSize(font.pointSize * 0.75)
            yourAttrStr.addAttribute(NSFontAttributeName, value: resizedFont, range: range)
        }
}
yourAttrStr.endEditing()//yourAttrStr will be the same size as html string

Это фрагмент кода, который работает в моем приложении.

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 }
        html.beginEditing()
        html.enumerateAttribute(NSFontAttributeName, in: NSMakeRange(0, html.length), options: .init(rawValue: 0)) {
            (value, range, stop) in
            if let font = value as? UIFont {
                let resizedFont = font.withSize(font.pointSize * 0.75)
                html.addAttribute(NSFontAttributeName,
                                         value: resizedFont,
                                         range: range)
            }
        }
        html.endEditing()
        return html
    }
}

Чтобы использовать его:

let htmlStr: String = "<font size=\"6\">font a</font><font size=\"16\">Font b</font>"
let attriStr: NSAttributedString? = htmlStr.htmlAttributedString()
person Fangming    schedule 19.06.2017

В Swift 3, использующем решение @fangming, это сработало для меня:

func newAttrSize(blockQuote: NSAttributedString) -> NSAttributedString
{
    let yourAttrStr = NSMutableAttributedString(attributedString: blockQuote)
    yourAttrStr.enumerateAttribute(.font, in: NSMakeRange(0, yourAttrStr.length), options: .init(rawValue: 0)) {
        (value, range, stop) in
        if let font = value as? UIFont {
            let resizedFont = font.withSize(font.pointSize * 0.75)
            yourAttrStr.addAttribute(.font, value: resizedFont, range: range)
        }
    }

    return yourAttrStr
}
person Asfand Shabbir    schedule 14.06.2018