import SwiftUI 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\n", at: intentRange.lowerBound) } } self = output } } struct ReleaseNotesView: View { @Environment(\.dismiss) private var dismiss var body: some View { NavigationView { ScrollView { VStack(alignment: .leading, spacing: 16) { if let attributedString = loadReleaseNotes() { Text(attributedString) .textSelection(.enabled) .padding() } else { Text("Unable to load release notes") .foregroundColor(.secondary) .padding() } } } .navigationTitle("What's New") .navigationBarTitleDisplayMode(.large) .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Done") { dismiss() } } } } } private func loadReleaseNotes() -> AttributedString? { guard let url = Bundle.main.url(forResource: "RELEASE_NOTES", withExtension: "md"), let markdownContent = try? String(contentsOf: url), let attributedString = try? AttributedString(styledMarkdown: markdownContent) else { return nil } return attributedString } } #Preview { ReleaseNotesView() }