3 surprises when using Markdown in SwiftUI

3 surprises when using Markdown in SwiftUI

Β·

2 min read

In WWDC 2021 Apple announced to add Markdown support to the Foundation (hello AttributedString) and SwiftUI frameworks (hello Text) starting with iOS 15.

Here I share three little surprises with you. Those are not obvious as they are not part of the documentation.

1. The supported specification

A nice surprise is that SwiftUI supports GitHub Flavored Markdown (GFM). This becomes obvious when trying

Text("Hello ~~World~~")

Because strikethrough, any text wrapped in two tildes ( ~ ), is not supported in CommonMark.

An Apple engineer confirmed the use of GFM in the Apple Developer forum.

Yes, it is GitHub flavored markdown. AttributedString converts both inline and block styles. SwiftUI renders inline styles (but not images at this time). We use the fantastic cmark-gfm library to parse the markdown string.

The last statement about using cmark-gfm library might or might not be true anymore considering Apple's investment in swift-markdown. swift-markdown is a Swift package for parsing, building, editing, and analyzing Markdown documents.

2. Using String variables is NOT straightforward

Using a string literal in Text works.

Text("**Hello** *World*") // :)

Fine, but using a string variable does not!

struct MarkdownTest: View {
  var markdown = "**Hello** *World*"
  var body: some View {
    VStack {
      Text(markdown) // shows **Hello *World* :(
    }
  }
}

String interpolation does not help.

Text("\(markdown)") // shows **Hello *World* :(

The trick is to use init(_:)) of LocalizedStringKey.

Text(.init(markdown)) // :)

Text implicitly looks up a localized string when you provide a string literal. When you use the initializer Text("Hello"), SwiftUI creates a LocalizedStringKey for you and uses that to look up a localization of the Hello string. This works because LocalizedStringKey conforms to ExpressibleByStringLiteral.

You must use the same initializer to mimic the behavior and ensure that markdown formatting is displayed correctly.

3. Line Breaks require use of MarkdownParsingOptions in AttributedString

You might run into the situation that you have an AttributedString with line breaks (\n), but those line breaks are not displayed.

The trick is to add the .inlineOnlyPreservingWhitespace option when initializing the AttributedString.

Example:

struct MarkdownTest: View {
    let attributedString = try! AttributedString(markdown: "**Hello** ~~*World*~~\n!", options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace))

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

Using line breaks in a regular string is no problem.

Summary

Summary

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!