gaohomway
gaohomway

Reputation: 4060

SwiftUI Text Markdown dynamic string not working

I want to dynamically deliver content and display hyperlinks, but it can’t be delivered dynamically and doesn’t work

let linkTitle = "Apple Link"
let linkURL = "http://www.apple.com"
let string = "[Apple Link](http://www.apple.com)"
            
Text(string)        // Not working

Text("[Apple Link](http://www.apple.com)")  // Working
            
Text("[\(linkTitle)](http://www.apple.com)")    // Working
            
Text("[\(linkTitle)](\(linkURL))")  // Not working
            

Upvotes: 26

Views: 8053

Answers (6)

Heitara
Heitara

Reputation: 452

Here is an improved version of workingdog support Ukraine's answer. The String extension uses the option to preserve the '\n' in the markdown string.

extension String {
var toMarkdownString: AttributedString {
    do {
        return try AttributedString(markdown: self,
                                    options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace))
    } catch {
        return AttributedString("Error parsing markdown: \(error)")
    }
  }
}

Upvotes: 0

Galen
Galen

Reputation: 53

Apple's Documentation On This

Creating Attributed Strings with Markdown

You can create an attributed string by passing a standard String or Data instance that contains Markdown to initializers like init(markdown:options:baseURL:). The attributed string creates attributes by parsing the markup in the string.

do {
    let thankYouString = try AttributedString(
        markdown:"**Thank you!** Please visit our [website](https://example.com)")
} catch {
    print("Couldn't parse: \(error)")
}

Localized strings that you load from strings files with initializers like init(localized:options:table:bundle:locale:comment:) can also contain Markdown to add styling. In addition, these localized attributed string initializers can apply the replacementIndex attribute, which allows you to determine the range of replacement strings, whose order may vary between languages.

Further Explanation

Why not to use the LocalizedStringKey init

The behavior where LocalizedStringKey effectively works as markdown is undocumented, and so we probably should not use LocalizedStringKey inits as this is not a guaranteed pattern. Future updates might change this, and so it's better to use the more appropriate component AttributedString.

You can check apple's documentation on this to see for yourself, but LocalizedStringKey documentation does not mention markdown.

Basically, we probably shouldn't do this:

Text(.init(dynamicString)) Text(LocalizedStringKey(markdownStr))

Why using AttributedString is the right answer

As a few people mentioned, using AttributedString(markdown: dynamicString) for dynamic strings is the right answer.

Using Text with a static string (i.e. Text("[Apple Link](http://www.apple.com)")) will work fine, but dynamic strings need to be typed to properly parse as markdown. @kgaidis explains this well.

Important Detail To Add

try! AttributedString(markdown: dynamicString) will strip the string of \n characters. In order to preserve these characters, you can pass options in the init like so:

    try! AttributedString(
        markdown: dynamicString,
        options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)
        // \n will be stripped unless this option is added.
    )

Upvotes: 3

AbdelAli
AbdelAli

Reputation: 816

To define markdown string separately and then pass it to text, we should explicitly convert it to a localized string key. Because otherwise, SwiftUI will just treat this as a regular string and will not parse it internally.

let markdownStr = "Hello, **world**! Check out our [website](https: //example.com)!"
Text (LocalizedStringKey (markdownStr))

Upvotes: 2

Raja Kishan
Raja Kishan

Reputation: 18924

Add another .init in text.

struct ContentView: View {
    
    let linkTitle = "Apple Link"
    let linkURL = "http://www.apple.com"
    let string = "[Apple Link](http://www.apple.com)"
    
    var body: some View {
    
        Text(.init(string))     // <== Here!
        
        Text("[Apple Link](http://www.apple.com)")  // Working
        
        Text("[\(linkTitle)](http://www.apple.com)")    // Working
        
        Text(.init("[\(linkTitle)](\(linkURL))"))  // <== Here!
    }
}

Upvotes: 23

kgaidis
kgaidis

Reputation: 15589

Short Answer

Wrap the string in AttributedString(markdown: my_string_here):

let string: String = "[Apple Link](http://www.apple.com)"
Text(try! AttributedString(markdown: string))

Extension

extension String {
  func toMarkdown() -> AttributedString {
    do {
      return try AttributedString(markdown: self)
    } catch {
      print("Error parsing Markdown for string \(self): \(error)")
      return AttributedString(self)
    }
  }
}

Long Answer

SwiftUI Text has multiple initializers.

For String:

init<S>(_ content: S) where S : StringProtocol

For AttributedString:

init(_ attributedContent: AttributedString)

When you declare a static string, Swift is able to guess whether the intent is to use a String or AttributedString (Markdown). However, when you use a dynamic string, Swift needs help in figuring out your intent.

As a result, with a dynamic string, you have to explicitly convert your String into an AttributedString:

try! AttributedString(markdown: string)

Upvotes: 38

you can try this taken from: How to show HTML or Markdown in a SwiftUI Text? halfway down the page.

extension String {
    func markdownToAttributed() -> AttributedString {
        do {
            return try AttributedString(markdown: self) /// convert to AttributedString
        } catch {
            return AttributedString("Error parsing markdown: \(error)")
        }
    }
}
struct ContentView: View {
    let linkTitle = "Apple Link"
    let linkURL = "https://www.apple.com"
    let string = "[Apple Link](https://www.apple.com)"
       
    @State var textWithMarkdown = "[Apple Link](https://www.apple.com)"
    
    var body: some View {
        VStack {

            Text(textWithMarkdown.markdownToAttributed()) // <-- this works
            
            Text(string)        // Not working

            Text("[Apple Link](http://www.apple.com)")  // Working
                        
            Text("[\(linkTitle)](http://www.apple.com)")    // Working
                        
            Text("[\(linkTitle)](\(linkURL))")  // Not working
        }

    }
}

Upvotes: 7

Related Questions