I found this string extension somewhere on SO that allows me to turn html code into an attributed string:
func html2AttributedString() -> NSAttributedString {
return try! NSAttributedString(data: self.data(using: String.Encoding.unicode, allowLossyConversion: true)!, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil)
}
It worked fine in Swift 3, but with Swift 4, Xcode complains:
Cannot convert value of type 'NSAttributedString.DocumentAttributeKey' to expected dictionary key type 'NSAttributedString.DocumentReadingOptionKey'
How do I fix this?
You need to pass one of the available NSAttributedString DocumentType options:
Hypertext Markup Language (HTML) document.
static let html: NSAttributedString.DocumentType
Plain text document.
static let plain: NSAttributedString.DocumentType
Rich text format document.
static let rtf: NSAttributedString.DocumentType
Rich text format with attachments document.
static let rtfd: NSAttributedString.DocumentType
In this case you will need to pass the first one (html) NSAttributedString.DocumentType.html
So the extension updated to Swift 4 should look like this:
extension NSAttributedString {
convenience init(data: Data, documentType: DocumentType, encoding: String.Encoding = .utf8) throws {
try self.init(data: data,
options: [.documentType: documentType,
.characterEncoding: encoding.rawValue],
documentAttributes: nil)
}
convenience init(html data: Data) throws {
try self.init(data: data, documentType: .html)
}
convenience init(txt data: Data) throws {
try self.init(data: data, documentType: .plain)
}
convenience init(rtf data: Data) throws {
try self.init(data: data, documentType: .rtf)
}
convenience init(rtfd data: Data) throws {
try self.init(data: data, documentType: .rtfd)
}
}
extension StringProtocol {
var data: Data { return Data(utf8) }
var htmlToAttributedString: NSAttributedString? {
do {
return try .init(html: data)
} catch {
print("html error:", error)
return nil
}
}
var htmlDataToString: String? {
return htmlToAttributedString?.string
}
}
extension Data {
var htmlToAttributedString: NSAttributedString? {
do {
return try .init(html: self)
} catch {
print("html error:", error)
return nil
}
}
}
Playground Testing
let htmlString = "<style type=\"text/css\">#red{color:#F00}#green{color:#0F0}#blue{color: #00F; font-weight: Bold; font-size: 32}</style><span id=\"red\" >Red</span><span id=\"green\" > Green </span><span id=\"blue\">Blue</span>"
let htmlData = Data(htmlString.utf8)
htmlString.htmlToAttributedString
htmlData.htmlToAttributedString
Discussion The HTML importer should not be called from a background
thread (that is, the options dictionary includes documentType with a
value of html). It will try to synchronize with the main thread, fail,
and time out. Calling it from the main thread works (but can still
time out if the HTML contains references to external resources, which
should be avoided at all costs). The HTML import mechanism is meant
for implementing something like markdown (that is, text styles,
colors, and so on), not for general HTML import
Had this after automatic conversion to Swift 4. Was fixed by changing from:
NSMutableAttributedString(data: data,
options: [NSAttributedString.DocumentAttributeKey.documentType : NSAttributedString.DocumentType.html],
documentAttributes: nil)
to:
NSMutableAttributedString(data: data,
options: [.documentType : NSAttributedString.DocumentType.html],
documentAttributes: nil) {
This works for me:
let attrStr = try! NSAttributedString(
data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
options:[.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
If you don’t add
.characterEncoding: String.Encoding.utf8.rawValue
the app will crash.
swift 4 : I dont know why all the answers have compiler error for me. so use this extension:
extension String {
var html2AttributedString: NSAttributedString? {
do {
return try NSAttributedString(data: data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!,
options: [.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
} catch {
print("error: ", error)
return nil
}
}
var html2String: String {
return html2AttributedString?.string ?? ""
}
}
how to use ?
mylable.text = htmlVariable.html2String
For HTML string, NSAttributedString.DocumentType.html is the correct option.
Swift 4
extension String {
var utfData: Data? {
return self.data(using: .utf8)
}
var htmlAttributedString: NSAttributedString? {
guard let data = self.utfData else {
return nil
}
do {
return try NSAttributedString(data: data,
options: [
NSAttributedString.documentType: NSAttributedString.DocumentType.html,
NSAttributedString.characterEncoding: String.Encoding.utf8.rawValue
], documentAttributes: nil)
} catch {
print(error.localizedDescription)
return nil
}
}
}
Use NSAttributedString.DocumentType.html
NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html] , documentAttributes: nil)
I use NSAttributedStringKey and had similar error "Cannot convert value of type" on Swift 4. In case anyone using NSAttributedStringKey comes here looking for an answer, this is how I fixed:
let TextStroke: [NSAttributedStringKey : Any] = [
NSAttributedStringKey(rawValue: NSAttributedStringKey.strokeColor.rawValue) : UIColor.black,
NSAttributedStringKey(rawValue: NSAttributedStringKey.foregroundColor.rawValue) : UIColor.white,
NSAttributedStringKey(rawValue: NSAttributedStringKey.strokeWidth.rawValue) : -6.0,]
And this is how I add the attribute to the text:
myLabel.attributedText = NSAttributedString(string: myString, attributes: TextStroke)
Swift 4.x & 5.x
if let rtfPath = Bundle.main.url(forResource: "FileName", withExtension: "rtf") {
do {
let attributedString: NSAttributedString = try NSAttributedString(url: rtfPath, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil)
debugPrint(attributedString)
} catch {
print("Error while reading the file - \(error.localizedDescription)")
}
}
Getting NSAttributedString from file will be changed in Swift 3.x which is as below:
let attributedStringWithRtf: NSAttributedString = try NSAttributedString(url: rtfPath, options: [NSDocumentTypeDocumentAttribute:NSRTFTextDocumentType], documentAttributes: nil)
all the other code same as Swift 4.x & 5.x.
For more details please read the Apple document related to the NSAttributedStringDocumentType
Related
I read several posts and all suggested using the following extension, but it's not working:
extension String {
func htmlAttributedString() -> NSAttributedString? {
guard let data = self.data(using: .utf8) else {
return nil
}
return try? NSAttributedString(
data: data,
options: [.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil
)
}
}
This is how I use it:
At runtime I get this error:
I found this string extension somewhere on SO that allows me to turn html code into an attributed string:
func html2AttributedString() -> NSAttributedString {
return try! NSAttributedString(data: self.data(using: String.Encoding.unicode, allowLossyConversion: true)!, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil)
}
It worked fine in Swift 3, but with Swift 4, Xcode complains:
Cannot convert value of type 'NSAttributedString.DocumentAttributeKey' to expected dictionary key type 'NSAttributedString.DocumentReadingOptionKey'
How do I fix this?
You need to pass one of the available NSAttributedString DocumentType options:
Hypertext Markup Language (HTML) document.
static let html: NSAttributedString.DocumentType
Plain text document.
static let plain: NSAttributedString.DocumentType
Rich text format document.
static let rtf: NSAttributedString.DocumentType
Rich text format with attachments document.
static let rtfd: NSAttributedString.DocumentType
In this case you will need to pass the first one (html) NSAttributedString.DocumentType.html
So the extension updated to Swift 4 should look like this:
extension NSAttributedString {
convenience init(data: Data, documentType: DocumentType, encoding: String.Encoding = .utf8) throws {
try self.init(data: data,
options: [.documentType: documentType,
.characterEncoding: encoding.rawValue],
documentAttributes: nil)
}
convenience init(html data: Data) throws {
try self.init(data: data, documentType: .html)
}
convenience init(txt data: Data) throws {
try self.init(data: data, documentType: .plain)
}
convenience init(rtf data: Data) throws {
try self.init(data: data, documentType: .rtf)
}
convenience init(rtfd data: Data) throws {
try self.init(data: data, documentType: .rtfd)
}
}
extension StringProtocol {
var data: Data { return Data(utf8) }
var htmlToAttributedString: NSAttributedString? {
do {
return try .init(html: data)
} catch {
print("html error:", error)
return nil
}
}
var htmlDataToString: String? {
return htmlToAttributedString?.string
}
}
extension Data {
var htmlToAttributedString: NSAttributedString? {
do {
return try .init(html: self)
} catch {
print("html error:", error)
return nil
}
}
}
Playground Testing
let htmlString = "<style type=\"text/css\">#red{color:#F00}#green{color:#0F0}#blue{color: #00F; font-weight: Bold; font-size: 32}</style><span id=\"red\" >Red</span><span id=\"green\" > Green </span><span id=\"blue\">Blue</span>"
let htmlData = Data(htmlString.utf8)
htmlString.htmlToAttributedString
htmlData.htmlToAttributedString
Discussion The HTML importer should not be called from a background
thread (that is, the options dictionary includes documentType with a
value of html). It will try to synchronize with the main thread, fail,
and time out. Calling it from the main thread works (but can still
time out if the HTML contains references to external resources, which
should be avoided at all costs). The HTML import mechanism is meant
for implementing something like markdown (that is, text styles,
colors, and so on), not for general HTML import
Had this after automatic conversion to Swift 4. Was fixed by changing from:
NSMutableAttributedString(data: data,
options: [NSAttributedString.DocumentAttributeKey.documentType : NSAttributedString.DocumentType.html],
documentAttributes: nil)
to:
NSMutableAttributedString(data: data,
options: [.documentType : NSAttributedString.DocumentType.html],
documentAttributes: nil) {
This works for me:
let attrStr = try! NSAttributedString(
data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
options:[.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
If you don’t add
.characterEncoding: String.Encoding.utf8.rawValue
the app will crash.
swift 4 : I dont know why all the answers have compiler error for me. so use this extension:
extension String {
var html2AttributedString: NSAttributedString? {
do {
return try NSAttributedString(data: data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!,
options: [.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
} catch {
print("error: ", error)
return nil
}
}
var html2String: String {
return html2AttributedString?.string ?? ""
}
}
how to use ?
mylable.text = htmlVariable.html2String
For HTML string, NSAttributedString.DocumentType.html is the correct option.
Swift 4
extension String {
var utfData: Data? {
return self.data(using: .utf8)
}
var htmlAttributedString: NSAttributedString? {
guard let data = self.utfData else {
return nil
}
do {
return try NSAttributedString(data: data,
options: [
NSAttributedString.documentType: NSAttributedString.DocumentType.html,
NSAttributedString.characterEncoding: String.Encoding.utf8.rawValue
], documentAttributes: nil)
} catch {
print(error.localizedDescription)
return nil
}
}
}
Use NSAttributedString.DocumentType.html
NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html] , documentAttributes: nil)
I use NSAttributedStringKey and had similar error "Cannot convert value of type" on Swift 4. In case anyone using NSAttributedStringKey comes here looking for an answer, this is how I fixed:
let TextStroke: [NSAttributedStringKey : Any] = [
NSAttributedStringKey(rawValue: NSAttributedStringKey.strokeColor.rawValue) : UIColor.black,
NSAttributedStringKey(rawValue: NSAttributedStringKey.foregroundColor.rawValue) : UIColor.white,
NSAttributedStringKey(rawValue: NSAttributedStringKey.strokeWidth.rawValue) : -6.0,]
And this is how I add the attribute to the text:
myLabel.attributedText = NSAttributedString(string: myString, attributes: TextStroke)
Swift 4.x & 5.x
if let rtfPath = Bundle.main.url(forResource: "FileName", withExtension: "rtf") {
do {
let attributedString: NSAttributedString = try NSAttributedString(url: rtfPath, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.rtf], documentAttributes: nil)
debugPrint(attributedString)
} catch {
print("Error while reading the file - \(error.localizedDescription)")
}
}
Getting NSAttributedString from file will be changed in Swift 3.x which is as below:
let attributedStringWithRtf: NSAttributedString = try NSAttributedString(url: rtfPath, options: [NSDocumentTypeDocumentAttribute:NSRTFTextDocumentType], documentAttributes: nil)
all the other code same as Swift 4.x & 5.x.
For more details please read the Apple document related to the NSAttributedStringDocumentType
I have extension to convert html symbols to string:
extension String {
func convertHtmlSymbols() throws -> String? {
guard let data = data(using: .utf8) else { return nil }
return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil).string
}
}
This extension works good. But I need convert this extension to function in class "Converter":
class Converter{
func convertHtmlSymbols(data: String) throws -> String? {
guard let data = data(using: .utf8) else { return nil }
return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil).string
}
}
But I got this error:
error: Cannot call value of non-function type
In the String extension method,
guard let data = data(using: .utf8)
is a shortcut for
guard let data = self.data(using: .utf8)
i.e. the data(using:) method is called on self. In your class
you want to call it on the given string parameter (which also happens
to be called "data"), therefore it has to be
guard let data = data.data(using: .utf8)
Renaming the parameter can reduce the confusion!
As Martin R Suggested:
class Converter{
func convertHtmlSymbols(input: String) throws -> String? {
guard let data = input.data(using: .utf8) else { return nil }
return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil).string
}
}
The reason is
func data(using encoding: String.Encoding, allowLossyConversion: Bool = default) -> Data?
is a method used on Strings, in your String extension it is member of the String so it knows what to do with it, but in your Converter class it's not a member/property so it doesn't see it and can't use it.
See Apple Doc
I would do it like this:
class Converter{
static func convertHtmlSymbols(_ string: String) throws -> String? {
guard let data = string.data(using: .utf8) else { return nil }
return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil).string
}
}
And than call it like this:
try Converter.convertHtmlSymbols("your string here")
Try this:
class Converter{
func convertHtmlSymbols(data: String) throws -> String? {
return try? NSAttributedString(data: data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!, options: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType], documentAttributes: nil).string
}
}
I'm working on a simple RSS Reader app as a beginner project in Xcode. I currently have it set up that it parses the feed, and places the title, pub date, description and content and displays it in a WebView.
I recently decided to show the description (or a truncated version of the content) in the TableView used to select a post. However, when doing so:
cell.textLabel?.text = item.title?.uppercaseString
cell.detailTextLabel?.text = item.itemDescription //.itemDescription is a String
It shows the raw HTML of the post.
I would like to know how to convert the HTML into plain text for just the TableView's detailed UILabel.
Thanks!
You can add this extension to convert your html code to a regular string:
edit/update:
Discussion The HTML importer should not be called from a background
thread (that is, the options dictionary includes documentType with a
value of html). It will try to synchronize with the main thread, fail,
and time out. Calling it from the main thread works (but can still
time out if the HTML contains references to external resources, which
should be avoided at all costs). The HTML import mechanism is meant
for implementing something like markdown (that is, text styles,
colors, and so on), not for general HTML import.
Xcode 11.4 • Swift 5.2
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 { html2AttributedString?.string ?? "" }
}
extension StringProtocol {
var html2AttributedString: NSAttributedString? {
Data(utf8).html2AttributedString
}
var html2String: String {
html2AttributedString?.string ?? ""
}
}
cell.detailTextLabel?.text = item.itemDescription.html2String
Swift 4, Xcode 9
extension String {
var utfData: Data {
return Data(utf8)
}
var attributedHtmlString: NSAttributedString? {
do {
return try NSAttributedString(data: utfData, options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
],
documentAttributes: nil)
} catch {
print("Error:", error)
return nil
}
}
}
extension UILabel {
func setAttributedHtmlText(_ html: String) {
if let attributedText = html.attributedHtmlString {
self.attributedText = attributedText
}
}
}
Here is my suggested answer. Instead of extension, if you want to put inside function.
func decodeString(encodedString:String) -> NSAttributedString?
{
let encodedData = encodedString.dataUsingEncoding(NSUTF8StringEncoding)!
do {
return try NSAttributedString(data: encodedData, options: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType,NSCharacterEncodingDocumentAttribute:NSUTF8StringEncoding], documentAttributes: nil)
} catch let error as NSError {
print(error.localizedDescription)
return nil
}
}
And call that function and cast NSAttributedString to String
let attributedString = self.decodeString(encodedString)
let message = attributedString.string
Please test with this code for the detailTextLabel:
var attrStr = NSAttributedString(
data: item.itemDescription.dataUsingEncoding(NSUnicodeStringEncoding, allowLossyConversion: true),
options: [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil,
error: nil)
cell.detailTextLabel?.text = attrStr
Try this solution in swift3
extension String{
func convertHtml() -> NSAttributedString{
guard let data = data(using: .utf8) else { return NSAttributedString() }
do{
return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil)
}catch{
return NSAttributedString()
}
}
}
To use
self.lblValDesc.attributedText = str_postdescription.convertHtml()
Swift4.0 Extension
extension String {
var html2AttributedString: 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
}
}
}
i have used Danboz answer, only changed it to return a simple String (not a rich text string):
static func htmlToText(encodedString:String) -> String?
{
let encodedData = encodedString.dataUsingEncoding(NSUTF8StringEncoding)!
do
{
return try NSAttributedString(data: encodedData, options: [NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType,NSCharacterEncodingDocumentAttribute:NSUTF8StringEncoding], documentAttributes: nil).string
} catch let error as NSError {
print(error.localizedDescription)
return nil
}
}
for me, it works like a charm, thanks Danboz
let content = givenString // html included string
let attrStr = try! NSAttributedString(data: content.data(using: String.Encoding.unicode, allowLossyConversion: true)!,options: [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],documentAttributes: nil)
self.labelName.attributedText = attrStr
I am trying to display an HTML string in UITextView. Here is the key line:
self.postPage.attributedText = NSAttributedString(string: myHTMLString)
The problem I saw is that the <div> tags <img> tags are still showing... is there anything I am doing wrong?
extension String {
var html2String:String {
return NSAttributedString(data: dataUsingEncoding(NSUTF8StringEncoding)!, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil, error: nil)!.string
}
}
"<div>Testing<br></div><img src=\"http://....\">".html2String // "Testing\n"
Need to add try! if you use Xcode 7.0.1.
extension String {
var html2String:String {
return try! NSAttributedString(data: dataUsingEncoding(NSUTF8StringEncoding)!, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil).string
}
}
This function will convert all the HTML tags into NSAttributedString.
extension String{
func convertHtml() -> NSAttributedString{
guard let data = data(using: .utf8) else { return NSAttributedString() }
do{
return try NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
}catch{
return NSAttributedString()
}
}
}
Usage is like:
yourTextView.attributedText = yourHtmlString.convertHtml()