Refactoring in Swift: Setup Closures
A quick idea on how a simple function can make your setup closures much neater
A classic sight at the top of a view controller is a list of views being instantiated and some properties being set on each:
let contentView: UIView = {
let v = UIView()
v.backgroundColor = .red
v.clipsToBounds = true
return v
}()let titleLabel: UILabel = {
let l = UILabel()
l.font = .systemFont(ofSize: 22)
l.textColor = .red
l.text = "WELCOME"
return l
}()let stackView: UIStackView = {
let s = UIStackView()
s.axis = .vertical
s.distribution = .fillEqually
s.alignment = .center
s.spacing = 8
return s
}()
When I look at that code, I can’t help but feel that there’s some redundant and duplicate information, specifically:
So, I created a function that can be used to make setup closures a lot neater:
func create<T>(_ setup: ((T) -> Void)) -> T where T: NSObject {
let obj = T()
setup(obj)
return obj
}
This function sits above everything and can instantiate any NSObject
subclass that doesn’t need arguments passed to it’s init
. Now if we were to re-write our setup closure list it will only have the bare minimum of scaffolding the compiler requires:
let contentView: UIView = create {
$0.backgroundColor = .red
$0.clipsToBounds = true
}let titleLabel: UILabel = create {
$0.font = .systemFont(ofSize: 22)
$0.textColor = .red
$0.text = "WELCOME"
}let stackView: UIStackView = create {
$0.axis = .vertical
$0.distribution = .fillEqually
$0.alignment = .center
$0.spacing = 8
}
This works by taking advantage of the following Swift language features:
- A function’s return value can be generically inferred by the type it is being assigned to, for example
func x<ReturnType>() -> ReturnType
- The last closure argument of a function can be written as a closure outside of the function, for example
array.forEach { … }
- Closure arguments don’t need to be named; it’s possible to use
$0
instead, for examplearray.compactMap { $0 }
So now the property’s type only needs to appear once and we don’t need to create a variable in the scope of a closure that we call immediately!