Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to render Markdown headings in SwiftUI AttributedString?

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?

like image 389
Pomme2Poule Avatar asked Dec 13 '25 06:12

Pomme2Poule


1 Answers

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.

like image 84
Joony Avatar answered Dec 15 '25 16:12

Joony