A freestyle way to config UIView
This article is to introduce a freestyle way to set up views in iOS (by code).
Normally, a view is set up like this
label = UILabel()
label.text = "Hello, It's raining outside. Enjoy?"
label.textAlignment = .Center
label.font = .headerFont()
label.textColor = .headerColor()
view.addSubview(label)
Configuring a view sometimes includes a sequence of steps. We can rougly call it “chaining actions”. “Chaining actions”, in the general perception, is the execution of multiple actions on the same actor.
actor.doSomething()
.doNextStep()
.doFinalStep()
Based on that premise, we could think of this implementation
label = UILabel()
.config {
$0.text = "Hello, It's raining outside. Enjoy?"
$0.textAlignment = .Center
$0.font = .headerFont()
$0.textColor = .headerColor()
}
.addTo(view)
And
view.addSubview(label, withConfig: {
$0.text = "Hello, It's raining outside. Enjoy?"
$0.textAlignment = .Center
$0.font = .headerFont()
$0.textColor = .headerColor()
})
.addSubview(button, withConfig: {
$0.setTitle("Enjoy", forState: .Normal)
$0.addTarget(self, action: #selector(tapYes), forControlEvents: .TouchUpInside)
})
In fact, this style consumes more lines of code than the conventional approach. Yet, it reflects the idea more clearly. Those inside a closure config
implicitly mean they are in the same context, making the code more structured and readable.
Now, let’s codeeee
Basically, we would make some extensions over UIView
extension UIView {
func config(@noescape closure: UIView -> Void) -> UIView {
closure(self)
return self
}
func addTo(parent: UIView) -> Self {
parent.addSubview(self.nt_view)
return self
}
}
Sadly, the closure is not generic enough to config a UILabel
since it takes a UIView
as the parameter
(⟶ You cannot declare $0.text = "..."
because $0
is a UIView
. Do not think of casting it to UILabel
:D)
You may think of the keyword Self
to make the compiler infer the input’s type. Yes, you’re on the right way.
However, it’s a classic problem that Self
is only valid for protocol extensions
(Remember: implementing extensions with so-called generic things is kind of limited)
To overcome this issue, we create a protocol NTViewType
which requires a properties nt_view: UIView
. The idea is quite simple. We perform extensions on this protocol and the detail implementation operates on nt_view
. And the value nt_view
of UIView
is just itself.
protocol NTViewType {
var nt_view: UIView { get }
}
extension UIView: NTViewType {
var nt_view: UIView { return self }
}
extension NTViewType {
func config(@noescape closure: Self -> Void) -> Self {
closure(self)
return self
}
func addTo(parent: UIView) -> Self {
parent.addSubview(nt_view)
return self
}
func addSubview<T: NTViewType>(subview: T, @noescape config: T -> Void) -> Self {
nt_view.addSubview(subview.nt_view)
config(subview)
return self
}
}
Have fun and make your own styleeeee