Swift: Markdown files from string literals
Tags: swift, markdown
Published: 2020-03-05 16:00:45

My [new iOS fiction project](https://twitter.com/_danilo/status/1227650622377414656) relies heavily on text.

That means I want to make it easy to create that content anywhere, and I want it to be frictionless to drop it into the project as needed.

My solution: Markdown files I can load from the bundle using string literals. Look how easy:

```swift
let markdown: MarkdownFile = "markdown.md"
```

Here’s how to do it.

String literals
---------------

In Swift, you use string literals all the time. Usually to initialize strings.

```swift
let string: String = "Hello, I am a string."
```

But Swift includes a protocol called `ExpressibleByStringLiteral`. Which means if your Swift type adopts it, that type can be initialized with nothing more than a string. While this is immediately convenient, it has real power for assets that need tedious boilerplate. Say, anything that needs to be loaded from a bundle.

Basic example
-------------

```swift
struct MarkdownFile: ExpressibleByStringLiteral {
    
    let bundleName: String
    let rawMarkdown: String?
    
    init(stringLiteral: String) {
        
        bundleName = stringLiteral
        
        var loadedMarkdown: String? = nil
                
        if let filepath = Bundle.main.path(forResource: bundleName, ofType: nil) {
        //By skipping the ofType argument above, we'll match to the first file whose name
        //exactly matches bundleName
            do {
                let loadedString = try String(contentsOfFile: filepath)
                loadedMarkdown = loadedString
            } catch {
                print("Could not load string: \(error)")
            }
        } else {
            print("Could not find file: \(bundleName)")
        }
        
        rawMarkdown = loadedMarkdown
    }
}
```

Here’s a basic example of a `MarkdownFile` struct. It knows two things about itself: the name of the file used to initialize it, and any string it was able to load from a file in the bundle with that name.

On `init` it goes looking for a bundle resource matching the name it was provided through the string literal. If it finds one, and it can load its contents as a string, those contents are stored to `rawMarkdown`. If not, `rawMarkdown` returns `nil`.

This is already pretty convenient. Again, to initialize, all you need is:

```swift
let markdown: MarkdownFile = "markdown.md"
```

But we can take it further.

Adding convenience
------------------

The `MarkdownFile` struct can be responsible for converting its contents into a display representation, as well. Let’s add a computed property to parse the Markdown into HTML. I’ll be using [Ink](https://github.com/JohnSundell/Ink) for this, but you could use any project—or convert it into something else, like `NSAttributedString`.

```swift
var htmlRepresentation: String? {
    if let raw = rawMarkdown {
        return MarkdownParser().html(from: raw)
    } else {
        return nil
    }
}
```

Putting it all together
-----------------------

With our output property all set up, we have a small, convenient API for handling Markdown files in any way we want. Here’s how we use it:

```swift
let markdown: MarkdownFile = "markdown.md"

if let html = markdown.htmlRepresentation {
    webview.loadHTMLString(html, baseURL: nil)
}

self.title = markdown.bundleName
```
![Screen Shot 2020-03-05 at 10 39 58 AM](https://user-images.githubusercontent.com/213358/75999380-1cf13600-5ed0-11ea-82b2-c1f2ff5a19ec.png)

Behind the scenes, lots of stuff is happening to load and parse the file. But when you need Markdown across your project, you need only concern yourself with a filename. If you want to change any part of how this works later on, you have a single struct that’s responsible for all the Markdown behavior in your code.

[Full example code here](https://github.com/daniloc/MarkdownStringLiteral).