String interpolation in attributed text from HTML (Swift 4) - html

I am displaying text in my app as an attributed string from local HTML files, populating a label, as this gives me formatting flexibility. Why is the usual string interpolation not working in this case, and is there a workaround? The aim is to allow the user-provided username to be contained within the string. It functions well except leaves "(user)" from the HTML file displayed in the label rather than interpolating the username as I expected. I'm still learning so if this is a strange and unworkable way to be doing things anyway then please let me know...
This is my code:
class ArticleViewController: UIViewController {
#IBOutlet weak var contentField: UITextView!
var articleID : String = ""
var user = UserDefaults.standard.object(forKey: "user") ?? "user"
override func viewDidLoad() {
super.viewDidLoad()
if let html = Bundle.main.path(forResource: "\(articleID)", ofType: "html") {
let urlToLoad = URL(fileURLWithPath: html)
let data = NSData(contentsOf: urlToLoad)
if let attributedString = try? NSAttributedString(data: data as! Data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
contentField.attributedText = attributedString
}
}
}
}
Thanks for your help!

Why is the usual string interpolation not working in this case
Usual string interpolation works on the String literals in the Swift source files, not on the content of general text files or html files.
You may need to replace the occurrences of (user) in the attributed string. (The basic concept is not different from Carpsen90's answer, but you need to be careful when replacing already attributed string.)
if let htmlURL = Bundle.main.url(forResource: articleID, withExtension: "html") {
do {
let data = try Data(contentsOf: htmlURL)
let attributedString = try NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
//### When you want to compare the result...
//originalText.attributedText = attributedString
let regex = try! NSRegularExpression(pattern: "\\(user\\)")
let range = NSRange(0..<attributedString.string.utf16.count)
let matches = regex.matches(in: attributedString.string, range: range)
for match in matches.reversed() {
attributedString.replaceCharacters(in: match.range, with: user)
}
contentField.attributedText = attributedString
} catch {
// Do error processing here...
print(error)
}
}
Example.
article.html:
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<i>(user)</i>😀<b>(user)</b>
</body>
</html>
What you can see in the text view:

You'll have to find and replace the occurrences of (user) in attributedString.
This should work:
import Foundation
import UIKit
var myString : NSAttributedString = NSAttributedString(string: "Hello (user), this is a message for you")
let regex = try! NSRegularExpression(pattern: "\\(user\\)", options: .caseInsensitive)
let range = NSMakeRange(0, myString.string.count)
let newString = regex.stringByReplacingMatches(in: myString.string, options: [], range: range, withTemplate: "CJDSW18")
let newAttribuetdString = NSAttributedString(string: newString, attributes: myString.attributes(at: 0, effectiveRange: nil))
print(newAttribuetdString.string)

Related

The file couldn’t be opened because the text encoding of the contents couldn’t be determined

import UIKit
import SwiftSoup
class ViewController: UIViewController {
#IBOutlet weak var Ingrediants: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let content = try! String(contentsOf: URL(string: "https://google.com")!)
let doc: Document = try! SwiftSoup.parse(content)
let tables = try! doc.select("body").first()!
let rows = try! tables.select("li")
let text = try! tables.html()
Ingrediants.text = text.HtmlToString
}
}
extension String {
var HtmlToString : String? {
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).string
} catch let error as NSError {print(error.localizedDescription)
return nil
}
}
}
In my swift code, what I am trying to do is turn an html code from a website into a text file or string. I used SwftSoup to first parse the html website, and store the contents in 'table'. All the code for this is located in viewDidLoad. I then proceeded to add an extension that should turn the html into a string which also works. When I used a random website like https://hello.com, the code worked and gave an output. But when I use https://google.com, the code doesn't work. I have also used https://www.google.com/?client=safari but I still get the posted error. Any thoughts on how I can fix this?

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

How to display links and images from an HTML String

I have a string that contains HTML code. What is the best way to display that (it contains images), also I want to make links in that tappable (open in Safari)
I have tried String extension that gives me NSAttributedString from HTML, but the image is only partially shown and links are not tappable.
let text = htmlString.attributedString(withRegularFont: UIFont.systemFont(ofSize: 14), andBoldFont: UIFont.systemFont(ofSize: 16))
extension String {
func attributedString(withRegularFont regularFont: UIFont, andBoldFont boldFont: UIFont, textColor: UIColor = UIColor.gray) -> NSMutableAttributedString {
var attributedString = NSMutableAttributedString()
guard let data = self.data(using: .utf8) else { return NSMutableAttributedString() }
do {
attributedString = try NSMutableAttributedString(data: data,
options: [.documentType: NSAttributedString.DocumentType.html,
.characterEncoding:String.Encoding.utf8.rawValue],
documentAttributes: nil)
let range = NSRange(location: 0, length: attributedString.length)
attributedString.enumerateAttribute(NSAttributedString.Key.font, in: range, options: .longestEffectiveRangeNotRequired) { value, range, _ in
let currentFont: UIFont = value as! UIFont
var replacementFont: UIFont? = nil
if currentFont.fontName.contains("bold") || currentFont.fontName.contains("Semibold") {
replacementFont = boldFont
} else {
replacementFont = regularFont
}
let replacementAttribute = [NSAttributedString.Key.font:replacementFont!, NSAttributedString.Key.foregroundColor: textColor]
attributedString.addAttributes(replacementAttribute, range: range)
} catch let e {
print(e.localizedDescription)
}
return attributedString
}
}
It shows me the HTML inside the UILabel but I am not able to tap on links and images are cropped respective to device width.
I think that the best option is to save this html string as a file and then load this file using web view.
check this question

Converting HTML to string with tags

In my app I want to fetch text in HTML format from the server and convert it into string to display using UILabel in another view. To convert the HTML to string I am using this extension:
extension Data{
var html2AttributedString: NSAttributedString?{
do{
return try NSAttributedString(data: self, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
}catch{
print("error", error)
return nil
}
}
var html2String: String {
return html2AttributedString?.string ?? ""
}
}
extension String{
var html2AttributedString: NSAttributedString? {
return Data(utf8).html2AttributedString
}
var html2String: String{
return html2AttributedString?.string ?? ""
}
}
The string is called from an array like so:
text.detailText = textArray[0].html2String
However when the data is displayed onscreen the string just displays as plain text without the HTML tags. What do I need to modify in the extension to display the text properly with the tags in affect?
EDIT: the text label in text.detailText refers to another class.
In context it looks like this:
The text variable in context refers to another class. In context it looks like this:
if let otherClass = segue.destination as? otherClass {
otherClass.detailText = textArray[0].html2String
}
The other class looks like this:
class otherClass: UIViewController {
var data: Data?
#IBOutlet weak var otherDetail: UILabel!
var detailText: String = ""
override func viewDidLoad() {
super.viewDidLoad()
otherDetail?.text = detailText
}
You can see this simple example, it works. I just set label attributedText field
let htmlString = """
<p><b>This text is bold</b></p>
<p><i>This text is italic</i></p>
<p>This is<sub> subscript</sub> and <sup>superscript</sup></p>
"""
let attributedString = htmlString.data(using: .utf8).flatMap { data -> NSAttributedString? in
return try? NSAttributedString(
data: data,
options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
],
documentAttributes: nil)
}
guard let attrString = attributedString else { return }
yourLabel.numberOfLines = 0
yourLabel.attributedText = attrString
Hope, this code will help you
In order to render the html tags, you should use attributed strings:
//detailText should be of type NSAttributedString
detailedText : NSAttributedString!
//replace html2String with html2AttributedString
if let otherClass = segue.destination as? otherClass {
otherClass.detailText = textArray[0].html2AttributedString
}
//and in the otherClass replace .text with .
var data: Data?
#IBOutlet weak var otherDetail: UILabel!
var detailText: NSAttributedString!
override func viewDidLoad() {
super.viewDidLoad()
otherDetail?.attributedText = detailText
}

Extract JSON string from html only using iOS API

I want to extract JSON string from html document "without" using third party Framework.
I'm trying to create iOS framework and I do not want to use third party Framework in it.
Example url:
http://www.nicovideo.jp/watch/sm33786214
In that html, there is a line:
I need to extract:
JSON_String_I_want_to extract
and convert it to JSON object.
With third party framework "Kanna", it is like this:
if let doc = Kanna.HTML(html: html, encoding: String.Encoding.utf8) {
if let descNode = doc.css("#js-initial-watch-data[data-api-data]").first {
let dataApiData = descNode["data-api-data"]
if let data = dataApiData?.data(using: .utf8) {
if let json = try? JSON(data: data, options: JSONSerialization.ReadingOptions.mutableContainers) {
I searched the web with similar question but unable to apply to my case:(I need to admit I'm not quite following regular expression)
if let html = String(data:data, encoding:.utf8) {
let pattern = "data-api-data=\"(.*?)\".*?>"
let regex = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let matches = regex.matches(in: html, options: [], range: NSMakeRange(0, html.count))
var results: [String] = []
matches.forEach { (match) -> () in
results.append( (html as NSString).substring(with: match.rangeAt(1)) )
}
if let stringJSON = results.first {
let d = stringJSON.data(using: String.Encoding.utf8)
if let json = try? JSONSerialization.jsonObject(with: d!, options: []) as? Any {
// it does not get here...
}
Anyone expert in extracting from html and convert it to JSON?
Thank you.
Your pattern does not seem to be bad, just that attribute values of HTML Elements may be using character entities.
You need to replace them into actual characters before parsing the String as JSON.
if let html = String(data:data, encoding: .utf8) {
let pattern = "data-api-data=\"([^\"]*)\""
let regex = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let matches = regex.matches(in: html, range: NSRange(0..<html.utf16.count)) //<-USE html.utf16.count, NOT html.count
var results: [String] = []
matches.forEach {match in
let propValue = html[Range(match.range(at: 1), in: html)!]
//### You need to replace character entities into actual characters
.replacingOccurrences(of: """, with: "\"")
.replacingOccurrences(of: "&apos;", with: "'")
.replacingOccurrences(of: ">", with: ">")
.replacingOccurrences(of: "<", with: "<")
.replacingOccurrences(of: "&", with: "&")
results.append(propValue)
}
if let stringJSON = results.first {
let dataJSON = stringJSON.data(using: .utf8)!
do {
let json = try JSONSerialization.jsonObject(with: dataJSON)
print(json)
} catch {
print(error) //You should not ignore errors silently...
}
} else {
print("NO result")
}
}