Convert HTML to NSAttributedString in iOS - iphone

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.

In iOS 7, UIKit added an initWithData:options:documentAttributes:error: method which can initialize an NSAttributedString 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)

There is a work-in-progress open source addition to NSAttributedString by Oliver Drobnik at Github. It uses NSScanner for HTML parsing.

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:

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)

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!).

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
}

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 }

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!")

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

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.

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

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.

Add this Extension and then use Text. after using this code we can use
custom size of our text.
extension Text {
init(html htmlString: String,
raw: Bool = false,
size: CGFloat? = nil,
fontFamily: String = "-apple-system") {
let fullHTML: String
if raw {
fullHTML = htmlString
} else {
var sizeCss = ""
if let size = size {
sizeCss = "font-size: \(size)px;"
}
fullHTML = """
<!doctype html>
<html>
<head>
<style>
body {
font-family: \(fontFamily);
\(sizeCss)
}
</style>
</head>
<body>
\(htmlString)
</body>
</html>
"""
}
let attributedString: NSAttributedString
if let data = fullHTML.data(using: .unicode),
let attrString = try? NSAttributedString(data: data,
options: [.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil) {
attributedString = attrString
} else {
attributedString = NSAttributedString()
}
self.init(attributedString)
}
init(_ attributedString: NSAttributedString) {
self.init("")
attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { (attrs, range, _) in
let string = attributedString.attributedSubstring(from: range).string
var text = Text(string)
if let font = attrs[.font] as? UIFont {
text = text.font(.init(font))
}
if let color = attrs[.foregroundColor] as? UIColor {
text = text.foregroundColor(Color(color))
}
if let kern = attrs[.kern] as? CGFloat {
text = text.kerning(kern)
}
if #available(iOS 14.0, *) {
if let tracking = attrs[.tracking] as? CGFloat {
text = text.tracking(tracking)
}
}
if let strikethroughStyle = attrs[.strikethroughStyle] as? NSNumber, strikethroughStyle != 0 {
if let strikethroughColor = (attrs[.strikethroughColor] as? UIColor) {
text = text.strikethrough(true, color: Color(strikethroughColor))
} else {
text = text.strikethrough(true)
}
}
if let underlineStyle = attrs[.underlineStyle] as? NSNumber,
underlineStyle != 0 {
if let underlineColor = (attrs[.underlineColor] as? UIColor) {
text = text.underline(true, color: Color(underlineColor))
} else {
text = text.underline(true)
}
}
if let baselineOffset = attrs[.baselineOffset] as? NSNumber {
text = text.baselineOffset(CGFloat(baselineOffset.floatValue))
}
self = self + text
}
}
}

The built in conversion always sets the text color to UIColor.black, even if you pass an attributes dictionary with .forgroundColor set to something else. To support DARK mode on iOS 13, try this version of the extension on NSAttributedString.
extension NSAttributedString {
internal convenience init?(html: String) {
guard
let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else { return nil }
let options : [DocumentReadingOptionKey : Any] = [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
]
guard
let string = try? NSMutableAttributedString(data: data, options: options,
documentAttributes: nil) else { return nil }
if #available(iOS 13, *) {
let colour = [NSAttributedString.Key.foregroundColor: UIColor.label]
string.addAttributes(colour, range: NSRange(location: 0, length: string.length))
}
self.init(attributedString: string)
}
}

Here is the Swift 5 version of Mobile Dan's answer:
public extension NSAttributedString {
convenience init?(_ html: String) {
guard let data = html.data(using: .unicode) else {
return nil
}
try? self.init(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
}
}

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 }

honoring font family, dynamic font I've concocted this abomination:
extension NSAttributedString
{
convenience fileprivate init?(html: String, font: UIFont? = Font.dynamic(style: .subheadline))
{
guard let data = html.data(using: String.Encoding.utf8, allowLossyConversion: true) else {
var totalString = html
/*
https://stackoverflow.com/questions/32660748/how-to-use-apples-new-san-francisco-font-on-a-webpage
.AppleSystemUIFont I get in font.familyName does not work
while -apple-system does:
*/
var ffamily = "-apple-system"
if let font = font {
let lLDBsucks = font.familyName
if !lLDBsucks.hasPrefix(".appleSystem") {
ffamily = font.familyName
}
totalString = "<style>\nhtml * {font-family: \(ffamily) !important;}\n </style>\n" + html
}
guard let data = totalString.data(using: String.Encoding.utf8, allowLossyConversion: true) else {
return nil
}
assert(Thread.isMainThread)
guard let attributedText = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) else {
return nil
}
let mutable = NSMutableAttributedString(attributedString: attributedText)
if let font = font {
do {
var found = false
mutable.beginEditing()
mutable.enumerateAttribute(NSAttributedString.Key.font, in: NSMakeRange(0, attributedText.length), options: NSAttributedString.EnumerationOptions(rawValue: 0)) { (value, range, stop) in
if let oldFont = value as? UIFont {
let newsize = oldFont.pointSize * 15 * Font.scaleHeruistic / 12
let newFont = oldFont.withSize(newsize)
mutable.addAttribute(NSAttributedString.Key.font, value: newFont, range: range)
found = true
}
}
if !found {
// No font was found - do something else?
}
mutable.endEditing()
// mutable.addAttribute(.font, value: font, range: NSRange(location: 0, length: mutable.length))
}
self.init(attributedString: mutable)
}
}
alternatively you can use the versions this was derived from and set
font on UILabel after setting attributedString
this will clobber the size and boldness encapsulated in the attributestring though
kudos for reading through all the answers up to here.
You are a very patient man woman or child.

A function to convert html to Attributed NSAttributedString that will adapt dynamic size + adapt accessibility for the text.
static func convertHtml(string: String?) -> NSAttributedString? {
guard let string = string else {return nil}
guard let data = string.data(using: .utf8) else {
return nil
}
do {
let attrStr = try NSAttributedString(data: data,
options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil)
let range = NSRange(location: 0, length: attrStr.length)
let str = NSMutableAttributedString(attributedString: attrStr)
str.enumerateAttribute(NSAttributedString.Key.font, in: NSMakeRange(0, str.length), options: .longestEffectiveRangeNotRequired) {
(value, range, stop) in
if let font = value as? UIFont {
let userFont = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title2)
let pointSize = userFont.withSize(font.pointSize)
let customFont = UIFont.systemFont(ofSize: pointSize.pointSize)
let dynamicText = UIFontMetrics.default.scaledFont(for: customFont)
str.addAttribute(NSAttributedString.Key.font,
value: dynamicText,
range: range)
}
}
str.addAttribute(NSAttributedString.Key.underlineStyle, value: 0, range: range)
return NSAttributedString(attributedString: str.attributedSubstring(from: range))
} catch {}
return nil
}
To use:
let htmToStringText = convertHtml(string: html)
self.bodyTextView.isEditable = false
self.bodyTextView.isAccessibilityElement = true
self.bodyTextView.adjustsFontForContentSizeCategory = true
self.bodyTextView.attributedText = htmToStringText
self.bodyTextView.accessibilityAttributedLabel = htmToStringText

Related

Get image thumbnails from Rumble?

I want to get a thumbnail image for videos from Rumble.
When getting images from Youtube I just do like this:
https://img.youtube.com/vi/f3ZccBBjmQg/0.jpg
Want to same from Rumble video url-
https://rumble.com/vxhedt-80000-matches-chain-reaction-domino-effect.html?mref=7ju1&mc=4w36m
I checked the rumble webpage of the link you provided. I am not sure if there is a smarter/faster way but here is a way to get the thumbnailUrl from their html code.
func getThumbnailFromRumble() {
let url = URL(string:"https://rumble.com/vxhedt-80000-matches-chain-reaction-domino-effect.html?mref=7ju1&mc=4w36m")!
URLSession.shared.dataTask(with: url){ data, response, error in
guard error == nil else { return }
guard let httpURLResponse = response as? HTTPURLResponse,
httpURLResponse.statusCode == 200,
let data = data else {
return
}
let str = String(data: data, encoding: .utf8) // get the htm response as string
let prefix = "\"thumbnailUrl\":"
let suffix = ",\"uploadDate\""
let matches = str?.match("(\(prefix)(...*)\(suffix))")
if let thumbnailUrlMatch = matches?.first?.first {
let thumbnailUrl = thumbnailUrlMatch
.replacingOccurrences(of: prefix, with: "") // remove prefix from urlstring
.replacingOccurrences(of: suffix, with: "") // remove suffix from urlstring
.replacingOccurrences(of: "\"", with: "") // remove escaping characters from urlstring
if let url = URL(string: thumbnailUrl),
let data = try? Data(contentsOf: url) {
let uiImage = UIImage(data: data)
// use the uiimage
}
}
}.resume()
}
I use this extension to get the necessary string part from the html response
extension String {
func match(_ regex: String) -> [[String]] {
let nsString = self as NSString
return (try? NSRegularExpression(pattern: regex, options: []))?.matches(in: self, options: [], range: NSMakeRange(0, nsString.length)).map { match in
(0..<match.numberOfRanges).map { match.range(at: $0).location == NSNotFound ? "" : nsString.substring(with: match.range(at: $0)) }
} ?? []
}
}
Feel free to improve the code.

How can I change <br> tag with line break in SwiftSoup? [duplicate]

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

Incorrect format after parsing string HTML in Swift

While parsing I use this code
func encode() -> String{
var newStr = String(utf8String: self.cString(using: .utf8)!)
newStr = newStr!.removingPercentEncoding
guard let data = String(utf8String: self.cString(using: .utf8)!)?.data(using: .utf8) else {
return newStr!
}
guard let attributedString = try? NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) else {
return newStr!
}
return attributedString.string
}
the problem is that it removes the \n.
So I do not display the text correctly
That's because you are using NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html
depending on what you are trying to accomplish, you may just ignore that fact or replace "\n" with something else in your String.

How to transfer HTML text into an attributed string without losing line breaks in Swift 3 [duplicate]

This question already has answers here:
HTML Format in UITextView
(8 answers)
Closed 5 years ago.
Please do not mark as duplicate. None of the existing questions solves not losing line breaks.
Given String: "Regular text becomes <b>bold with </b>\n\n<b><i>An italic</i></b>\n\n<b>Linebreak</b>"
I have two options:
let attrStr = try! NSAttributedString(
data: story.body.data(using: String.Encoding.unicode, allowLossyConversion: true)!,
options: [ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSFontAttributeName: Handler.shared.setFont(FontNames.sourceSerifProRegular, 25.0)],
documentAttributes: nil)
This option loses the font, size and the line breaks.
The following extension from this answer, keeps UILabel's font, but it loses the line breaks as well.
extension UILabel {
func _slpGetSize() -> CGSize? {
return (text as NSString?)?.size(attributes: [NSFontAttributeName: font])
}
func setHTMLFromString(htmlText: String) {
let modifiedFont = NSString(format:"<span style=\"font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%#</span>" as NSString, htmlText) as String
let attrStr = try! NSAttributedString(
data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue],
documentAttributes: nil)
self.attributedText = attrStr
}
}
label.setHTMLFromString(htmlText: story.body)
What am I missing? What do I need to do to keep the line breaks?
Help is very appreciated.
Try set numberOfLines property in the UILabel.
For that, you can count the number of break lines and set into numberOfLines.
extension UILabel {
func _slpGetSize() -> CGSize? {
return (text as NSString?)?.size(attributes: [NSFontAttributeName: font])
}
func setHTMLFromString(htmlText: String) {
let modifiedFont = NSString(format:"<span style=\"font-family: \(self.font!.fontName); font-size: \(self.font!.pointSize)\">%#</span>" as NSString, htmlText) as String
let attrStr = try! NSAttributedString(
data: modifiedFont.data(using: .unicode, allowLossyConversion: true)!,
options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue],
documentAttributes: nil)
self.attributedText = attrStr
self.numberOfLines = htmlText.components(separatedBy: "\n").count
}
}
I hope this example help you.

EXIF data read and write

I searched for getting the EXIF data from picture files and write them back for Swift. But I only could find predefied libs for different languages.
I also found references to "CFDictionaryGetValue", but which keys do I need to get the data? And how can I write it back?
I'm using this to get EXIF infos from an image file:
import ImageIO
let fileURL = theURLToTheImageFile
if let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil) {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
if let dict = imageProperties as? [String: Any] {
print(dict)
}
}
It gives you a dictionary containing various informations like the color profile - the EXIF info specifically is in dict["{Exif}"].
Swift 4
extension UIImage {
func getExifData() -> CFDictionary? {
var exifData: CFDictionary? = nil
if let data = self.jpegData(compressionQuality: 1.0) {
data.withUnsafeBytes {(bytes: UnsafePointer<UInt8>)->Void in
if let cfData = CFDataCreate(kCFAllocatorDefault, bytes, data.count) {
let source = CGImageSourceCreateWithData(cfData, nil)
exifData = CGImageSourceCopyPropertiesAtIndex(source!, 0, nil)
}
}
}
return exifData
}
}
Swift 5
extension UIImage {
func getExifData() -> CFDictionary? {
var exifData: CFDictionary? = nil
if let data = self.jpegData(compressionQuality: 1.0) {
data.withUnsafeBytes {
let bytes = $0.baseAddress?.assumingMemoryBound(to: UInt8.self)
if let cfData = CFDataCreate(kCFAllocatorDefault, bytes, data.count),
let source = CGImageSourceCreateWithData(cfData, nil) {
exifData = CGImageSourceCopyPropertiesAtIndex(source, 0, nil)
}
}
}
return exifData
}
}
You can use AVAssetExportSession to write metadata.
let asset = AVAsset(url: existingUrl)
let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)
exportSession?.outputURL = newURL
exportSession?.metadata = [
// whatever [AVMetadataItem] you want to write
]
exportSession?.exportAsynchronously {
// respond to file writing completion
}