可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am using a instance of UIWebView
to process some text and color it correctly, it gives the result as HTML but rather than displaying it in the UIWebView
I want to display it using Core Text
with a NSAttributedString
.
I am able to create and draw the NSAttributedString
but I am unsure how I can convert and map the HTML into the attributed string.
I understand that under Mac OS X NSAttributedString
has a initWithHTML:
method but this was a Mac only addition and is not available for iOS.
I also know that there is a similar question to this but it had no answers, I though I would try again and see whether anyone has created a way to do this and if so, if they could share it.
回答1:
In iOS 7, UIKit added an initWithData:options:documentAttributes:error: method which can initialize an NSAtttributedString using HTML, eg:
[[NSAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
documentAttributes:nil error:nil];
In Swift:
let htmlData = NSString(string: details).data(using: String.Encoding.unicode.rawValue)
let options = [NSAttributedString.DocumentReadingOptionKey.documentType:
NSAttributedString.DocumentType.html]
let attributedString = try? NSMutableAttributedString(data: htmlData ?? Data(),
options: options,
documentAttributes: nil)
回答2:
There is a work-in-progress open source addition to NSAttributedString by Oliver Drobnik at Github. It uses NSScanner for HTML parsing.
回答3:
Creating an NSAttributedString from HTML must be done on the main thread!
Update: It turns out that NSAttributedString HTML rendering depends on WebKit under the hood, and must be run on the main thread or it will occasionally crash the app with a SIGTRAP.
New Relic crash log:
Below is an updated thread-safe Swift 2 String extension:
extension String {
func attributedStringFromHTML(completionBlock:NSAttributedString? ->()) {
guard let data = dataUsingEncoding(NSUTF8StringEncoding) else {
print(\"Unable to decode data from html string: \\(self)\")
return completionBlock(nil)
}
let options = [NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: NSNumber(unsignedInteger:NSUTF8StringEncoding)]
dispatch_async(dispatch_get_main_queue()) {
if let attributedString = try? NSAttributedString(data: data, options: options, documentAttributes: nil) {
completionBlock(attributedString)
} else {
print(\"Unable to create attributed string from html string: \\(self)\")
completionBlock(nil)
}
}
}
}
Usage:
let html = \"<center>Here is some <b>HTML</b></center>\"
html.attributedStringFromHTML { attString in
self.bodyLabel.attributedText = attString
}
Output:
回答4:
Swift initializer extension on NSAttributedString
My inclination was to add this as an extension to NSAttributedString
rather than String
. I tried it as a static extension and an initializer. I prefer the initializer which is what I\'ve included below.
Swift 4
internal convenience init?(html: String) {
guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else {
return nil
}
guard let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) else {
return nil
}
self.init(attributedString: attributedString)
}
Swift 3
extension NSAttributedString {
internal convenience init?(html: String) {
guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else {
return nil
}
guard let attributedString = try? NSMutableAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) else {
return nil
}
self.init(attributedString: attributedString)
}
}
Example
let html = \"<b>Hello World!</b>\"
let attributedString = NSAttributedString(html: html)
回答5:
This is a String
extension written in Swift to return a HTML string as NSAttributedString
.
extension String {
func htmlAttributedString() -> NSAttributedString? {
guard let data = self.dataUsingEncoding(NSUTF16StringEncoding, allowLossyConversion: false) else { return nil }
guard let html = try? NSMutableAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil) else { return nil }
return html
}
}
To use,
label.attributedText = \"<b>Hello</b> \\u{2022} babe\".htmlAttributedString()
In the above, I have purposely added a unicode \\u2022 to show that it renders unicode correctly.
A trivial: The default encoding that NSAttributedString
uses is NSUTF16StringEncoding
(not UTF8!).
回答6:
Swift 3.0 Xcode 8 Version
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: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil) else { return nil }
return html
}
回答7:
The only solution you have right now is to parse the HTML, build up some nodes with given point/font/etc attributes, then combine them together into an NSAttributedString. It\'s a lot of work, but if done correctly, can be reusable in the future.
回答8:
Made some modification on Andrew\'s solution and update the code to Swift 3:
This code now use UITextView as self
and able to inherit its original font, font size and text color
Note: toHexString()
is extension from here
extension UITextView {
func setAttributedStringFromHTML(_ htmlCode: String, completionBlock: @escaping (NSAttributedString?) ->()) {
let inputText = \"\\(htmlCode)<style>body { font-family: \'\\((self.font?.fontName)!)\'; font-size:\\((self.font?.pointSize)!)px; color: \\((self.textColor)!.toHexString()); }</style>\"
guard let data = inputText.data(using: String.Encoding.utf16) else {
print(\"Unable to decode data from html string: \\(self)\")
return completionBlock(nil)
}
DispatchQueue.main.async {
if let attributedString = try? NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil) {
self.attributedText = attributedString
completionBlock(attributedString)
} else {
print(\"Unable to create attributed string from html string: \\(self)\")
completionBlock(nil)
}
}
}
}
Example usage:
mainTextView.setAttributedStringFromHTML(\"<i>Hello world!</i>\") { _ in }
回答9:
Swift 4
- NSAttributedString convenience initializer
- Without extra guards
- throws error
extension NSAttributedString {
convenience init(htmlString html: String) throws {
try self.init(data: Data(html.utf8), options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
], documentAttributes: nil)
}
}
Usage
UILabel.attributedText = try? NSAttributedString(htmlString: \"<strong>Hello</strong> World!\")
回答10:
The above solution is correct.
[[NSAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUTF8StringEncoding]
options:@{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: @(NSUTF8StringEncoding)}
documentAttributes:nil error:nil];
But the app wioll crash if you are running it on ios 8.1,2 or 3.
To avoid the crash what you can do is : run this in a queue. So that it always be on main thread.
回答11:
Using of NSHTMLTextDocumentType is slow and it is hard to control styles. I suggest you to try my library which is called Atributika. It has its own very fast HTML parser. Also you can have any tag names and define any style for them.
Example:
let str = \"<strong>Hello</strong> World!\".style(tags:
Style(\"strong\").font(.boldSystemFont(ofSize: 15))).attributedString
label.attributedText = str
You can find it here https://github.com/psharanda/Atributika
回答12:
Swift 3:
Try this:
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: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
documentAttributes: nil) else { return nil }
return html
}
}
And for using:
let str = \"<h1>Hello bro</h1><h2>Come On</h2><h3>Go sis</h3><ul><li>ME 1</li><li>ME 2</li></ul> <p>It is me bro , remember please</p>\"
self.contentLabel.attributedText = str.htmlAttributedString()
回答13:
Helpful Extensions
Inspired by this thread, a pod, and Erica Sadun\'s ObjC example in iOS Gourmet Cookbook p.80, I wrote an extension on String
and on NSAttributedString
to go back and forth between HTML plain-strings and NSAttributedStrings and vice versa -- on GitHub here, which I have found helpful.
The signatures are (again, full code in a Gist, link above):
extension NSAttributedString {
func encodedString(ext: DocEXT) -> String?
static func fromEncodedString(_ eString: String, ext: DocEXT) -> NSAttributedString?
static func fromHTML(_ html: String) -> NSAttributedString? // same as above, where ext = .html
}
extension String {
func attributedString(ext: DocEXT) -> NSAttributedString?
}
enum DocEXT: String { case rtfd, rtf, htm, html, txt }