How to show HTML or Markdown in a SwiftUI Text?
Asked Answered
K

13

50

How can I set a SwiftUI Text to display rendered HTML or Markdown?

Something like this:

Text(HtmlRenderedString(fromString: "<b>Hi!</b>"))

or for MD:

Text(MarkdownRenderedString(fromString: "**Bold**"))

Perhaps I need a different View?

Keating answered 4/7, 2019 at 18:9 Comment(0)
P
4

Text can just display Strings. You can use a UIViewRepresentable with an UILabel and attributedText.

Probably attributedText text support will come later for SwiftUI.Text.

Ptolemy answered 4/7, 2019 at 23:32 Comment(1)
yes, but link could not clickable, what wrong ?Flood
D
44

iOS 15

Text now supports basic Markdown!

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Regular")
            Text("*Italics*")
            Text("**Bold**")
            Text("~Strikethrough~")
            Text("`Code`")
            Text("[Link](https://apple.com)")
            Text("***[They](https://apple.com) ~are~ `combinable`***")
        }
    }
}

Result:

Markdown result


Update: If you store markdown as a String, it won't render — instead, set the type to be LocalizedStringKey.

struct ContentView: View {
    @State var textWithMarkdown: LocalizedStringKey = "***[They](https://apple.com) ~are~ `combinable`***"

    var body: some View {
        Text(textWithMarkdown)
    }
}

Result:

Markdown rendered

Depraved answered 8/6, 2021 at 14:10 Comment(5)
Great! But it does not work, if you put a string containing markdowns in a variable! Is there a solution or is it just a bug to file?Placebo
@Placebo most likely a bug (I remember someone asking about this in the WWDC21 digital lounges). See my edit for a workaroundDepraved
@Depraved Markdown only working for string literals is intended, see this tweet.Burthen
To work around a stored string not being converted to Markdown, instead of converting to an AttributedString, you can simply create a LocalizedStringKey from the string value and initialize the Text view with that LocalizedStringKey. i.e. Text(LocalizedStringKey(textWithMarkdown))Stacy
I solved this by just using Text(.init(yourTextVariable)). No need for a markdownToAttributed function. See answer: https://mcmap.net/q/265716/-how-to-make-hyperlinks-in-swiftuiBlistery
I
31

If you don't need to specifically use a Text view. You can create a UIViewRepresentable that shows a WKWebView and simple call loadHTMLString().

import WebKit
import SwiftUI

struct HTMLStringView: UIViewRepresentable {
    let htmlContent: String

    func makeUIView(context: Context) -> WKWebView {
        return WKWebView()
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.loadHTMLString(htmlContent, baseURL: nil)
    }
}

In your body simple call this object like this:

import SwiftUI

struct Test: View {
    var body: some View {
        VStack {
            Text("Testing HTML Content")
            Spacer()
            HTMLStringView(htmlContent: "<h1>This is HTML String</h1>")
            Spacer()
        }
    }
}

struct Test_Previews: PreviewProvider {
    static var previews: some View {
        Test()
    }
}
Intertwine answered 11/1, 2020 at 22:38 Comment(8)
My requirement is to display a html data into the along with other text data of a list of items using swiftui. However, whenever I am trying to do the above code, i don't see any view. Could you please let me know what could the reason.Polivy
Hi @DJ, It's working on my project, I have updated my answer with a complete SwiftUI File. I mean, you will not see nothing on the "preview screen" but if you press play will work. Let me know if I've answered your question.Intertwine
Thanks for your response, it worked for it as well but not within the list . I believe this may be an issue with the sizing within the list.I will try to investigate it further.Polivy
@Polivy I tried with UIViewRepresentable attributed multiline text. I am able to get attributed and multiline text Label for setting preferredMaxLayoutWidth from GeometryReader width. but issue with list item sizing text getting overlap on other item. Please add answer if you find the solution, Thanks in Advance.Pokeberry
Please try with my other answer. https://mcmap.net/q/267498/-how-to-show-html-or-markdown-in-a-swiftui-textIntertwine
Perhaps the same as DJ, I have a problem with this in a ScrollView. Plus, there's a latency of loading (I'm using a local file).Cot
See changes here. That's fixing for me. developer.apple.com/forums/thread/653935Cot
After the implementation of changes by @ChrisPrince , you may use this link of stackoverflow to set the correct font size. https://mcmap.net/q/267592/-the-font-looks-like-smaller-in-wkwebview-than-in-uiwebviewCalamondin
I
16

Since iOS 15, Text can have an AttributedString parameter.

No UIViewRepresentable necessary

Since NSAttributedString can be created from HTML, the process is straight forward:

import SwiftUI

@available(iOS 15, *)
struct TestHTMLText: View {
    var body: some View {
        let html = "<h1>Heading</h1> <p>paragraph.</p>"

        if let nsAttributedString = try? NSAttributedString(data: Data(html.utf8), options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil),
           let attributedString = try? AttributedString(nsAttributedString, including: \.uiKit) {
            Text(attributedString)
        } else {
            // fallback...
            Text(html)
        }
    }
}

@available(iOS 15, *)
struct TestHTMLText_Previews: PreviewProvider {
    static var previews: some View {
        TestHTMLText()
    }
}

The code renders this:

Rendered HTML example

Be aware that NSAttributedString only works in main task. That's no problem in the code above, but if you move things in a model, you have to take care.

Inclined answered 28/1, 2023 at 8:54 Comment(3)
you can't apply SwiftUI parameters to the resulting Text though. For example .fontMelosa
This solution was the only one that would work and allowed me to use other languages. The only modification needed was to change Data(html.utf8) -to- html.data(using: .utf16)!Thunderstone
However, there is a missing "newline" between the heading and the paragraph.Castellano
I
11

Since I have found another solution I would like to share it with you.

Create a new View Representable

struct HTMLText: UIViewRepresentable {

   let html: String
    
   func makeUIView(context: UIViewRepresentableContext<Self>) -> UILabel {
        let label = UILabel()
        DispatchQueue.main.async {
            let data = Data(self.html.utf8)
            if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
                label.attributedText = attributedString
            }
        }

        return label
    }
    
    func updateUIView(_ uiView: UILabel, context: Context) {}
}

And use it later like this:

HTMLText(html: "<h1>Your html string</h1>")
Intertwine answered 9/6, 2020 at 11:38 Comment(5)
how to increase font size?Virnelli
Hi @DiNerd, in the parameter "options:" of the NSAttributedString you should add a new option for the font, like this: NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .font: UIFont.boldSystemFont(ofSize: 36)], documentAttributes: nil)Intertwine
Who do you use when text is not fitting in one line? I added this lines, but it did not work: label.lineBreakMode = .byWordWrapping, label.numberOfLines = 0Williswillison
Hi @Williswillison check out this answer i think could help https://mcmap.net/q/267593/-how-can-i-get-text-to-wrap-in-a-uilabel-via-uiviewrepresentable-without-having-a-fixed-widthIntertwine
This is great thank you! I found an issue with the width of the label, it was expanding horizontally and not vertically. It turned out it's because the label was inside a ScrollView. The answer here helped fixing this if anyone has the same issue: https://mcmap.net/q/267594/-size-a-uilabel-in-swiftui-via-uiviewrepresentable-like-text-to-wrap-multiple-linesImpervious
C
7

You can try to use the package https://github.com/iwasrobbed/Down, generate HTML or MD from you markdown string, then create a custom UILabel subclass and make it available to SwiftUI like in the following example:

struct TextWithAttributedString: UIViewRepresentable {

    var attributedString: NSAttributedString

    func makeUIView(context: Context) -> ViewWithLabel {
        let view = ViewWithLabel(frame: .zero)
        return view
    }

    func updateUIView(_ uiView: ViewWithLabel, context: Context) {
        uiView.setString(attributedString)
    }
}

class ViewWithLabel : UIView {
    private var label = UILabel()

    override init(frame: CGRect) {
        super.init(frame:frame)
        self.addSubview(label)
        label.numberOfLines = 0
        label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setString(_ attributedString:NSAttributedString) {
        self.label.attributedText = attributedString
    }

    override var intrinsicContentSize: CGSize {
        label.sizeThatFits(CGSize(width: UIScreen.main.bounds.width - 50, height: 9999))
    }
}

I have kind of success with that but cannot get the frame of the label subclass right. Maybe I need to use GeometryReader for that.

Coomer answered 10/12, 2019 at 12:55 Comment(6)
Could you please give an example about how to use your code? I tried this with no success: TextWithAttributedString(attributedString: DownView(frame: .zero, markdownString: "").accessibilityAttributedValue!) .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)Keim
Can you please let us know how do we call this ? Can we just say TextWithAttributedString(attributedString:"<div>Hello check</div>")Polivy
Yes it was intended to call it using TextWithAttributedString(attributedString:"# Hello SwiftUI") but in the meantime I switched to another approach wich actually displays something but is also not optimal yet. If I make real progress I'll post a new answer here.Coomer
@Coomer - I tried with UIViewRepresentable attributed multiline text. I am able to get attributed and multiline text Label. Setting label's preferredMaxLayoutWidth from GeometryReader width. But issue with list item sizing text getting overlap on other item. Please add answer if you find the solution, Thanks in Advance.Pokeberry
@Coomer this doesnt convert the MD nor HTML - just outputs the raw string in the label - what am I missing?Biggers
nvm, i just found down.toAttributedString() :)Biggers
P
7

Some people advise to use WKWebView or UILabel, but these solutions are terribly slow or inconvenient. I couldn't find a native SwiftUI solution, so I implemented my own (AttributedText). It's quite simple and limited in its functionality, but it works quickly and satisfies my needs. You can see all features in the README.md file. Feel free to contribute if the existing functionality is not enough for you.

Code example

AttributedText("This is <b>bold</b> and <i>italic</i> text.")

Result

Example

Philanthropy answered 12/5, 2021 at 9:29 Comment(3)
Perfect. <br> not supported though.Giantism
I love this project - lightweight and perfect!Moise
I’ve been struggling with html and the whole nightmare scenario for weeks. This package worked flawlessly and saved me from total self implosion. THANK YOU.Oleaginous
B
7

New since Swift 5.7 - convert from "basic" HTML

Swift 5.7 brought new functionalities related to regex. A new RegexBuilder was implemented in addition to the existing regex support, and that makes it easier to extrapolate the strings in HTML tags.

With little work, we can build a converter from "basic" HTML codes to markdown. By "basic" I mean:

  • they contain line breaks, bold, italic (no attributes)
  • they can contain hyperlinks, and that's the complicated part of the converter below
  • they do not contain headers, scripts, id attributes...

Of course, with more effort, anything can be achieved, but I'm going to stick with the basic example.

The String extension:


extension String {
    func htmlToMarkDown() -> String {
        
        var text = self
        
        var loop = true

        // Replace HTML comments, in the format <!-- ... comment ... -->
        // Stop looking for comments when none is found
        while loop {
            
            // Retrieve hyperlink
            let searchComment = Regex {
                Capture {
                    
                    // A comment in HTML starts with:
                    "<!--"
                    
                    ZeroOrMore(.any, .reluctant)
                    
                    // A comment in HTML ends with:
                    "-->"
                }
            }
            if let match = text.firstMatch(of: searchComment) {
                let (_, comment) = match.output
                text = text.replacing(comment, with: "")
            } else {
                loop = false
            }
        }

        // Replace line feeds with nothing, which is how HTML notation is read in the browsers
        var text = self.replacing("\n", with: "")
        
        // Line breaks
        text = text.replacing("<div>", with: "\n")
        text = text.replacing("</div>", with: "")
        text = text.replacing("<p>", with: "\n")
        text = text.replacing("<br>", with: "\n")

        // Text formatting
        text = text.replacing("<strong>", with: "**")
        text = text.replacing("</strong>", with: "**")
        text = text.replacing("<b>", with: "**")
        text = text.replacing("</b>", with: "**")
        text = text.replacing("<em>", with: "*")
        text = text.replacing("</em>", with: "*")
        text = text.replacing("<i>", with: "*")
        text = text.replacing("</i>", with: "*")
        
        // Replace hyperlinks block
        
        loop = true
        
        // Stop looking for hyperlinks when none is found
        while loop {
            
            // Retrieve hyperlink
            let searchHyperlink = Regex {

                // A hyperlink that is embedded in an HTML tag in this format: <a... href="<hyperlink>"....>
                "<a"

                // There could be other attributes between <a... and href=...
                // .reluctant parameter: to stop matching after the first occurrence
                ZeroOrMore(.any)
                
                // We could have href="..., href ="..., href= "..., href = "...
                "href"
                ZeroOrMore(.any)
                "="
                ZeroOrMore(.any)
                "\""
                
                // Here is where the hyperlink (href) is captured
                Capture {
                    ZeroOrMore(.any)
                }
                
                "\""

                // After href="<hyperlink>", there could be a ">" sign or other attributes
                ZeroOrMore(.any)
                ">"
                
                // Here is where the linked text is captured
                Capture {
                    ZeroOrMore(.any, .reluctant)
                }
                One("</a>")
            }
                .repetitionBehavior(.reluctant)
            
            if let match = text.firstMatch(of: searchHyperlink) {
                let (hyperlinkTag, href, content) = match.output
                let markDownLink = "[" + content + "](" + href + ")"
                text = text.replacing(hyperlinkTag, with: markDownLink)
            } else {
                loop = false
            }
        }

        return text
    }
}

Usage:

HTML text:

let html = """
<div>You need to <b>follow <i>this</i> link</b> here: <a href="https://example.org/en">sample site</a></div>
"""

Markdown conversion:

let markdown = html.htmlToMarkDown()
print(markdown)

// Result:
// You need to **follow *this* link** here: [sample site](https://example.org/en)

In SwiftUI:

Text(.init(markdown))

What you see:

enter image description here

Bedtime answered 14/12, 2022 at 15:21 Comment(0)
C
5

iOS 15 Supports Basic Markdown, but it does not include headings or images. Here is an answer if you want to include basic headings & images in text:

Text("Body of text here with **bold** text") // This will work as expected

But:

let markdownText = "Body of text here with **bold** text".
Text(markdownText) // This will not render the markdown styling

But you can fix that by doing:

Text(.init(markdownText)) // This will work as expected, but you won't see the headings formatted

BUT SwiftUI markdown doesn't support the headings (#, ##, ###, etc.) so if you want "# heading \nBody of text here with **bold** text" everything will render properly, minus the heading, you will still see "# heading".

So one solution is to break the string into lines, and implement a ForEach loop to check for the headings prefix (#), drop the #, and and create a Text() element with the appropriate styling like so:

let lines = blogPost.blogpost.components(separatedBy: .newlines)

VStack(alignment: .leading) {
                    ForEach(lines, id: \.self) { line in
                                    if line.hasPrefix("# ") {
                                        Text(line.dropFirst(2))
                                            .font(.largeTitle)
                                            .fontWeight(.heavy)
                                    } else if line.hasPrefix("## ") {
                                        Text(line.dropFirst(3))
                                            .font(.title)
                                            .fontWeight(.heavy)
                                    } else if line.hasPrefix("### ") {
                                        Text(line.dropFirst(4))
                                            .font(.headline)
                                            .fontWeight(.heavy)
                                    } else {
                                        Text(.init(line))
                                            .font(.body)
                                    }
                                }
}

This will create a well formated markdown text including headings.

If we want to also add images, first we can create an extension on the URL property:

extension URL {
func isImage() -> Bool {
    let imageExtensions = ["jpg", "jpeg", "png", "gif"]
    return imageExtensions.contains(self.pathExtension.lowercased())
}
}

This method checks if the URL's path extension is one of the common image file extensions (jpg, jpeg, png, or gif) and returns true if it is.

Then, we can alter the ForEach loop like so:

let lines = blogPost.blogpost.components(separatedBy: .newlines)
ForEach(lines, id: \.self) { line in
if line.hasPrefix("# ") {
    Text(line.dropFirst(2))
        .font(.largeTitle)
        .fontWeight(.heavy)
} else if line.hasPrefix("## ") {
    Text(line.dropFirst(3))
        .font(.title)
        .fontWeight(.heavy)
} else if line.hasPrefix("### ") {
    Text(line.dropFirst(4))
        .font(.headline)
        .fontWeight(.heavy)
} else if let imageUrl = URL(string: line), imageUrl.isImage() {
    // If the line contains a valid image URL, display the image
    AsyncImage(url: imageUrl) { phase in
        switch phase {
        case .empty:
            ProgressView()
        case .success(let image):
            image
                .resizable()
                .aspectRatio(contentMode: .fit)
        case .failure:
            Text("Failed to load image")
        @unknown default:
            fatalError()
        }
    }
} else {
    Text(line)
        .font(.body)
}
}

In this updated code, we're checking if the line contains a valid image URL by attempting to create a URL object from the line using URL(string: line) and then calling a custom extension method isImage() on the resulting URL to check if it points to an image.

If the line contains a valid image URL, we use the AsyncImage view to load the image asynchronously from the URL. The AsyncImage view automatically handles loading and caching of the image and provides a placeholder ProgressView while the image is being loaded. Once the image is loaded, we display it using the Image view with the resizable() and aspectRatio(contentMode: .fit) modifiers to resize and scale the image appropriately. If the image fails to load for some reason, we display an error message instead.

Calebcaledonia answered 23/3, 2023 at 10:1 Comment(0)
P
4

Text can just display Strings. You can use a UIViewRepresentable with an UILabel and attributedText.

Probably attributedText text support will come later for SwiftUI.Text.

Ptolemy answered 4/7, 2019 at 23:32 Comment(1)
yes, but link could not clickable, what wrong ?Flood
C
3

As far as rendering HTML in swiftUI there are a number of solutions, but for rendering it as a generic UILabel via AttributedText, this is what I went with after combining a few other solutions I found.

Here is the UIViewRepresentable which you'll use from your parent swiftUI views:

//Pass in your htmlstring, and the maximum width that you are allowing for the label
//this will, in turn, pass back the size of the newly created label via the binding 'size' variable
//you must use the new size variable frame on an encompassing view of wherever this htmlAttributedLabel now resides (like in an hstack, etc.)
struct htmlAttributedLabel: UIViewRepresentable {
    @Binding var htmlText: String
    var width: CGFloat
    @Binding var size:CGSize
    var lineLimit = 0
    //var textColor = Color(.label)
 
    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        label.lineBreakMode = .byWordWrapping
        label.numberOfLines = lineLimit
        label.preferredMaxLayoutWidth = width
        //label.textColor = textColor.uiColor()
        return label
    }

    func updateUIView(_ uiView: UILabel, context: Context) {
    let htmlData = NSString(string: htmlText).data(using: String.Encoding.unicode.rawValue)
    let options = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html]
    DispatchQueue.main.async {
        do {
            let attributedString = try NSMutableAttributedString(data: htmlData!, options: options, documentAttributes: nil)
            //add attributedstring attributes here if you want
            uiView.attributedText = attributedString
            size = uiView.sizeThatFits(CGSize(width: width, height: CGFloat.greatestFiniteMagnitude))
            print("htmlAttributedLabel size: \(size)")
        } catch {
            print("htmlAttributedLabel unexpected error: \(error).")
        }
    }
}

Now, to use this label effectively, you'll need to provide it a maximum width, which you can get from geometry reader. You'll also need to pass in a CGSize binding so the label can tell the parent view how much space it needs to render. You'll in turn use this size to set an encompassing view height, so that the rest of swiftUI can layout around your html label appropriately:

@State var htmlText = "Hello,<br />I am <b>HTML</b>!"
@State var size:CGSize = .zero
var body: some View {
    HStack {
        GeometryReader { geometry in
                                htmlAttributedLabel(htmlText: $htmlText, width: geometry.size.width, size: $size).frame(width:size.width, height: size.height). //the frame is important to set here, otherwise sometimes it won't render right on repeat loads, depending on how this view is presented
                       }
           }.frame(height: size.height) //most important, otherwise swiftui won't really know how to layout things around your attributed label
}

You can also set line limits, or text color, etc., and obviously you can extend this object to take in whatever UIlabel parameters you'd like to use.

Cyrillic answered 9/4, 2021 at 23:57 Comment(1)
this works great but I was trying to add Font to this and without luck, any suggestions? Thanks.Pearlstein
R
2

This is a simple extension that uses AttributedString

extension String {
    var htmlToNSAttributed: NSAttributedString {
        guard let data = data(using: .utf8) else { return NSAttributedString(string: self) }
        do {
            return try NSAttributedString(
                data: data,
                options: [
                    .documentType: NSAttributedString.DocumentType.html,
                    .characterEncoding: String.Encoding.utf8.rawValue
                ],
                documentAttributes: nil
            )
        } catch {
            return NSAttributedString(string: self)
        }
    }
    
    var htmlToString: String {
        htmlToNSAttributed.string
    }
    
    var htmlToAttributed: AttributedString {
        do {
            return try AttributedString(htmlToNSAttributed, including: \.swiftUI)
        } catch {
            return AttributedString(stringLiteral: self)
        }
    }
}

Usage

Text(text.htmlToAttributed)
Radiobiology answered 22/3, 2023 at 11:18 Comment(0)
C
1

iOS 14 and above

Late to the party, but I found a solution that also works for iOS 14 without UIViewRepresentable and without having to check the iOS verion.

You simply have to create an extension for Text to add support for NSAttributedString. You can copy the extension from here:

extension Text {
init(_ astring: NSAttributedString) {
    self.init("")
    
    astring.enumerateAttributes(in: NSRange(location: 0, length: astring.length), options: []) { (attrs, range, _) in
        
        var t = Text(astring.attributedSubstring(from: range).string)

        if let color = attrs[NSAttributedString.Key.foregroundColor] as? UIColor {
            t  = t.foregroundColor(Color(color))
        }

        if let font = attrs[NSAttributedString.Key.font] as? UIFont {
            t  = t.font(.init(font))
        }

        if let kern = attrs[NSAttributedString.Key.kern] as? CGFloat {
            t  = t.kerning(kern)
        }
        
        
        if let striked = attrs[NSAttributedString.Key.strikethroughStyle] as? NSNumber, striked != 0 {
            if let strikeColor = (attrs[NSAttributedString.Key.strikethroughColor] as? UIColor) {
                t = t.strikethrough(true, color: Color(strikeColor))
            } else {
                t = t.strikethrough(true)
            }
        }
        
        if let baseline = attrs[NSAttributedString.Key.baselineOffset] as? NSNumber {
            t = t.baselineOffset(CGFloat(baseline.floatValue))
        }
        
        if let underline = attrs[NSAttributedString.Key.underlineStyle] as? NSNumber, underline != 0 {
            if let underlineColor = (attrs[NSAttributedString.Key.underlineColor] as? UIColor) {
                t = t.underline(true, color: Color(underlineColor))
            } else {
                t = t.underline(true)
            }
        }
        
        self = self + t
        
    }
}

}

Here is how to convert your HTML String to an NSAttributedString: Convert HTML to NSAttributedString in iOS

Calyces answered 23/5, 2023 at 6:32 Comment(0)
M
0

For rendering HTML, I use extension of String for convert to Attributed HTML String and extension of UIColor for working with hex color

extension String {
    func htmlAttributedString(
        fontSize: CGFloat = 16,
        color: UIColor = UIColor(Color.theme.body),
        linkColor: UIColor = UIColor(Color.theme.primary),
        fontFamily: String = "Roboto"
    ) -> NSAttributedString? {
        let htmlTemplate = """
        <!doctype html>
        <html>
          <head>
            <link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'>
            <style>
                body {
                    color: \(color.hexString!);
                    font-family: \(fontFamily);
                    font-size: \(fontSize)px;
                }
                a {
                    color: \(linkColor.hexString!);
                }
            </style>
          </head>
          <body>
            \(self)
          </body>
        </html>
        """

        guard let data = htmlTemplate.data(using: .unicode) else {
            return nil
        }

        guard let attributedString = try? NSAttributedString(
            data: data,
            options: [.documentType: NSAttributedString.DocumentType.html],
            documentAttributes: nil
            ) else {
            return nil
        }

        return attributedString
    }
}

extension UIColor {
    var hexString:String? {
        if let components = self.cgColor.components {
            let r = components[0]
            let g = components[1]
            let b = components[2]
            return  String(format: "#%02x%02x%02x", (Int)(r * 255), (Int)(g * 255), (Int)(b * 255))
        }
        return nil
    }
}

And use it later like this:

import SwiftUI

struct ContentView: View {
    
    @State var htmlText = """
        <a href="example.com">Example</a>
    """
    
    var body: some View {
        if let nsAttrString = htmlText.htmlAttributedString() {
            Text(AttributedString(nsAttrString))
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Mounts answered 5/9, 2022 at 3:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.