17

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?

6
  • 1
    hackingwithswift.com/quick-start/swiftui/… Commented Jan 9, 2022 at 16:41
  • Yes, I saw that article, but he doesn't try headings either because he didn't think about it, or because he came across the same problem. Commented Jan 10, 2022 at 10:46
  • It just mean it is not supported. Commented Jan 12, 2022 at 5:06
  • 4
    Same issue here. Found this: The current version of Swift doesn’t support all the Markdown syntax. For example, it can’t render heading, numbered list, and image. Hopefully, Apple will provide further improvement in future updates of SwiftUI. Commented Apr 12, 2022 at 3:25
  • 1
    the same in iOS 17 Beta, no rendering for headings Commented Jun 8, 2023 at 17:54

1 Answer 1

34

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.

Sign up to request clarification or add additional context in comments.

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.