Markdown Editor in SwiftUI

DevTechie Inc
Jun 24, 2022


Photo by James Harrison on Unsplash

Here is what we will build by the end of this article:

Support for markdown was introduced with SwiftUI 3. Markdown has made it simple to mix and match string formatting without creating a complex hierarchy of subviews.

SwiftUI doesn’t support markdown to its full power but only subset of functions are available. Given the successful reception of markdown, hopefully SwiftUI team will add support for more formatting options in markdown space.

At this point SwiftUI only supports following operations for markdown:

**bold text**
*italics*
~~strikethrough~~
`inline code`
[DevTechie](https://www.devtechie.com)
In this article, we will build a simple markdown editor using SwiftUI. We will use another newly introduced struct called AttributedString

Attributed strings are character strings that have attributes for individual characters or ranges of characters. Attributes provide traits like visual styles for display, accessibility for guided access, and hyperlink data for linking between data sources. Attribute keys provide the name and value type of each attribute. System frameworks like Foundation and SwiftUI define common keys, and you can define your own in custom extensions.
AttributedStrings can be used to format string by pattern matching for example, in the code below, we can color DevTechie.com with orange color by finding range of the text and set its foreground color to orange. Text View can render attributedString directly so simply passing the string will render the view, as shown below:

var attributedString = AttributedString("Visit DevTechie.com for more!")let range = attributedString.range(of: "DevTechie.com")!
attributedString[range].foregroundColor = .orangeText(attributedString)
With that knowledge of AttributedString, let’s continue to build our Markdown Editor.

We will start with a string type State variable to bind TextEditor view.

struct MarkdownEditorSwiftUI: View {
    
    @State private var markdownText: String = "**DevTechie**"}
Let’s also create a VStack with GeometryReader so we can divide even space between our TextEditor(where user will enter markdown text AKA input) and Text view(where rendered string will be displayed AKA output).

struct MarkdownEditorSwiftUI: View {
    
    @State private var markdownText: String = "**DevTechie**"
    
    var body: some View {
        VStack {
            Text("**DevTechie Markdown Editor**")
            GeometryReader { proxy in
                ScrollView {
                    VStack(alignment: .leading) {
                        TextEditor(text: $markdownText)
                            .disableAutocorrection(true)
                            .autocapitalization(.none)
                            .frame(height: proxy.size.height/2)
                        Divider()
                        Text("Output will go here")
                            .lineLimit(nil)
                            .multilineTextAlignment(.leading)
                    }
                }
            }
        }.padding()
    }
}
Now its time to add the markdown output. For this we will take help from AttributedString. We will create a property called markdown of AttributedString type. This will be a computed property with following logic:

var markdown: AttributedString {
    (try? AttributedString(markdown: markdownText)) ?? AttributedString()
}
Here we are checking if we can convert markdownText to attributedString or not. We will bind this property to our Text view which currently displays “Output will go here”:

struct MarkdownEditorSwiftUI: View {
    
    @State private var markdownText: String = "**DevTechie**"
    
    var markdown: AttributedString {
        (try? AttributedString(markdown: markdownText)) ?? AttributedString()
    }
    
    var body: some View {
        VStack {
            Text("**DevTechie Markdown Editor**")
            GeometryReader { proxy in
                ScrollView {
                    VStack(alignment: .leading) {
                        TextEditor(text: $markdownText)
                            .disableAutocorrection(true)
                            .autocapitalization(.none)
                            .frame(height: proxy.size.height/2)
                        Divider()
                        Text(markdown)
                            .lineLimit(nil)
                            .multilineTextAlignment(.leading)
                    }
                }
            }
        }.padding()
    }
}
Here is our final output:

With that we have reached the end of this article. Thank you once again for reading. Subscribe to our weekly newsletter at https://www.devtechie.com