11

I wrote a simple extension to decode the html entities:

extension String {
    func htmlDecode() -> String {
        if let encodedData = self.data(using: String.Encoding.unicode) {
            let attributedString = try! NSAttributedString(data: encodedData, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.unicode], documentAttributes: nil)
            return attributedString.string
        }
        return self
    }
}

Now it throws an error on the line if let attributedString …:

*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 4 beyond bounds [0 .. 2]'

And self is not nil or something, just a String like this:

self = (String) "...über 25'000 Franken..."

Where is this strange NSArray-exception coming from?

Fabio Poloni
  • 8,219
  • 5
  • 44
  • 74

5 Answers5

3

A shot in the dark: do you ensure that the initialization happens on the main thread?

I had exactly the same problem. I noticed that in my case the exception occurs under reproducible conditions (animations in a UICollectionViewController), but I was unable to find the actual cause. My best guess is that it's a framework bug, so I'd too suggest you file a radar.

I ended up pre-rendering my HTML formatted strings into a cache (aka array) at a time where it works, and then load the NSAttributedStrings from it on demand.

Note though that this solution may not fit your use case, since I only have to deal with a limited amount of formatted strings at a time and hence know the expense of rendering them in advance.

ahaese
  • 469
  • 5
  • 12
2

In my case, this was happening because I was trying to instantiate a NSAttributedString from within a UICollectionViewCell that was in detached state (before it was inserted in the parent UICollectionView).

Blago
  • 4,697
  • 2
  • 34
  • 29
0

Seems like a bug, possibly related to how Swift strings handle characters differently than NSString.

I would file a radar.

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287
0

DispatchQueue.main.async { let text = yourHtmlText.htmlDecode() }

  • 1
    Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Mark Rotteveel Mar 09 '21 at 15:51
-1

I just run over this error with a different error:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue unsignedIntegerValue]: unrecognized selector sent to instance 0x60000024b790'

And found a serious bug in this piece of code:

I was passing String.Encoding.unicode - a Swift value - to an Objective-C method that crashed the app. After using String.Encoding.unicode.rawValue the crash disappeared:

extension String {
    func htmlDecode() -> String {
        if let encodedData = self.data(using: String.Encoding.unicode) {
            if let attributedString = try? NSAttributedString(data: encodedData, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.unicode.rawValue], documentAttributes: nil) {
                return attributedString.string
            }
        }
        return self
    }
}
Fabio Poloni
  • 8,219
  • 5
  • 44
  • 74
  • Interesting. I'm still using Swift 2.2, and I have the exact same problem. However, instead of using String.Encoding.unicode.rawValue (which is not available in Swift 2.2) I used NSUTF8StringEncoding for both, the data and NSAttributedString generation, so at least in my case it is not due to Swift specific types. – ahaese Oct 13 '16 at 16:02
  • Good on you! However like @ahaese said, I'm still on swift 2.2 so not able to try this. Anyway, I found this today: [http://stackoverflow.com/a/17081186/3804365](http://stackoverflow.com/a/17081186/3804365). I think I'm going to use this for now. This is not a bullet-proof fix, but it seems to be more robust as it doesn't involve any string encoding stuff. I can still see the crash happen but it's much much less than before. – Bonan Oct 14 '16 at 00:36
  • 2
    @Bonan can you elaborate on your specific use case? Looking at the crash stack trace my problem seems closely related to UICollectionView animation stuff, but I can't tell for sure. My workaround (see my answer) is to do the conversion in advance rather than on demand. – ahaese Oct 14 '16 at 07:12
  • In my case, the HTML strings are in the datasource array of my tableView, and are decoded and put into UITextView while constructing the cells. The crash happens when I scroll really fast and reach the end of the page, feels like the decoding can't catch up with the row creation. I tried to make the decoding done earlier, i.e. before the reload of the table, however it still crashed...BTW all decoding is run on main thread. – Bonan Oct 16 '16 at 22:49
  • I was wrong. Using `textView.setValue(html, forKey: "contentToHTMLString")` doesn't make things better... I guest it's exactly the same as the attributedString way. – Bonan Oct 16 '16 at 22:56
  • @Bonan Pre-generating attributed strings so far works very well in a shipping app. My suggestion would be, experiment and try to find a better place in your app to do the conversion. That being said, there's hope for the future: with Swift 3 and iOS 9 onwards, the issue seems to be gone (I tested with Xcode 8 and different simulators). Targeting iOS ≥ 9.3, I'm unable to reproduce it even if I generate the formatted string on the fly in `cellForRowAt...`. Also, it's smooth as butter, whereas on iOS 8... – ahaese Oct 17 '16 at 15:23
  • @ahaese Thanks for the heads up. For now I'm using search and replace to clean up the string as it's not required to keep the css styles. Anyway, always good to know that there's hope ahead! ;-) – Bonan Oct 18 '16 at 00:25
  • 1
    @ahaese I am still experiencing issues on UITableViewScroll scroll if strings are generated in cellForRowAt... with Xcode 8, iOS 10.2, Swift 3. Works fine if attributed strings are generated in advance. – Deniss Fedotovs Feb 01 '17 at 13:02
  • @DenissFedotovs we noticed the same, and hence still use a pre-generated cache. Could you provide some code? Maybe we can backtrack the issue. This works for me in `cellForRow`...: `let html = "#\(indexPath.row): This is a test"; let formattedString = try! NSAttributedString(data: html.data(using: .utf8, allowLossyConversion: false)!, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil); cell.textLabel!.attributedText = formattedString` – ahaese Feb 05 '17 at 19:24
  • 1
    @ahaese I tried using very similar code, most of the time it works, but in rare cases app crashes on it. Pre-generating attributedString before updating cell works fine for me, also removes cpu overhead on table scroll. – Deniss Fedotovs Feb 06 '17 at 12:04
  • 2
    I'm still getting an error with the code from this answer with Swift 3 (Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndexedSubscript:]: index 0 beyond bounds for empty array') – Mick F Oct 03 '17 at 07:37