Minimal templating system in Swift in only 10 lines

Matthias Lamoureux
5 min readApr 17, 2020

--

Sometimes you just need a basic feature and you don’t want to import a new framework or a library dependency into your project. You don’t use a nuke to kill a fly, do you?… Do you?

Anyway, yesterday I had to write a simple bit of code to allow our users to send us a technical report when needed. The technical report is simple, just a few informations like the date, the device they use, which os version, and so on.

First make it work

I started, as always, to do it the quick and dirty way. Just to check that everything else works well within our app. I wrote something like this:

That’s ok, but… Can’t we do better?

Of course, IRL, there were much more data, but let stick with these for our example. So it worked, our tests were good, everything was fine… But when I had to start and think about localization, changing report layout, adding or removing informations, this solution started to smell…

Then make it right

In fact this is the job of a templating engine. But I don’t need any fancy system with loops, conditions and so on, just a good old search and replace mechanism. So I agree with myself to use quite a classical standard for template fields with this syntax: ${fieldName}

This way my report could look like this:

Basic template report

That would be much easier for translators and for us when we want to adjust its layout.

Well thats nice, but now we need our template engine to transform our placeholders with genuine data. For that we’ll use the basic method replaceOccurrences(of:with:) of String. It is not the most optimized solution but for our needs it’s going to be just fine (a few fields in a small template).

Now the real question is, how to enter data. As it will be strings in the end, I chose to use a basic dictionary with the name of the field as key and its matching string representation as value.

With all that let’s write our first template engine.

That’s not too fancy, but it works.

Here we use the reduce method on the data dictionary to replace each field by its value.

Let’s give it a try:

Well it’s really basic, but can’t we go further?

I promised you 10 lines

Ok, it works, but it’s not that nice. Fortunately our good friend @dynamicMemberLookup will really help for this task, let’s see how:

Yeah! That’s it!!!

This way, each time you want to add data, you just have to use a dynamic property. Moreover, as the result of the subscript is CustomStringConvertible you don’t need to use the description method on Int or Date values anymore.

Isn’t it nice?

Of course you still have to be extra careful with the names of the properties that must match exactly the fields in your template. A typo will result in an empty field at runtime, you won’t see it during compilation…

Ok we have a nice template engine with only ten lines…

But…

One more thing

What if you want more, for instance dates having to conform to ISO 8601 standard? There are many ways of doing it, one of them is to add another dynamic lookup member subscript this time resulting with the Date type, and the correct date formatter.

Tadaaaaa!

So now all dates in your report will be ISO 8601 compliant:

Our nice report

You’ve probably noticed a little something that may not be that cool. We must write getters for our subscripts, because in swift you cannot have only a setter for any property or subscript. In our case, this has no real meaning as our template is used to populate data, not to read them. For the default subscript, as it basically is jus syntactic sugar to access the data dictionary, that’s okay: if the field does not exist we return nil, just as the dictionary would. But for the date, we can’t return nil. The reason is quite simple, to do it we would have to set the subscript type to Date? but that’s impossible: the first subscript is already an optional, the compiler will simply refuse it because it won’t be able to infer what subscript to use if we do template.myField = nil! So as an old fashioned workaround we return January the first, 1970 whenever the date getter would return no value… I know that’s ugly, but acceptable because these subscript getters are not meant to be used… They are not…

Localization

Thanks to Xcode integrated localization system, we just have to store our templates as plain text files and have them localized:

Localized versions of the report template

That’s it, I hope you’ve enjoyed this useful little trick that, of course, can’t replace real template engines, but it’s really enough for the job it needs to do.

You can find the source of the last Template struct here: https://gist.github.com/letatas/ac0b45490c7eb666def5245b348322e0

--

--