My new iOS fiction project 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:
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.
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
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:
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 for this, but you could use any project—or convert it into something else, like NSAttributedString
.
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:
let markdown: MarkdownFile = "markdown.md"
if let html = markdown.htmlRepresentation {
webview.loadHTMLString(html, baseURL: nil)
}
self.title = markdown.bundleName
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.
Top comments (0)