In order to convert html to string, I use 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 ?? ""
}
}
But text what i get in API i must convert with NSAttributedString.DocumentType.html and sometime with NSAttributedString.DocumentType.plain
How can i combine these two parameters?
What those extensions do is (1) to create a Data object out of an HTML string, (2) to convert the Data object into an NSAtrributedString object. In other words, it goes like the following.
import UIKit
class ViewController: UIViewController {
// MARK: - Variables
// MARK: - IBOutlet
#IBOutlet weak var label: UILabel!
// MARK: - IBAction
override func viewDidLoad() {
super.viewDidLoad()
let htmlStr = makeHTMLString()
let data = Data(htmlStr.utf8)
if let attributedString = try?NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
//print(attributedString.string)
label.attributedText = attributedString
}
}
func makeHTMLString() -> String {
return "<html>\n<head></head>\n<body>\n<h1>Hello, world!</h1>\n</body>\n</html>"
}
}
In the end, you don't need those extensions.
Related
Here is my code which is used to convert HTML string to attributed string.
extension UILabel {
func setHTMLFromString(text: String) {
let modifiedFont = NSString(format:"<span style=\"font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%#</span>" as NSString, text)
let attrStr = try! NSAttributedString(
data: modifiedFont.data(using: String.Encoding.unicode.rawValue, allowLossyConversion: true)!,
options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
self.attributedText = attrStr
}
}
What I want to achieve is to translate the text with attributes applied to it. Below is the code for translation that I'm using to translate the simple string.
extension UIViewController {
func translate(text:String, to:TranslateLanguage, compltion: #escaping ((Bool,String?)->()) ) {
let options = TranslatorOptions(sourceLanguage: .en, targetLanguage: to)
let translator = NaturalLanguage.naturalLanguage().translator(options: options)
perform(#selector(showHudAfterTime), with: self, afterDelay: 1)
translator.downloadModelIfNeeded(with: .init(allowsCellularAccess: true, allowsBackgroundDownloading: false)) { (error) in
NSObject.cancelPreviousPerformRequests(withTarget: self)
GlobalAPI.hideLoadingHUD()
if error == nil {
translator.translate(text) { (translation, error) in
if error == nil {
compltion(true,translation)
} else {
compltion(false,text)
}
}
} else {
compltion(false,text)
}
}
}
#objc func showHudAfterTime() {
GlobalAPI.showLoadingHud()
}
}
Let me know if need any more description.
Any help would be appreciated!
I want to display a string(which is actually a small HTML code, with hyperlink) on UILabel. E.g. The string which I have to display is: "Click here to know more". So is there a way to display it on UILabel, and on clicking the hyperlink(Click here) it opens up the desired web page?
Here's what I did:
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let htmlData = NSString(string: "Click here to know more").data(using: String.Encoding.unicode.rawValue)
let attributedString = try? NSAttributedString(data: htmlData!, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
testLabel.attributedText = attributedString
}
The label displayed the string just like I wanted, but on clicking the hyperlink, it didn't do what what desired, i.e. open a web page.
Try to use UITextView instead of UILabel
#IBOutlet weak var testTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
testTextView.isEditable = false
testTextView.dataDetectorTypes = .link
testTextView.isScrollEnabled = false
let htmlData = NSString(string: "Click here to know more").data(using: String.Encoding.unicode.rawValue)
let attributedString = try? NSAttributedString(data: htmlData!, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
testTextView.attributedText = attributedString
}
#IBOutlet weak var testLabel: UILabel!
let message = "Please <a href='https://www.google.com'>click here</a> to search"
#override func viewDidLoad() {
super.viewDidLoad()
formatLabel(with: message.htmlToString)
}
func formatLabel(with message: String) {
let formattedText = String.format(strings: ["click here"],
boldFont: UIFont.init(name: "Roboto-Bold", size: 16.0)!,
boldColor: UIColor.blue,
inString: message,
font: UIFont.init(name: "Roboto-Regular", size: 16.0)!,
color: UIColor(red: 33/255, green: 136/255, blue: 189/255, alpha: 1.0))
testLabel.attributedText = formattedText
testLabel.numberOfLines = 0
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTermTapped))
testLabel.addGestureRecognizer(tap)
testLabel.isUserInteractionEnabled = true
testLabel.textAlignment = .center
}
#objc func handleTermTapped(gesture: UITapGestureRecognizer) {
let clickString = (testLabel.text ?? "") as NSString
let clickRange = clickString.range(of: "click here")
let tapLocation = gesture.location(in: testLabel)
let index = testLabel.indexOfAttributedTextCharacterAtPoint(point: tapLocation)
if checkRange(clickRange, contain: index) == true {
guard let url = URL(string: "https://www.google.com") else { return }
UIApplication.shared.open(url, options: [:], completionHandler: nil)
return
}
}
Helper function and extensions:
func checkRange(_ range: NSRange, contain index: Int) -> Bool {
return index > range.location && index < range.location + range.length
}
extension String {
static func format(strings: [String],
boldFont: UIFont = UIFont.init(name: "Roboto-Bold", size: 16.0)!,
boldColor: UIColor = UIColor.blue,
inString string: String,
font: UIFont = UIFont.init(name: "Roboto-Regular", size: 16.0)!,
color: UIColor = UIColor(red: 33/255, green: 136/255, blue: 189/255, alpha: 1.0)) -> NSAttributedString {
let attributedString =
NSMutableAttributedString(string: string,
attributes: [
NSAttributedString.Key.font: font,
NSAttributedString.Key.foregroundColor: color])
let boldFontAttribute = [NSAttributedString.Key.font: boldFont, NSAttributedString.Key.foregroundColor: boldColor]
for bold in strings {
attributedString.addAttributes(boldFontAttribute, range: (string as NSString).range(of: bold))
}
return attributedString
}
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)
} catch {
return NSAttributedString()
}
}
var htmlToString: String {
return htmlToAttributedString?.string ?? ""
}
}
extension UILabel {
func indexOfAttributedTextCharacterAtPoint(point: CGPoint) -> Int {
let textStorage = NSTextStorage(attributedString: self.attributedText!)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: self.frame.size)
textContainer.lineFragmentPadding = 0
textContainer.maximumNumberOfLines = self.numberOfLines
textContainer.lineBreakMode = self.lineBreakMode
layoutManager.addTextContainer(textContainer)
let index = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return index
}
}
Here is simple demo of how to do what you need with UILabel
import UIKit
class ViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.frame = CGRect(x: 150, y: 200, width: 200, height: 20)
label.attributedText = NSAttributedString(string: "Tap Me", attributes: [.link : URL(string: "http://www.google.com")!])
label.isUserInteractionEnabled = true
label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleLink(_:))))
view.addSubview(label)
self.view = view
}
#IBAction func handleLink(_ sender: UIGestureRecognizer) {
guard sender.state == .ended else { return }
guard let label = sender.view as? UILabel else { return }
guard let link = label.attributedText?.attribute(.link, at: 0, effectiveRange: nil) as? URL else { return }
UIApplication.shared.open(link, options: [:], completionHandler: nil)
}
}
Imagine I have this json:
I could convert Json to string and show it by below code:
extension String {
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)
} catch {
return NSAttributedString()
}
}
var htmlToString: String {
return htmlToAttributedString?.string ?? ""
}
}
but I can’t display the image in json. How can I do that?
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
}
I created a UILabel and synced its font size with a seekbar and loaded my html formatted text onto the UILabel. Unfortunately, the format gets removed if I change the font size of my UILabel using seekbar.
I used this approach, from garie, that was answered from another thread
private func getHtmlLabel(text: String) -> UILabel {
let label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.attributedString = stringFromHtml(string: text)
return label
}
private func stringFromHtml(string: String) -> NSAttributedString? {
do {
let data = string.data(using: String.Encoding.utf8, allowLossyConversion: true)
if let d = data {
let str = try NSAttributedString(data: d,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil)
return str
}
} catch {
}
return nil
}
Link: Swift: Display HTML data in a label or textView
Is there any approach I could use to retain the my html formatted text?
EDIT #1:
This is how I resize my UILabel.
#IBOutlet weak var textLabel: UILabel!
#IBAction func resizeText(_ sender: UISlider) {
textLabel.font = UIFont.systemFont(ofSize: CGFloat(sender.value) * 20.0)
}
The HTML data I'm passing onto my UILabel:
<html><body><b>hello world</b></body></html>
This is the formatted HTML data on UILabel:
After resizing the UILabel, it loses its format:
EDIT #2:
Tried Milan's answer but encountered an error. Unresolved identifier in NSAttributedStringKey
Here's my code:
import UIKit
class GalleryViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
textLabel.attributedText = stringFromHtml()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBOutlet weak var textLabel: UILabel!
#IBAction func resizeText(_ sender: UISlider) {
if let attributedText = textLabel.attributedText {
let newAttributedText = NSMutableAttributedString(attributedString: attributedText)
newAttributedText.setFontSize(newSize: CGFloat(sender.value) * 20.0)
textLabel.attributedText = newAttributedText
}
}
private func stringFromHtml() -> NSAttributedString? {
do {
let string = "<html><body><b>hello world</b></body></html>"
let data = string.data(using: String.Encoding.utf8, allowLossyConversion: true)
if let d = data {
let str = try NSAttributedString(data: d,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil)
return str
}
} catch {
}
return nil
}
}
extension NSMutableAttributedString {
func setFontSize(newSize: CGFloat) {
beginEditing()
self.enumerateAttribute(NSAttributedStringKey.font, in: NSRange(location: 0, length: self.length)) { (value, range, stop) in
if let f = value as? UIFont {
let newFont = f.withSize(newSize)
removeAttribute(NSAttributedStringKey.font, range: range)
addAttribute(NSAttributedStringKey.font, value: newFont, range: range)
}
}
endEditing()
}
}
Setting the font directly messes up your attributed text. You will have to set font size on the attributedText:
#IBAction func resizeText(_ sender: UISlider) {
if let attributedText = textLabel.attributedText {
let newAttributedText = NSMutableAttributedString(attributedString: attributedText)
newAttributedText.setFontSize(newSize: CGFloat(sender.value) * 20.0)
textLabel.attributedText = newAttributedText
}
}
For this to work you will need following extension on NSMutableAttributedString:
extension NSMutableAttributedString {
func setFontSize(newSize: CGFloat) {
beginEditing()
self.enumerateAttribute(NSAttributedStringKey.font, in: NSRange(location: 0, length: self.length)) { (value, range, stop) in
if let f = value as? UIFont {
let newFont = f.withSize(newSize)
removeAttribute(NSAttributedStringKey.font, range: range)
addAttribute(NSAttributedStringKey.font, value: newFont, range: range)
}
}
endEditing()
}
}
Try using UserDefaults to save the the NSAttributedString returning from second function:
1- Hold the attributed text in a variable
let attributedText = stringFromHtml(string: String)
2- Save it in UserDefaults
UserDefaults.standard.set(attributedText, forKey: "attributedText")
And for retrieving it you may use this format to safely unwrap the value:
if let text = UserDefaults.standard.object(forKey: "attributedText") as? NSAttributedString {
label.attributedString = text
}