Skip to content

Instantly share code, notes, and snippets.

@globulus
Created May 27, 2021 07:08
Show Gist options
  • Save globulus/82c698496465ad81066f05f45e1d7c2e to your computer and use it in GitHub Desktop.
Save globulus/82c698496465ad81066f05f45e1d7c2e to your computer and use it in GitHub Desktop.
SwiftUI Text with HTML via NSAttributedString
// full recipe at https://swiftuirecipes.com/blog/swiftui-text-with-html-via-nsattributedstring
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
}
}
}
@andredewaard
Copy link

andredewaard commented Dec 21, 2021

Cant get this to work. Keep getting this error multiple times and will not render anything.

=== AttributeGraph: cycle detected through attribute 900936 ===
=== AttributeGraph: cycle detected through attribute 901772 ===
=== AttributeGraph: cycle detected through attribute 901772 ===
=== AttributeGraph: cycle detected through attribute 900936 ===
=== AttributeGraph: cycle detected through attribute 900936 ===
=== AttributeGraph: cycle detected through attribute 900936 ===
.......

Would be great if i get this to work.

I get the same error with the new SwiftUI 3 version

if let data = fullHTML.data(using: .unicode),
   let nsAttrString = try? NSAttributedString(data: data,
                                              options: [.documentType: NSAttributedString.DocumentType.html],
                                              documentAttributes: nil) {
   Text(AttributedString(nsAttrString))
}

@gordan-glavas-codecons
Copy link

@andredewaard AttributeGraph: cycle detected is an internal SwiftUI bug (not an error, a bug) and can't be worked around. It likely means that your entire view hierarchy is structured in a way that SwiftUI can't support (even though it should).

@advienncurtiz
Copy link

Somehow it crashes on Line 33 when putting inside a ForEach

  ForEach(contextTexts, id: \.self) { string in
                        Spacer(minLength: 8)
                        VStack(alignment: .leading) {
                            
                            Text(html: contextTexts, raw: false, size: 16)
                                .frame(maxWidth: .infinity, alignment: .leading)
                            
                        
                                              
                        }

                    }

@advienncurtiz
Copy link

advienncurtiz commented Jun 16, 2023

var contextTexts: [String] = [
"<p>Test</p>","<p>Test</p>"

]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment