Resizing UILabel with HTML data - html

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
}

Related

Translate HTML string to with Attributes applied using Firebase/MLNLTranslate

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!

How to display HTML text(as string) on UILabel with hyperlinks?

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)
}
}

How to convert html to string

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.

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
}

NSAttributedString to HTML in iOS Swift 3

I have searched a lot but can only find HTML to plain text, not the other way around, I have email implementation in my app, thus need to send the content of email as HTML to the backend.
Edit 1: I have rich text that includes bold, italic, ordered/unordered list, underlined words.
If you are looking to convert NSAttributedString to String, here is the extension method you are looking for. Simply call yourAttributtedString.htmlString() and print it out.
extension NSAttributedString {
func htmlString() -> String? {
let documentAttributes = [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType]
do {
let htmlData = try self.data(from: NSMakeRange(0, self.length), documentAttributes:documentAttributes)
if let htmlString = String(data:htmlData, encoding:String.Encoding.utf8) {
return htmlString
}
}
catch {}
return nil
}
}
According to this post:
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
}