Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert NSAttributedString into Data for storage

I have a UITextView with attributed text and allowsEditingTextAttributes set to true.

I'm trying to convert the attributed string into a Data object, using the following code:

let text = self.textView.attributedText
let data = try text.data(from: NSMakeRange(0, text.length), documentAttributes: [:])

However, this is throwing the following error:

Error Domain=NSCocoaErrorDomain Code=66062 "(null)"

Any ideas what this error means or what could cause this? I'm on the latest Xcode and iOS. Thanks.

like image 606
Ruben Martinez Jr. Avatar asked Apr 10 '17 00:04

Ruben Martinez Jr.


2 Answers

You need to specify what kind of document data you would like to convert your attributed string to:


.txt    // Plain Text Document Type (Simple Text)
.html   // HTML  Text Document Type (Hypertext Markup Language) 
.rtf    // RTF   Text Document Type (Rich text format document)
.rtfd   // RTFD  Text Document Type (Rich text format document with attachment)

update Xcode 10.2 • Swift 5 or later

let textView = UITextView()
textView.attributedText = .init(string: "abc",
                                attributes: [.font: UIFont(name: "Helvetica", size: 16)!])
if let attributedText = textView.attributedText {
    do {
        let htmlData = try attributedText.data(from: .init(location: 0, length: attributedText.length),
                                               documentAttributes: [.documentType: NSAttributedString.DocumentType.html])
        let htmlString = String(data: htmlData, encoding: .utf8) ?? ""
        print(htmlString)
    } catch {
        print(error)
    }
}

Expanding on that:

extension NSAttributedString {

    convenience init(data: Data, documentType: DocumentType, encoding: String.Encoding = .utf8) throws {
        try self.init(attributedString: .init(data: data, options: [.documentType: documentType, .characterEncoding: encoding.rawValue], documentAttributes: nil))
    }

    func data(_ documentType: DocumentType) -> Data {
        // Discussion
        // Raises an rangeException if any part of range lies beyond the end of the receiver’s characters.
        // Therefore passing a valid range allow us to force unwrap the result
        try! data(from: .init(location: 0, length: length),
                  documentAttributes: [.documentType: documentType])
    }

    var text: Data { data(.plain) }
    var html: Data { data(.html)  }
    var rtf:  Data { data(.rtf)   }
    var rtfd: Data { data(.rtfd)  }
}

Usage:

let textView = UITextView()
textView.attributedText = .init(string: "abc", attributes: [.font: UIFont(name: "Helvetica", size: 16)!])
if let textData = textView.attributedText?.text {
    let text = String(data: textData, encoding: .utf8) ?? ""
    print(text)  // abc
}
if let htmlData = textView.attributedText?.html {
    let html = String(data: htmlData, encoding: .utf8) ?? ""
    print(html)  // /* <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" ...
}

This will print

abc
/* <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<title></title>
<meta name="Generator" content="Cocoa HTML Writer">
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 16.0px Helvetica}
span.s1 {font-family: 'Helvetica'; font-weight: normal; font-style: normal; font-size: 16.00pt}
</style>
</head>
<body>
<p class="p1"><span class="s1">abc</span></p>
</body>
</html>
*/
like image 111
Leo Dabus Avatar answered Nov 11 '22 09:11

Leo Dabus


Please check it once this work for me. You can update font style and family also by using below functions.

 var htmlStr = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n<meta http-equiv=\"Content-Style-Type\" content=\"text/css\">\n<title></title>\n<meta name=\"Generator\" content=\"Cocoa HTML Writer\">\n<style type=\"text/css\">\np.p1 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 22.0px; font: 17.0px \'Times New Roman\'; color: #000000; -webkit-text-stroke: #000000}\nspan.s1 {font-family: \'TimesNewRomanPS-BoldMT\'; font-weight: bold; font-style: normal; font-size: 17.00px; font-kerning: none}\nspan.s2 {font-family: \'Times New Roman\'; font-weight: normal; font-style: normal; font-size: 17.00px; text-decoration: underline ; font-kerning: none}\nspan.s3 {font-family: \'Times New Roman\'; font-weight: normal; font-style: normal; font-size: 17.00px; font-kerning: none}\n</style>\n</head>\n<body>\n<p class=\"p1\"><span class=\"s1\">This </span><span class=\"s2\">is</span><span class=\"s1\"> pikes</span><span class=\"s3\"> AsD this </span><span class=\"s1\">is</span><span class=\"s3\"> finding </span><span class=\"s2\">error</span></p>\n</body>\n</html>\n"
    htmlStr = htmlStr.replacingOccurrences(of: "\n", with: "")
    htmlStr = htmlStr.replacingOccurrences(of: "\\", with: "")

//Send htmlStr to server and when you will get back it from
// then convert html string to attributed string by below line

    let str = Self.htmlToAttributedString(html: htmlStr,fontSize :17, fontName:"Times New Roman")
    self.tfEmail.attributedText = str


//Function for attributed string from html string
func htmlToAttributedString(html:String,fontSize:CGFloat = 15.0, fontName : String = "NunitoSans-Regular",ignorFontBold: Bool = false) -> NSAttributedString {
    let attr = (try? NSAttributedString(htmlString: html, font: UIFont(name: fontName, size: fontSize),ignorFontBold: ignorFontBold)) ?? NSAttributedString()
    return attr
}

//Extension for NSAttribute string
extension NSAttributedString {

convenience init(htmlString html: String, font: UIFont? = nil, useDocumentFontSize: Bool = false, ignorFontBold: Bool = false) throws {
    let options: [NSAttributedString.DocumentReadingOptionKey : Any] = [
        .documentType: NSAttributedString.DocumentType.html,
        .characterEncoding: String.Encoding.utf8.rawValue
    ]

    let data = html.data(using: .utf8, allowLossyConversion: true)
    guard (data != nil), let fontFamily = font?.familyName, let attr = try? NSMutableAttributedString(data: data!, options: options, documentAttributes: nil) else {
        try self.init(data: data ?? Data(html.utf8), options: options, documentAttributes: nil)
        return
    }

    let fontSize: CGFloat? = useDocumentFontSize ? nil : font!.pointSize
    let range = NSRange(location: 0, length: attr.length)
    attr.enumerateAttribute(.font, in: range, options: .longestEffectiveRangeNotRequired) { attrib, range, _ in
        if let htmlFont = attrib as? UIFont {
            let traits = htmlFont.fontDescriptor.symbolicTraits
            var descrip = htmlFont.fontDescriptor.withFamily(fontFamily)
            if ignorFontBold == false{
            if (traits.rawValue & UIFontDescriptor.SymbolicTraits.traitBold.rawValue) != 0 {
                descrip = descrip.withSymbolicTraits(.traitBold)!
            }

            if (traits.rawValue & UIFontDescriptor.SymbolicTraits.traitItalic.rawValue) != 0 {
                descrip = descrip.withSymbolicTraits(.traitItalic) ?? descrip
            }
            }
            attr.addAttribute(.font, value: UIFont(descriptor: descrip, size: fontSize ?? htmlFont.pointSize), range: range)
        }
    }

    self.init(attributedString: attr)
}

}

like image 34
Vikas Rajput Avatar answered Nov 11 '22 09:11

Vikas Rajput