Justin Duke

Markdown in Swift

A thought process I had last week:

  1. Man, it would be great if I could write descriptions for Barback in Markdown.
  2. Wait, maybe I can. There are Markdown compilers for everything, right?
  3. Wait, what would it even compile to? HTML? An NSAttributedString? How would that work?
  4. Blargh. Let’s just Google things and hope for the best.

But for you, reader dearest, I have compiled my solution. It is thoroughly unexciting and likely suboptimal. Let us press on!

1. First, you need a Markdown compiler.

I selected Markingbird, because I’m a sucker for using Swift drop-ins. But there are many other solutions. (Don’t roll your own unless you like agony or premature optimization.) Throw this in your project. Swift means auto-importing. You invoke it like this:

var markdownEngine = Markdown()
let outputHTML = markdownEngine.transform(markdownText)

It is important that Markdown isn’t declared via let, as it is self-mutating. You could probably get away with having it as a singleton, but it’s not threadsafe and the setup time is negligible (~10ms).

2. Next, you throw the HTML into an NSAttributedString.

This is surprisingly easy:

let informationData = informationHTML.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let informationString = NSAttributedString(data: informationData!, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil, error: nil)

There’s some bad news here: currently, this is done via rendering the whole dang thing in a UIWebView, which is like super slow. I’m talking approx 400ms slow. This is depressing, but still acceptable for my use case so I’m not too bummed: that being said, if anyone has a better way of accomplishing this let me know!

3. Lastly, you throw the NSAttributedString into a relevant view.

This should be self-explanatory for anyone who has experience with attributed strings: you just set the attributedText attribute:

attributedText = informationString

There are two things to note about this, though:

Setting attributedText overwrites the style information of the label.

There are probably more sophisticated ways to fix this issue, but the styling I wanted was pretty fixed across the views so instead of iterating over the string elements I did it by straight-up adding CSS to the HTML before rendering it:

var informationHTML += "<style type='text/css'>"
    + "p { text-align: center;"
    + "    font-family: \(font.familyName);"
    + "    font-size: \(font.pointSize)px;"
    + "    color: \(normalColor); }"
    + "</style>"

UILabels don’t play nice with hyperlinks.

Again, something that might be obvious, but if you’re dealing with Markdown you probably want support for hyperlinks – even if just the default handling mechanism – and you’re going to need to make sure you’re using a UITextView with some specific attributes set:

view.selectable = true
view.editable = false

There are a couple ways you could implement all of this stuff – again, I’m not experienced with Swift to proselytize a certain approach as the most Swift-y 1. But, for better or for worse, this is how I implemented it – as an observed attribute of an existant subclass of UIWebView:

import Foundation
import UIKit

class DescriptionTextView : UITextView {

    // Other boring/irrelevant stuff is in this class, I promise.

    var markdownText: NSString? {
        didSet {

            // Hey, short-circuiting logic!
            if markdownText == nil || markdownText == "" {
                attributedText = NSAttributedString(string: "")
                return
            }

            var markdownEngine = Markdown()
            let normalColor = NSString(format:"%2X", Color.Light.rawValue)
            let tintColor = NSString(format:"%2X", Color.Tint.rawValue)
            var informationHTML = "<style type='text/css'>"
                + "p { text-align: center;"
                + "    font-family: \(font.familyName);"
                + "    font-size: \(font.pointSize)px;"
                + "    color: \(normalColor); }"
                + "</style>"
            informationHTML += markdownEngine.transform(markdownText!)
            let informationData = informationHTML.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
            let informationString = NSAttributedString(data: informationData!, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil, error: nil)
            attributedText = informationString

        }
    }

}

  1. Number one complaint of Swift’s name is that it’s an adjective. What’s the Swift equivalent of “Pythonic”? [return]
Liked this post? You should subscribe to my newsletter and follow me on Twitter.

(I've got an RSS feed, too, if you'd prefer.)