4 Magic Tricks to Simplify Property Configuration in Swift

Starring extensions, closures, generics, keypaths and subscripts

Let’s try to improve the way we configure our objects in Swift. We’ll set up some goals and try to hit them from different angles. Eventually we’ll overshoot, and write some really silly code, but we may just find a syntactic sweet spot along the way.

Meet the patient

let label = UILabel()func setupLabel() {
label.text = "text"
label.textColor = .blue
}

There’s nothing wrong with this, but without proper discipline, this type of configuration code can easily become scattered, and end up in unexpected places. How can we remedy that?

Primary goals:

  • A uniform way to configure layout
  • A single place for configuration, preferably at the point of declaration

Secondary goal:

  • Less verbosity, for brevity is the soul of wit

Take #1: Chain ’em up!

Photo by Mike Alonzo on Unsplash

By extending types with setter methods that return self, we can accomplish a fluent configuration interface that comes pretty close to our goals.

The trick is that every method is named just like the property it configures. This way, nobody has to learn anything (new), and auto-completion can keep doing its thing (!).

extension UIView {    func backgroundColor(_ backgroundColor: UIColor?) -> Self { 
self
.backgroundColor = backgroundColor
return self
}
}

This allows us to configure the background color of a view right after we declare it, and still be able to store the reference to it.

let blueView = UIView().backgroundColor(.blue)

Big win!

Self awareness

Returning Self makes sure the specific subclass is returned, so you can keep performing subclass specific configuration in subsequent calls, and have the correct type returned.

The type extended should of course be the earliest one in the inheritance chain that introduces the property; the backgroundColor method extends the base class UIView, which makes it available for all subclasses, while a method for setting the text of a label would extend UILabel.

extension UILabel {    func text(_ text: String?) -> Self {
self.text = text
return self
}
}
let label = UILabel().backgroundColor(.blue).text("I'm a label")

What’s the catch?

Also, your auto-completion list will be longer, since these properties will also have methods with the same name, but that’s more of a feature than a flaw.

We’re using this on the project I’m working on right now, and view configuration generally looks pretty sweet:

private let titleLabel = UILabel()
.lines(0)
.autoshrink(to: 0.5)
.fontSize(17)

We’re obviously doing a bit more than prescribed here, changing names to reduce verbosity and grouping related changes under one method, but even a developer new to the project will figure out what this label is configured to do.

I’m certainly not the first one to stumble upon this idea. A GitHub search will reveal projects such as ChainKit, where the hard work of writing all these methods has been done already. Pod install, and you’re ready to chain ’em up.

Take #2 — BlockConfigurable

For all its similarities to Swift, Kotlin has some seriously useful tricks up its sleeve.

There’s an apply method which enables block based configuration of the receiver, but with a big convenience; the scope is temporarily changed to that of the receiver.

This means you don’t have to qualify the receiver within the block, just set properties as you would on self. And just as in our previous, chainable extension methods, the receiver is returned from the apply method, so it can be tacked on right after creation, like so:

val dude = Dude().apply {
name = “Dude”
age = 48
}

Proper ninja convenience! Readability: 5/5

Imagine if this was implemented in Swift; our configuration could be done like this:

let view = UIView().apply {
backgroundColor = .blue
isHidden = true
}

It’s not, of course, but let’s not sit and mope about that.

We can try to get as close as possible, with a method that takes a configuration block, to which it passes itself as a parameter, before finally returning self.

extension UIView {    func apply(block: (UIView) -> Void) -> UIView {
block(self)
return self
}
}

At the call site, this block can be written as a trailing closure, with the receiver as the anonymous parameter $0, which takes us about as close as we can get to Kotlins sugary syntax.

let view = UIView().apply {
$0.backgroundColor = .blue
$0.isHidden = true
}

Readability: 4/5. Once you’ve seen the Kotlin syntax, those $0s are a little like eyelashes poking your eyeballs.

Apply to everything!

protocol BlockConfigurable {}extension BlockConfigurable where Self: AnyObject {    func apply(block: (Self) throws -> Void) rethrows -> Self {
try block(self)
return self
}
}
extension NSObject: BlockConfigurable {}

Great. We’ve even added proper error propagation. Now it’s safe to try dangerous stuff inside that apply block.

From here, making this available for any type that is an AnyObject is a oneliner. By conforming NSObject, our apply method is instantly available on all views, and most other objects inside the kit.

As you might have guessed, this idea is not entirely unstumbled upon either. Then does precisely this, but the terminology is ´then´ instead of ´apply´. Pod install Then you’re good to go.

This article should probably end here.

But you promised me silliness?

Take #3 — KeypathConfigurable

Photo by Silas Köhler on Unsplash

I actually quite enjoy writing layout code of the kind from the first approach.

It’s readable and safe. What I don’t like is all the code required to make that possible, whether I’ve written it myself or brought it in as a dependency.

What I would really like is a generic solution, but with a similar syntax.

ReferenceWritableKeypath<Root, Value> to the rescue.

In Swift, a keypath is written with a backslash, followed by the type and property, dot separated;

\UIView.backgroundColor

The concrete type of that keypath will be ReferenceWritableKeypath<UIView, UIColor?>

If the type is known, it can be omitted, so we’re down to \.backgroundColor for our example.

Let’s construct a method that will configure any property, by keypath, and return self.

protocol KeypathConfigurable {}extension KeypathConfigurable where Self: AnyObject {    func sporting<T>(_ keyPath: ReferenceWritableKeyPath<Self, T>,
_ value: T) -> Self {
self[keyPath: keyPath] = value
return self
}
}

Now view configuration looks like this:

let view = UIView()
.sporting(\.backgroundColor, .blue)
.sporting(\.isHidden, true)

Is this any better than the previous approaches?

Probably not.

Now, let’s take it too far!

Take #4 — SubscriptKeypathConfigurable

What if we could replace that sporting method with regular subscript syntax?

protocol SubscriptKeypathConfigurable {}extension SubscriptKeypathConfigurable where Self: AnyObject {    subscript<T>(_ keyPath: ReferenceWritableKeyPath<Self, T>, 
_ value: T) -> Self {
self[keyPath: keyPath] = value
return self
}
}

Great, let’s try that out:

let view = UIView()[\.backgroundColor, blue][\.isHidden, true]

Hmm. Something tells me this won’t fly, come code review.

Photo by Will Myers on Unsplash

What did we just do?

Did it work?

…things can be expressed more clearly, more concisely, or in an alternative style that some may prefer.

Thank you for reading.

…becoming proper ninja!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store