I'm using this way and it works fine, but has a slowness fallback because of the usage of NSHTMLTextDocumentType as i did my research
do {
let attributedOptions:[String: Any] = [
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue]
let date = html.data(using: String.Encoding.utf8, allowLossyConversion: true)!
return try NSAttributedString(data: data, options: attributedOptions , documentAttributes: nil)
} catch let error as NSError {
print("htmo2String \(error)")
}
any ideas how to do it faster or another efficient way to do it!
Maybe you can execute the parse code on a queue...
func parse(_ html: String, completionHandler: #escaping (_ attributedText: NSAttributedString?) -> (Void)) -> Void
{
let htmlData = text.data(using: String.Encoding.utf8, allowLossyConversion: false)
let options: [String: AnyObject] = [
NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType as AnyObject
]
completionHandler(try? NSAttributedString(data: htmlData!, options: options, documentAttributes: nil))
}
And now call the function and wait from response...
let queue: DispatchQueue = DispatchQueue(label: "com.yourcompany.Process./html_converter")
queue.async
{
parse("<p>¡Hola mundo</p>", completionHandler: { (attributtedString: NSAttributedString?) -> (Void) in
if let attributtedString = attributtedString
{
DispatchQueue.main.async
{
print("str:: \(attributtedString)")
}
}
})
}
Did you try o use a UIWebView to present HTML content ?
You can display HTML from a string or from a URL, as you prefer.
Here is an example to display HTML from a string :
string sHTMLContent = "<html><head><body><p>Hello World</p></body></head></html>";
m_WebView.LoadHtmlString(sHTMLContent , null);
Then you can set the size of your Webview to be equal to your textview with constraints. The webview will be scrollable automatically if needed.
Final string extension to show html string efficiently with #Adolfo async idea
with the ability to change font and color ^_^
extension String {
func html2StringAsync(_ fontSize: CGFloat? = nil, color: UIColor? = nil, completionBlock:#escaping (NSAttributedString) ->()) {
let fontSize = fontSize ?? 10
let fontColor = color ?? UIColor.black
let font = "Avenir !important"
let html = "<div style=\"font-family:\(font); font-size:\(fontSize)pt; color:\(fontColor.hexString);\">" + self + "</div>"
if let data = html.data(using: String.Encoding.utf8, allowLossyConversion: true){
DispatchQueue.main.async {
do {
let attributedOptions:[String: Any] = [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue]
let attrStr = try NSAttributedString(data: data, options: attributedOptions , documentAttributes: nil)
completionBlock(attrStr)
} catch let error as NSError {
print("htmo2String \(error)")
}
}
}else{
completionBlock(NSAttributedString(string: self))
}
}
}
I want to be able to edit the HTML I get from my api request, changing the font and increasing among other things.
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
var dataString:String = NSString(data: data, encoding: NSUTF8StringEncoding)! as String
dispatch_async(dispatch_get_main_queue()) {
var attributedString:NSAttributedString = NSAttributedString(data: data, options:[NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding], documentAttributes: nil, error: nil)!
self.textView.attributedText = attributedString
}
}
To access the properties correctly you should be using an NSMutableAttributeString.
Change your String instantiation to this:
var attributedString: NSMutableAttributedString = NSMutableAttributedString(data: data, options:[NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding], documentAttributes: nil, error: nil)!
And add an attribute like so:
mutableString.addAttribute(NSFontAttributeName, value: UIFont(name: "Avenir", size: 18.0)!, range: NSRange(location:2,length:4))
This is just an example, you can create your own attributes to suit your needs.
I am trying to get a snippet of text that is formatted in html to display nicely on an iPhone in a UITableViewCell.
So far I have this:
NSError* error;
NSString* source = #"<strong>Nice</strong> try, Phil";
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithData:[source dataUsingEncoding:NSUTF8StringEncoding]
options:#{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]}
documentAttributes:nil error:&error];
This kind of works. I get some text that has 'Nice' in bold! But... it also sets the font to be Times Roman! This is not the font face I want.
I am thinking I need to set something in the documentAttributes, but, I can't find any examples anywhere.
Swift 2 version, based on the answer given by Javier Querol
extension UILabel {
func setHTMLFromString(text: String) {
let modifiedFont = NSString(format:"<span style=\"font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%#</span>", text) as String
let attrStr = try! NSAttributedString(
data: modifiedFont.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)!,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding],
documentAttributes: nil)
self.attributedText = attrStr
}
}
Swift 3.0 and iOS 9+
extension UILabel {
func setHTMLFromString(htmlText: String) {
let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%#</span>", htmlText)
let attrStr = try! NSAttributedString(
data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue],
documentAttributes: nil)
self.attributedText = attrStr
}
}
Swift 5 and iOS 11+
extension UILabel {
func setHTMLFromString(htmlText: String) {
let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%#</span>", htmlText)
let attrStr = try! NSAttributedString(
data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue],
documentAttributes: nil)
self.attributedText = attrStr
}
}
#import "UILabel+HTML.h"
#implementation UILabel (HTML)
- (void)jaq_setHTMLFromString:(NSString *)string {
string = [string stringByAppendingString:[NSString stringWithFormat:#"<style>body{font-family: '%#'; font-size:%fpx;}</style>",
self.font.fontName,
self.font.pointSize]];
self.attributedText = [[NSAttributedString alloc] initWithData:[string dataUsingEncoding:NSUnicodeStringEncoding]
options:#{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: #(NSUTF8StringEncoding)}
documentAttributes:nil
error:nil];
}
#end
This way you don't need to specify which font you want, it will take the label font and size.
I actually found a working solution to this problem:
Changing the font in your HTML response string before it gets parsed.
NSString *aux = [NSString stringWithFormat:#"<span style=\"font-family: YOUR_FONT_NAME; font-size: SIZE\">%#</span>", htmlResponse];
Example:
NSString *aux = [NSString stringWithFormat:#"<span style=\"font-family: HelveticaNeue-Thin; font-size: 17\">%#</span>", [response objectForKey:#"content"]];
Swift version:
let aux = "<span style=\"font-family: YOUR_FONT_NAME; font-size: SIZE\">\(htmlResponse)</span>"
A more generic approach is to look at the font traits while enumerating, and create a font with the same traits (bold, italic, etc.):
extension NSMutableAttributedString {
/// Replaces the base font (typically Times) with the given font, while preserving traits like bold and italic
func setBaseFont(baseFont: UIFont, preserveFontSizes: Bool = false) {
let baseDescriptor = baseFont.fontDescriptor
let wholeRange = NSRange(location: 0, length: length)
beginEditing()
enumerateAttribute(.font, in: wholeRange, options: []) { object, range, _ in
guard let font = object as? UIFont else { return }
// Instantiate a font with our base font's family, but with the current range's traits
let traits = font.fontDescriptor.symbolicTraits
guard let descriptor = baseDescriptor.withSymbolicTraits(traits) else { return }
let newSize = preserveFontSizes ? descriptor.pointSize : baseDescriptor.pointSize
let newFont = UIFont(descriptor: descriptor, size: newSize)
self.removeAttribute(.font, range: range)
self.addAttribute(.font, value: newFont, range: range)
}
endEditing()
}
}
Figured it out. Bit of a bear, and maybe not the best answer.
This code will go through all the font changes. I know that it is using "Times New Roman" and "Times New Roman BoldMT" for the fonts.
But regardless, this will find the bold fonts and let me reset them. I can also reset the size while I'm at it.
I honestly hope/think there is a way to set this up at parse time, but I can't find it if there is.
- (void)changeFont:(NSMutableAttributedString*)string
{
NSRange range = (NSRange){0,[string length]};
[string enumerateAttribute:NSFontAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id value, NSRange range, BOOL *stop) {
UIFont* currentFont = value;
UIFont *replacementFont = nil;
if ([currentFont.fontName rangeOfString:#"bold" options:NSCaseInsensitiveSearch].location != NSNotFound) {
replacementFont = [UIFont fontWithName:#"HelveticaNeue-CondensedBold" size:25.0f];
} else {
replacementFont = [UIFont fontWithName:#"HelveticaNeue-Thin" size:25.0f];
}
[string addAttribute:NSFontAttributeName value:replacementFont range:range];
}];
}
Swift 4+ update of UILabel extension
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
}
}
iOS 9+
extension UILabel {
func setHTMLFromString(htmlText: String) {
let modifiedFont = NSString(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%#</span>" as NSString, htmlText) as String
//process collection values
let attrStr = try! NSAttributedString(
data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
options: [NSAttributedString.DocumentReadingOptionKey.documentType:NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
self.attributedText = attrStr
}
}
Yes, there is an easier solution. Set the font in the html source!
NSError* error;
NSString* source = #"<strong>Nice</strong> try, Phil";
source = [source stringByAppendingString:#"<style>strong{font-family: 'Avenir-Roman';font-size: 14px;}</style>"];
NSMutableAttributedString* str = [[NSMutableAttributedString alloc] initWithData:[source dataUsingEncoding:NSUTF8StringEncoding]
options:#{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: [NSNumber numberWithInt:NSUTF8StringEncoding]}
documentAttributes:nil error:&error];
Hope this helps.
The answers above all work OK if you're doing the conversion at the same time as creating the NSAttributedString. But I think a better solution, which works on the string itself and therefore doesn't need access to the input, is the following category:
extension NSMutableAttributedString
{
func convertFontTo(font: UIFont)
{
var range = NSMakeRange(0, 0)
while (NSMaxRange(range) < length)
{
let attributes = attributesAtIndex(NSMaxRange(range), effectiveRange: &range)
if let oldFont = attributes[NSFontAttributeName]
{
let newFont = UIFont(descriptor: font.fontDescriptor().fontDescriptorWithSymbolicTraits(oldFont.fontDescriptor().symbolicTraits), size: font.pointSize)
addAttribute(NSFontAttributeName, value: newFont, range: range)
}
}
}
}
Use as:
let desc = NSMutableAttributedString(attributedString: *someNSAttributedString*)
desc.convertFontTo(UIFont.systemFontOfSize(16))
Works on iOS 7+
Improving on Victor's solution, including color:
extension UILabel {
func setHTMLFromString(text: String) {
let modifiedFont = NSString(format:"<span style=\"color:\(self.textColor.toHexString());font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%#</span>", text) as String
let attrStr = try! NSAttributedString(
data: modifiedFont.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true)!,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSUTF8StringEncoding],
documentAttributes: nil)
self.attributedText = attrStr
}
}
For this to work you will also need YLColor.swift of the uicolor to hex conversion https://gist.github.com/yannickl/16f0ed38f0698d9a8ae7
Using of NSHTMLTextDocumentType is slow and hard to control styles. I suggest you to try my library which is called Atributika. It has its own very fast parser. Also you can have any tag names and define any style for them.
Example:
let str = "<strong>Nice</strong> try, Phil".style(tags:
Style("strong").font(.boldSystemFont(ofSize: 15))).attributedString
label.attributedText = str
You can find it here https://github.com/psharanda/Atributika
Joining together everyone's answers, I made two extensions that allow setting a label with html text. Some answers above did not correctly interpret the font family in the attributed strings. Others were incomplete for my needs or failed in other ways. Let me know if there's anything you'd like me to improve on.
I hope this helps someone.
extension UILabel {
/// Sets the label using the supplied html, using the label's font and font size as a basis.
/// For predictable results, using only simple html without style sheets.
/// See https://stackoverflow.com/questions/19921972/parsing-html-into-nsattributedtext-how-to-set-font
///
/// - Returns: Whether the text could be converted.
#discardableResult func setAttributedText(fromHtml html: String) -> Bool {
guard let data = html.data(using: .utf8, allowLossyConversion: true) else {
print(">>> Could not create UTF8 formatted data from \(html)")
return false
}
do {
let mutableText = try NSMutableAttributedString(
data: data,
options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
mutableText.replaceFonts(with: font)
self.attributedText = mutableText
return true
} catch (let error) {
print(">>> Could not create attributed text from \(html)\nError: \(error)")
return false
}
}
}
extension NSMutableAttributedString {
/// Replace any font with the specified font (including its pointSize) while still keeping
/// all other attributes like bold, italics, spacing, etc.
/// See https://stackoverflow.com/questions/19921972/parsing-html-into-nsattributedtext-how-to-set-font
func replaceFonts(with font: UIFont) {
let baseFontDescriptor = font.fontDescriptor
var changes = [NSRange: UIFont]()
enumerateAttribute(.font, in: NSMakeRange(0, length), options: []) { foundFont, range, _ in
if let htmlTraits = (foundFont as? UIFont)?.fontDescriptor.symbolicTraits,
let adjustedDescriptor = baseFontDescriptor.withSymbolicTraits(htmlTraits) {
let newFont = UIFont(descriptor: adjustedDescriptor, size: font.pointSize)
changes[range] = newFont
}
}
changes.forEach { range, newFont in
removeAttribute(.font, range: range)
addAttribute(.font, value: newFont, range: range)
}
}
}
Thanks for the answers, I really liked the extension but I have not converted to swift yet. For those old schoolers still in Objective-C this should help a little :D
-(void) setBaseFont:(UIFont*)font preserveSize:(BOOL) bPreserve {
UIFontDescriptor *baseDescriptor = font.fontDescriptor;
[self enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, [self length]) options:0 usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
UIFont *font = (UIFont*)value;
UIFontDescriptorSymbolicTraits traits = font.fontDescriptor.symbolicTraits;
UIFontDescriptor *descriptor = [baseDescriptor fontDescriptorWithSymbolicTraits:traits];
UIFont *newFont = [UIFont fontWithDescriptor:descriptor size:bPreserve?baseDescriptor.pointSize:descriptor.pointSize];
[self removeAttribute:NSFontAttributeName range:range];
[self addAttribute:NSFontAttributeName value:newFont range:range];
}]; }
Happy Coding!
--Greg Frame
Here is an extension for NSString that returns an NSAttributedString using Objective-C.
It correctly handles a string with HTML tags and sets the desired Font and Font color while preserving HTML tags including BOLD, ITALICS...
Best of all it does not rely on any HTML markers to set the font attributes.
#implementation NSString (AUIViewFactory)
- (NSAttributedString*)attributedStringFromHtmlUsingFont:(UIFont*)font fontColor:(UIColor*)fontColor
{
NSMutableAttributedString* mutableAttributedString = [[[NSAttributedString alloc] initWithData:[self dataUsingEncoding:NSUTF8StringEncoding] options:#{NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute : #(NSUTF8StringEncoding)} documentAttributes:nil error:nil] mutableCopy]; // parse text with html tags into a mutable attributed string
[mutableAttributedString beginEditing];
// html tags cause font ranges to be created, for example "This text is <b>bold</b> now." creates three font ranges: "This text is " , "bold" , " now."
[mutableAttributedString enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, mutableAttributedString.length) options:0 usingBlock:^(id value, NSRange range, BOOL* stop)
{ // iterate every font range, change every font to new font but preserve symbolic traits such as bold and italic (underline and strikethorugh are preserved automatically), set font color
if (value)
{
UIFont* oldFont = (UIFont*)value;
UIFontDescriptor* fontDescriptor = [font.fontDescriptor fontDescriptorWithSymbolicTraits:oldFont.fontDescriptor.symbolicTraits];
UIFont* newFont = [UIFont fontWithDescriptor:fontDescriptor size:font.pointSize];
[mutableAttributedString removeAttribute:NSFontAttributeName range:range]; // remove the old font attribute from this range
[mutableAttributedString addAttribute:NSFontAttributeName value:newFont range:range]; // add the new font attribute to this range
[mutableAttributedString addAttribute:NSForegroundColorAttributeName value:fontColor range:range]; // set the font color for this range
}
}];
[mutableAttributedString endEditing];
return mutableAttributedString;
}
#end
Swift 5 Solution for UILabel and UITextView
extension UITextView {
func setHTMLFromString(htmlText: String) {
let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%#</span>", htmlText)
let attrStr = try! NSAttributedString(
data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue],
documentAttributes: nil)
self.attributedText = attrStr
}
}
extension UILabel {
func setHTMLFromString(htmlText: String) {
let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', 'HelveticaNeue'; font-size: \(self.font!.pointSize)\">%#</span>", htmlText)
let attrStr = try! NSAttributedString(
data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue],
documentAttributes: nil)
self.attributedText = attrStr
}
}
Usage for UILabel
self.label.setHTMLFromString(htmlText: htmlString)
Usage for UITextView
self.textView.setHTMLFromString(htmlText: htmlString)
Output
Swift 3 String extension including a nil font. The property without font is taken from other SO question, do not remember which one :(
extension String {
var html2AttributedString: NSAttributedString? {
guard let data = data(using: .utf8) else {
return nil
}
do {
return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil)
}
catch {
print(error.localizedDescription)
return nil
}
}
public func getHtml2AttributedString(font: UIFont?) -> NSAttributedString? {
guard let font = font else {
return html2AttributedString
}
let modifiedString = "<style>body{font-family: '\(font.fontName)'; font-size:\(font.pointSize)px;}</style>\(self)";
guard let data = modifiedString.data(using: .utf8) else {
return nil
}
do {
return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil)
}
catch {
print(error)
return nil
}
}
}
Swift Solution
The below approach works. You can very well provide the font family, font size, and color in this approach. Feel free to suggest changes or any better way of doing this.
extension UILabel {
func setHTMLFromString(htmlText: String,fontFamily:String,fontColor:String) {
let modifiedFont = String(format:"<span style=\"font-family: '-apple-system', \(fontFamily); font-size: \(self.font!.pointSize); color: \(fontColor) ; \">%#</span>", htmlText)
do{
if let valData = modifiedFont.data(using: .utf8){
let attrStr = try NSAttributedString(data: valData, options: [NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html.rawValue], documentAttributes: nil)
self.attributedText = attrStr
}
}catch{
print("Conversion failed with \(error)")
self.attributedText = nil
}
}
Actually, an even easier and cleanr way exists. Just set the font after parsing the HTML:
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
options:#{
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: #(NSUTF8StringEncoding)}
documentAttributes:nil error:nil];
[text addAttributes:#{NSFontAttributeName: [UIFont fontWithName:#"Lato-Regular" size:20]} range:NSMakeRange(0, text.length)];