10

I've been trying to use the new AttributedString that released with iOS 15 to render Markdown stored in a variable. However, I haven't been able to find a way for it to render markdown headings such as:

# Title 1
### Title 3
###### Title 6

Here's my code:

let description = """
        # Hello World

        Coin coin
        """
let attributed = (try? AttributedString(markdown: description)) ?? AttributedString(description)
return ScrollView {
    Text(attributed)
        .padding(.horizontal)
}

But here's what's displayed in the preview:

enter image description here

Does anyone successfully got them working or is this something impossible to do as of now?

Clément Cardonnel
  • 4,232
  • 3
  • 29
  • 36

1 Answers1

12

The markdown is parsed properly, the problem seems to be that nothing is done with the presentation intents for headers.

You could look through the presentation intents and apply the styling manually to headers.

You'll have to use interpretedSyntax: .full which will mean that whitespace is ignored, so you might want to also add a new line after each block.

extension AttributedString {
    init(styledMarkdown markdownString: String) throws {
        var output = try AttributedString(
            markdown: markdownString,
            options: .init(
                allowsExtendedAttributes: true,
                interpretedSyntax: .full,
                failurePolicy: .returnPartiallyParsedIfPossible
            ),
            baseURL: nil
        )

        for (intentBlock, intentRange) in output.runs[AttributeScopes.FoundationAttributes.PresentationIntentAttribute.self].reversed() {
            guard let intentBlock = intentBlock else { continue }
            for intent in intentBlock.components {
                switch intent.kind {
                case .header(level: let level):
                    switch level {
                    case 1:
                        output[intentRange].font = .system(.title).bold()
                    case 2:
                        output[intentRange].font = .system(.title2).bold()
                    case 3:
                        output[intentRange].font = .system(.title3).bold()
                    default:
                        break
                    }
                default:
                    break
                }
            }
            
            if intentRange.lowerBound != output.startIndex {
                output.characters.insert(contentsOf: "\n", at: intentRange.lowerBound)
            }
        }

        self = output
    }
}

Example

As far as I can see only baselineOffset, backgroundColor, font, foregroundColor, kern, strikethroughStyle, tracking, and underlineStyle are supported in SwiftUI.

It's not a perfect solution, but it might get you closer to what you need.

Joony
  • 4,498
  • 2
  • 30
  • 39