Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Displaying HTML text in UITextView crash occurred in swift 4?

I am using this code snipped

Code to show HTML Text in TextView

var htmlToAttributedString: NSAttributedString? {
    guard let data = data(using: .utf8) else { return NSAttributedString() }
    do {
        return try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue], documentAttributes: nil) // Get crash on this line
    } catch let error {
        print(error.localizedDescription)
        return NSAttributedString()
    }
}

var htmlToString: String {
    return htmlToAttributedString?.string ?? ""
}

showing HTML text in UITableViewCell

cell.textViewMessage.attributedText = msg.htmlToAttributedString

Launching first time there is no crash but after that when I run the code got a crash and not working after that.

Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)

#Edit HTML String to display in cell

<p>Here\'s a short video tour.  Press play to start.</p><br><iframe class=\"ql-video\" frameborder=\"0\" allowfullscreen=\"true\" src=\"https://www.youtube.com\"></iframe><br>

#Edit 1 - I am trying to run this code in Playground and it's just working fine except now it's showing an error. Please see the attached image

Playground output

like image 533
iamVishal16 Avatar asked Sep 25 '18 06:09

iamVishal16


2 Answers

Here is a solution inspired by this repo. Basically we remove the iframe tag and replace it with clickable img:

let msg = "<p>Here\'s a short video tour. Press play to start.</p><br><iframe class=\"ql-video\" frameborder=\"0\" allowfullscreen=\"true\" src=\"https://www.youtube.com/embed/wJcOvdkI7mU\"></iframe><br>"

//Let's get the video id
let range = NSRange(location: 0, length: msg.utf16.count)
let regex = try! NSRegularExpression(pattern: "((?<=(v|V)/)|(?<=be/)|(?<=(\\?|\\&)v=)|(?<=embed/))([\\w-]++)")
guard let match = regex.firstMatch(in: msg, options: [], range: range) else {
    fatalError("Couldn't find the video ID")
}
let videoId: String = String(msg[Range(match.range, in: msg)!])

//Let's replace the the opening iframe tag
let regex2 = try! NSRegularExpression(pattern:
    "<[\\s]*iframe[\\s]+.*src=")

let str2 = regex2.stringByReplacingMatches(in: msg, options: [], range: range, withTemplate: "<a href=")

//And then replace the closing tag
let regex3 = try! NSRegularExpression(pattern:
    "><\\/iframe>")
let range2 = NSRange(location: 0, length: str2.utf16.count)
let str3 = regex3.stringByReplacingMatches(in: str2, options: [], range: range2, withTemplate: "><img src=\"https://img.youtube.com/vi/" + videoId + "/0.jpg\" alt=\"\" width=\"\(textView.frame.width)\" /></a>")         // You could adjust the width and height to your liking

//Set the text of the textView
textView.attributedText = str3.htmlToAttributedString
textView.delegate = self

To open the Youtube app when the user taps and holds on the image, implement this delegate method:

extension NameOfYourViewController: UITextViewDelegate {
    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        UIApplication.shared.open(URL, options: [:])
        return true
    }
}

If the youtube app is not installed, then the video will be played in Safari.

Here is the result:

Youtube in a UITextField

like image 166
ielyamani Avatar answered Oct 02 '22 13:10

ielyamani


The reason for this issue is the table view, this error will occur very randomly and hard to reproduce because its more specific to device memory and UI draw process which might result in executing the method in the background thread. While reallocating and deallocating the table cells, deep down somewhere the table cells might call this method on a background thread while the HTML importer uses a non-thread-safe WebKit importer and expects to be on the main thread.

How to reproduce this error: Run the code using UITest and it will crash more often since the unit test slows down the UI draw process significantly

Solution: decode HTML to String should be on the main thread but do this in the model layer on main thread instead of doing it during cell creation. This will make the UI more fluid as well.

Why the crash was not caught in catch block: Your app has crashed due to an unhandled language exception, as used by the exception handling infrastructure for Objective-C. SWIFT is like a nice wrapper around Cocoa’s NSError. Swift is not able to handle Objective-C exceptions, and thus an exception through by Objective-C code in the frameworks will not be caused by your Swift error handler.

like image 40
ksinghit Avatar answered Oct 02 '22 13:10

ksinghit