33

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
            
2
  • 2
    hmm, does not work for me as well. You may have to resort to: "Link(linkTitle, destination: URL(string: linkURL)!)" Commented Jul 18, 2021 at 7:49
  • @workingdog I am implementing hashtags(#) and mentions(@) Text very perfect, I only need to customize the URL Schemes, but unfortunately I can’t pass the string dynamically Commented Jul 18, 2021 at 8:06

6 Answers 6

47

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)
Sign up to request clarification or add additional context in comments.

Comments

26

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!
    }
}

1 Comment

This works because calling the init creates a LocalisedString that recognises markdown
8

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
        }

    }
}

1 Comment

Simple and effective, can be processed into AttributedString. Thank you
4

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

Comments

4

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

1 Comment

LocalizedStringKey's documentation would never mention markdown because it's not LocalizedStringKey's responsibility. Instead, Text's documentation explains it here: developer.apple.com/documentation/swiftui/text/…
2

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.