Does NSHTMLTextDocumentType support HTML table? - html

In my app, i want to display a text in a UILabel. I use HTML to store the text in my data base to dynamically from my text in my app. I actually use this (Swift 3.2, iOS 8+) :
if let data = text.data(using: .utf8) {
let htmlString = try? NSMutableAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil)
self.textLabel.attributedText = htmlString
}
It's work great for the HTML stuff i used like
<b>Text</b>
<i>Test</i>
And more...
Now, i want to display a table in my label. This is the HTML code for the table :
<table border="2px solid black">
<tr><th>Symbole</th><th>Å</th><th>↓</th><th>■</th><th>╩</th><th>¬</th><th>▓</th><th>Ø</th><th>±</th><th> º </th><th>¶</th><th>░</th></tr>
<tr><td>Utilisation</td><td>1</td><td>11</td><td>11</td><td>5</td><td>1</td><td>4</td><td>12</td><td>4</td><td>1</td><td>5</td><td>1</td></tr>
</table>
This code displays a table form but there is no border in the table. I want to display the table border like the reel HTML render. It's possible or not ?

Weird issue, I didn't understand why this simple thing didn't work, however I managed to make the border appear by adding a random attribute to the NSAttributedString, which makes me believe it's a NSAttributedString rendering bug.
Here's the function that I used (this is Swift 4 but can be converted to earlier versions):
extension String {
func attributedString() -> NSAttributedString? {
guard let data = self.data(using: String.Encoding.utf8,
allowLossyConversion: false) else { return nil }
let options: [NSAttributedString.DocumentReadingOptionKey : Any] = [
NSAttributedString.DocumentReadingOptionKey.characterEncoding : String.Encoding.utf8.rawValue,
NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html
]
let htmlString = try? NSMutableAttributedString(data: data, options: options, documentAttributes: nil)
// Removing this line makes the bug reappear
htmlString?.addAttribute(NSAttributedStringKey.backgroundColor, value: UIColor.clear, range: NSMakeRange(0, 1))
return htmlString
}
}

Related

How to display HTML document correctly in UITextView

I have an UITextview inside a iOS app, where I want to display a HTML document. I found solutions to transform the HTML document to an NSAttributedString.
extension String {
var htmlToAttributedString: NSAttributedString? {
guard let data = data(using: .utf8) else { return nil }
do {
return try NSAttributedString(data: data,
options: [.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
} catch {
return nil
}
}
var htmlToString: String {
return htmlToAttributedString?.string ?? ""
}
}
Now I am struggling with the fact, that the HTML document is not displayed as it should be.
For example I want to display this HTML-example inside my UITextView: https://www.w3schools.com/howto/tryit.asp?filename=tryhow_css_example_website
It should look like this:
HTML document in browser
But it looks like this:
HTML document on iPad emulator inside UITextView
What am I doing wrong?
I think it better to display it in WKWebView WKWebKit Offical Documentation

Unable to show new paragraph for html data into Textview in iOS Swift

I am working on iOS (Swift) application. I am getting some server response like below.
"description":"This is sample text to show in UI. When doing everyday activities.\u003cbr /\u003eclass is a strong predictor of life, and again sample text here.\u003cbr /\u003eSample text can show here also."
So, Above text has 3 paragraphs, I am trying to displaying them in Textview, But it is showing in plain with new line instead of New Paragraph.
override func viewDidLoad() {
super.viewDidLoad()
let description = jsonResponse["description"] as! String
self.textView.attributedText = description.htmlAttributedString()
}
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: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil) else { return nil }
return html
}
}
The issue is it is showing text, But like new line showing instead of new paragraph. How to fix this?
I have fixed this issue by following, And after conversion server response into json serialization, the special characters code showing as
So, I have fixed like below
let description = jsonResponse["description"] as! String
let formattedString = description.replacingOccurrences(of: "<br />", with: " \n\n")
self.textView.text = formattedString

Displaying HTML text in UITextView crash occurred in swift 4?

I am using this code snipped
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
Looks like the problem is the tag, not every tag can show up in uitextview.
You can display better these tag in uiwebview
I thinks the problem is iframe tag.
To display iFrame use uiwebview instead, or wkwebview.
Thanks
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.
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:
Write in main async is helped me.
DispatchQueue.main.async {
self.myLabel.attributedText = self.myAtributedText
}
Try this for UITextView:
let string = "<h2>The bedding was hardly able to cover it.</h2>"
if !string.isEmpty{
if let htmlData = string.data(using:String.Encoding.unicode) {
do {
let attributedText = try NSAttributedString(data: htmlData, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil)
cell.textViewMessage.attributedText = attributedText
} catch let e as NSError {
print("Couldn't translate \(string): \(e.localizedDescription) ")
}
}
Try this for UILabel for setting html text to uilabel with html tags:
extension String {
var withoutHtmlTags: String {
let a = self.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil)
return a.replacingOccurrences(of: "&[^;]+;", with: "", options: String.CompareOptions.regularExpression, range: nil)
}
}
let string = "<h2>The bedding was hardly able to cover it.</h2>"
textUIlabel.text = string.withoutHtmlTags
Changes your options to:
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html,
NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue
]

NSAttributedString from HTML with Hyperlinks

I am attempting to render a large HTML string, that contains hyperlinks, into a UITextView using NSAttributedString. Everything is working fine except the hyperlinks, they don't actually open the link.
For an example, here is a dummy version of my html string:
let htmlString = "<html><p>If you would like to contact someone, you can email
them at <a class=rvts10 href=\"mailto:some#one.com\">some#one.com</a></p></html>"
I have a function called convertHTML() that converts strings to NSAttributedString with html document type options, that I use to assign to the UITextView's attributed text:
textView.attributedText = htmlString.convertHTML()
The TextField is selectable but not editable. When the page is loaded, you can see the hyperlink styling (blue text) and everything, but you can't tap on the link and open the mail app.
I assume this I need to change "mailto:..." to something else that iOS will recognize, but I just have no idea what needs to be done to allow this link to be linkable.
This is my html method:
func convertHtml() -> 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)
}catch{
return NSAttributedString()
}
}
I think you have error with your convertHTML() Method check this
let htmlString = "<html><p>If you would like to contact someone, you can email them at <a class=rvts10 href=\"mailto:some#one.com\">some#one.com</a></p></html>"
// you have to convert string to data
let data = Data(htmlString.utf8)
// then convert data to NSAttributedString with NSAttributedString.DocumentType.htm
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
self.textView.attributedText = attributedString
}
I use this extension :
import Foundation
extension NSAttributedString {
convenience init(htmlString html: String) throws {
try self.init(data: Data(html.utf8), options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
], documentAttributes: nil)
}
}
After implementing you can use it like this:
contentTextField.attributedText = try? NSAttributedString(htmlString: aHTMLString)

".SFUIText-Regular" doesn't exist on watchOS

I am creating an app for Apple Watch and iOS. I have HTML data which I transform into NSAttributedString to display in a UITextView (on iOS). I also want to send it to the watch to display it in a label.
Everything looks ok in the text view (e.g., correct background color). On the watch, it only displays the text (without any colors) and returns this error:
app-Watch Extension[2994:212335] CoreText: PostScript name ".SFUIText-Regular" does not exist.
Here is my code:
let mutAttText = NSMutableAttributedString(attributedString: self.textView.attributedText)
let attributedOptions : [String: AnyObject] = [
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding
]
var data: NSData = NSData()
do {
data = try mutAttText.dataFromRange(NSMakeRange(0, mutAttText.length), documentAttributes: attributedOptions)
} catch {
}
let htmlString = NSString(data: data, encoding: NSUTF8StringEncoding)
print(htmlString)
var attrStr = NSMutableAttributedString()
do {
attrStr = try NSMutableAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType], documentAttributes: nil)
attrStr.enumerateAttribute(NSFontAttributeName, inRange: NSMakeRange(0, attrStr.length), options: NSAttributedStringEnumerationOptions.LongestEffectiveRangeNotRequired, usingBlock: { (attribute: AnyObject?, range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
if let attributeFont = attribute as? UIFont {
let newPointSize = CGFloat(15)
let scaledFont = UIFont(descriptor: attributeFont.fontDescriptor(), size: newPointSize)
attrStr.addAttribute(NSFontAttributeName, value: scaledFont, range: range)
}
})
self.textView.attributedText = attrStr
self.sendText(attrStr)
}
catch {
print("error creating attributed string")
}
Although iOS and watchOS both use the San Francisco font, the fonts do differ between platforms:
iOS, tvOS, and OS X uses San Francisco (SF-UI).
watchOS uses San Francisco Compact (SF-Compact).
It looks like you're trying to scale the pointSize of an iOS system font, but .SFUIText-Regular doesn't exist on watchOS.
You also may want to use systemFontOfSize: instead of trying to scale the point size of a named font, since there are different (text and display) versions depending on point size. This will allow the system to automatically select the appropriate (text or display) variant for that point size.