on
UIView styling with functions
Today I want to talk about UIView
styling. The common approach when we want to customize the display of native UI controls (for instance UIButton
or UILabel
) is to create a subclass that overrides a bunch of properties. That works well most of the time, but some problems may arise.
The filled and rounded button
We you use subclasses in Swift to style your views, you loose composition. For example, if you create two UIButton
subclasses, FilledButton
and RoundedButton
, how do you create a button that is both filled and rounded ?
One solution of this problem is to stop using subclasses, but to leverage Swift functions and type system.
We can think about a view style, as a function that consumes a view, and set some properties on it.
We can wrap this function into an object, for more control on it.
We can now create some styles for our filled and rounded button.
Now that we have our two styles for both filled and rounded buttons, we can create a new style for a rounded and filled button very easily.
What was previously impossible with UIButton
subclasses is now very straightforward using simple functions.
Improvements
Now that we get the general idea, it’s time for syntactic sugar!
First of all, for now our styles live in the global namespace. That’s not very scalable.
The solution here is to extend ViewStyle
and to constrain the generic type.
That’s nice, we have a namespace to list all our styles. But it’s not very handy to style a button yet.
To improve this, we can define a function that is responsible to apply a style to an object, inferring the type of the style based on the type of the object.
Protocols to the rescue
The code looks good and is readable. But we can go one step further! I want to get rid of the global style(_:with:)
function, and to use an instance method of UIButton
instead. For this, let’s define an empty protocol Stylable
, and make UIView
conform to it. That way we will be able to add methods to Stylable
and all the UIView
subclasses will get them for free.
That may seem a little odd, but we can now extend Stylable
to add a method to apply a style to any Stylable
instance.
All the UIView
subclasses gain this apply(_:)
method for free! The code becomes compact and readable.
What’s more, we can’t misuse our styles because of the Swift type system!
Init with style
With the previous apply(_:)
method you will often find yourself writing these two lines:
What if we could initialize our button (or any other UIView
) with a predefined style? It would save us one line of code each time.
That is possible, modifying slightly our Stylable
protocol.
We can now use the following syntax:
Conclusion
With view styles as plain Swift functions, we achieved two things:
- first, a technical improvement on view subclasses: the composition of two
UIView
subclasses was impossible, whereas it becomes very easy using Swift functions. - second, an easier communication between developers and designers. Indeed, designers often work with styles, in order to reuse components and keep a consistent look and feel all around the app. If you can extract their styles and map them in a Swift file, it will become much simpler to develop your UI, and to update these styles in the future.
You can find the full gist here.