1

In my code there s an extension for NSAttributedString:

internal convenience init?(html: String) {
    guard let data = html.data(using: String.Encoding.utf8, allowLossyConversion: true) else {
        return nil
    }
    print(UIKit.Thread.isMainThread) //TRUE
    guard let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) else { //here is the error
        return nil
    }
    self.init(attributedString: attributedString)
}

then I try to use it like this:

let text = "<p>Your order has been created. </p><p>Below You can find the details of Your order:</p><p>Order ID: 183</p><p>Summary: <ul><li>Filtered coffee 50.00 x 1</li></ul></p><p>Service fee: 30.0</p><p>Total: 80.0 Kn</p><p>You will receive a message when our staff starts preparing Your order.<br/></p>"

let attributedString = NSAttributedString(html: text)

text has the following value:

Your order has been created. 

Below You can find the details of Your order:

Order ID: 183

Summary:

  • Filtered coffee 50.00 x 1

Service fee: 30.0

Total: 80.0 Kn

You will receive a message when our staff starts preparing Your order.

What is wrong?;)

EDIT:

I use it with MessageKit to display attributed text:

extension Message: MessageType {
var sender: Sender {
    return Sender(id: createdBy?.identifier ?? "", displayName: createdBy?.name ?? "BOT_RESPONSE")
}
var messageId: String {
    return identifier
}
var sentDate: Date {
    return date
}
var kind: MessageKind {
    guard let attributedString = NSAttributedString(html: text) else {
        return .text(text)
    }
    return .attributedText(attributedString)
}
}

When I put the breakpoint at line where error arise and print it on console:

po NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)

then everything is fine;) Why?

Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
  • Try `String.Encoding.uft16` or `.utf8` instead of Unicode. `NSString` / `NSAttributedString` were designed around UTF 16. – Code Different Sep 12 '18 at 15:26
  • 2
    I tested, but didn't got a crash (with the previous HTML text you removed from the question). – Larme Sep 12 '18 at 15:27
  • I don't have an exact answer to your question, but I do have some points to look at. 1) you turn `html` into `Data` using `unicode` encoding, but then tell the attributed string to use `.utf8`. Should these be different? 2) Wrap the `try` in a do/catch block so that you can see any exceptions that happen and possibly get more clarity as to what's going on. 3) Are you calling this on the main thread or on a background thread? 4) I pasted this exact code into a playground and it worked without crashing. – keithbhunter Sep 12 '18 at 15:28
  • May it be MessageKit issue?;) I updated the question.. – Bartłomiej Semańczyk Sep 12 '18 at 15:31
  • What is your problem? Where does an error occur? – Nikolai Ruhe Sep 25 '18 at 08:17

2 Answers2

3

This extension on NSAttributedStringworks for me with String.Encoding.utf16:

extension NSAttributedString {
    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)
    }
}

let text = "<p>Your order has been created. </p><p>Below You can find the details of Your order:</p><p>Order ID: 183</p><p>Summary: <ul><li>Filtered coffee 50.00 x 1</li></ul></p><p>Service fee: 30.0</p><p>Total: 80.0 Kn</p><p>You will receive a message when our staff starts preparing Your order.<br/></p>"

let attributedString = NSAttributedString(html: text)
print(attributedString ?? "Nothing")

Here is in an extension on String that works for me in a playground (It is ported to Swift 4 from this answer.):

import Foundation
import UIKit
import PlaygroundSupport

let label = UILabel()

extension String {
    func initFrom(html: String, with completionHandler: @escaping (NSAttributedString?) ->()) {
        guard let data = html.data(using: String.Encoding.utf8, allowLossyConversion: true) else {
            return completionHandler(nil)
        }

        let options: [NSAttributedString.DocumentReadingOptionKey : Any] = [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue]

        DispatchQueue.main.async {
            if let attributedString =
                try? NSAttributedString(data: data, options: options, documentAttributes: nil)
            {
                completionHandler(attributedString)
            } else {
                completionHandler(nil)
            }
        }
    }
}

let text = "<p>Your order has been created. </p><p>Below You can find the details of Your order:</p><p>Order ID: 183</p><p>Summary: <ul><li>Filtered coffee 50.00 x 1</li></ul></p><p>Service fee: 30.0</p><p>Total: 80.0 Kn</p><p>You will receive a message when our staff starts preparing Your order.<br/></p>"

text.initFrom(html: text, with: { attString in
    print("attString =", attString ?? "")
    label.attributedText = attString
})

PlaygroundPage.current.needsIndefiniteExecution = true
ielyamani
  • 17,807
  • 10
  • 55
  • 90
1

I had a similar issue before and I found that I was running my logic on a background thread. It was crashing at the line where I created my new NSAttributedString instance, similar to your line let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil). Can you make sure that you are creating the NSAttributedString instance on the main thread?

In the NSAttributedString documentation it's stated that "The HTML importer should not be called from a background thread (that is, the options dictionary includes documentType with a value of html)", so maybe this could guide you a little bit to figure out the problem.

  • I think it is the case. When I run the same function within `AppDelegate` then everything works pretty fine;) So, what may be the reason? – Bartłomiej Semańczyk Sep 18 '18 at 11:29
  • It's very hard to tell to be honest without deep debugging :) But the UIKit generally is not thread-safe. Maybe more than one thread are trying to render the `NSAttributedString` and one of them is a background thread, so this is why the crash happens. Inside AppDelegate, for example if you do your logic inside `didFinishLaunchingWithOptions` you are pretty sure that the thread executing your logic is the main thread here, well at least at this point. – Mohammed Abdullatif Sep 18 '18 at 12:16
  • Did you try maybe dispatching your function again to the main thread by wrapping it inside `DispatchQueue.main.async`? – Mohammed Abdullatif Sep 18 '18 at 12:17