How we do UI style at Lyst
The iOS team at Lyst has been opening and sharing it's knowledge over the last months on different topics: lean approach, coding standards.
This time we want to show the way we approach UI styling on our iOS project.
How to configure your colours outside your ViewControllers
A lot of people in iOS still use the MVC pattern recommended by Apple. With this pattern, all your styling code will eventually fall into the Controller. There are other options: categories on Views, code in xibs or storyboards (and others).
We try to keep our ViewControllers as dumb as possible, this improves reusability. To do this, we need to rip the styling code from the VC, otherwise reusing a VC becomes impossible (if you want different background colour).
How do we do this? (and why should you?)
Apple created a protocol (to a proxy of a class) for all of us to be able to configure an element of our UI. The UIAppearance protocol can be found on UIView subclasses and allows us to:
[[UIView appearance] setBackgroundColour:[UIColour blueColour]];
You got it right, ALL your UIView subclasses will now have background colour blue.
When your view is about to be added onto the screen, iOS will call this method to be able to paint your view. (this has some gotchas that will be approached later, like UICells).
You can set any property that has:
as decoration on the header of a UIView subclass.
This is not very useful... unless...
[[LYTitleLabel appearanceWhenContainedIn:[LYProductView class], nil] setLYFont:TitleFont];
Now THIS is much more specific. Is it not? Totally determines what font a particular label inside another view should have.
(Have you realised that all those are "class" methods and that they don't need to be in your VC?).
Own your style
Take this approach all the way and... you guessed, you have a CSS-like file.
We call this a Theme. It's easy, put ALL your styling code inside your own class method and call it (before your app loads!). This way it's easy for someone to go and change colors, fonts and other detais without the risk of breaking something.
We need more power
You just went to UILabel and UIView header files to see what can and can't be configured with the appearance protocol?
Then you may have realised that you need more power. ("We need more power Scotty!").
That's why we have open sourced a set of Categories that we use to empower our styling. It's called Peacock.
With Peacock you will be able to configure more aspects of your UIView subclasses as the text align, text Line Break Mode, Line Spacing among many others. All those elements are exposed so it's easy to set them up.
UIAppearance works with UIView subclasses therefore we have to "typify" our views and labels in order to empower them.
This means that we need to create "type placeholders". This placeholders can be created in separated classes or inside your VC's.
@interface HelpButton : LYButton
// UIAppearance placeholder
@implementation HelpButton // UIAppearance placeholder @end
### Accessing values Sometimes we need to know what is the value of an "appearance-set property" before it's on the screen (for example when we want to set a UITableViewCell accessory view). But since the view it's not on the screen the property will be nil. How do we fix that? Luckily the proxy works in both directions. If we want to know the appearance property we can by: ```objectivec id appearance = [HelpButton appearance]; UIColor *backgroundColour = [appearance backgroundColour];
You will realise how limited is the available UILabel configuration. The way we approached the problem is by making all text an AttributedText. This way we can expose all the @properties of NSMutableParagraphStyle in the Peacock Categories header file so they can be set. Peacock and UIAppearance will handle all the blending and setup before adding the Label in the screen for you. If you check the properties available on NSMutableParagraphStyle you will realise there's much more stuff there than in a regular UILabel.
Peacock exposes some of those in order to easily access and set them. More can be added at your discretion and we will be more than happy to take contributors on the project.
NS_CLASS_AVAILABLE_IOS(6_0) @interface NSMutableParagraphStyle : NSParagraphStyle @property(readwrite) CGFloat lineSpacing; @property(readwrite) CGFloat paragraphSpacing; @property(readwrite) NSTextAlignment alignment;
Those are some examples. Then we need to set the text as Attributed or the configuration won't be applied to our UILabels:
[self.myLabel setAttributedTextUsingString:@"This will be very custom text"];
Messing with Apple
Some of the Apple controls or views are UIAppearance configurable (with appearance decorator on the heater file). Some are not.
BUT, at the end, there's a UIView subclass that gets added into the screen. Therefore you can set and configure more than what they show us.
Beware, if you do that, Apple may change a subclass or view hierarchy of it's controls and your app will stop behaving as expected on new iOS versions.
As shown in the Apple documentation for UIAppearance the OS will try to figure out the right configuration for a View. Try not to have multiple possibilities. When possible use the more concrete approach "appearanceWhenContainedIn:" rather than the regular "appearance".
Setting UILabels, UIButtons, UIViews or any other subclass directly may not be a good idea. When possible we recommend having placeholder subclasses of those. This approach will save you time and difficult to figure bugs.
UIAppearance is all about views on the Screen. Therefore despite the library being written in Objective-C if your app has views written in swift the configuration you did will carry on working. Check our example project to see how it works. We are aware that for fully Swift project we'll need to port the full library (this will come).
Peacock is just a bunch of Categories that will help you. Everything explained is a strategy (there's no silver bullet) to organise better your code.
Lyst iOS Team.
Contributions will be most welcome!