BRStyle provides a simple application style support framework for UIKit.
Many applications rely on style guidelines involving color and font treatments.
The UIAppearance
system supports configuring some basic style settings, but
often falls short. BRStyle aims to make using style guidelines easier.
For the simplest case in an application with a single global style, you'd
customize a single BRUIStyle
instance when your application starts up and set
that as the default global style:
BRMutableUIStyle *mutableStyle = [BRMutableUIStyle new];
BRMutableUIStyleColorSettings *colors = [BRMutableUIStyleColorSettings new];
colors.primaryColor = [UIColor blueColor];
// configure more color styles as needed...
BRMutableUIStyleFontSettings *fonts = [BRMutableUIStyleFontSettings new];
fonts.actionFont = [UIFont systemFontOfSize:15];
// configure more font styles as needed...
// when configured, set as the global default
[BRUIStyle setDefaultStyle:mutableStyle];
That's quite a lot of code, so BRUIStyle
supports loading the configuration
from a JSON encoded file. Given a JSON file style.json
with contents like this:
{
"colors" : {
"primaryColor" : "#0000ffff"
},
"fonts" : {
"actionFont" : { "size" : 15 }
}
}
the setup code is then reduced to this:
BRUIStyle *style = [BRUIStyle styleWithJSONResource:@"style.json" inBundle:nil];
[BRUIStyle setDefaultStyle:style];
BRStyle does not try to replace UIAppearance
! In fact, you can use BRStyle as
a way to configure UIAppearance proxies. For example, you might do something like
this when your app starts up:
// load up style from JSON resource
BRUIStyle *style = [BRUIStyle styleWithJSONResource:@"style.json" inBundle:nil];
[BRUIStyle setDefaultStyle:style];
// configure UINavigationBar and UIToolbar style via UIAppearance
UINavigationBar *bar = [UINavigationBar appearance];
bar.tintColor = style.colors.inverseControlSettings.normalColorSettings.actionColor;
bar.barTintColor = style.colors.navigationColor;
[bar setTitleTextAttributes:@{
NSForegroundColorAttributeName: style.colors.inverseControlSettings.normalColorSettings.actionColor,
NSFontAttributeName: style.fonts.navigationFont,
}];
UIToolbar *toolbar = [UIToolbar appearance];
toolbar.tintColor = bar.tintColor;
toolbar.barTintColor = bar.barTintColor;
In fact, why not use the BRUIStyleAppearanceLoader
helper class to automatically apply appearance settings from a handy styles.json
file?
For example, take a (very simplified) BRStyle styles.json
file like this:
{
"default" : {
"colors" : {
"primaryColor" : "#ff0000ff",
"backgroundColor" : null
},
"fonts" : {
"actionFont" : { "name" : "Helvetica-Regular", "size" : 12 }
},
"controls" : {
"actionColor" : "#0000ccff",
"borderColor" : "#000000ff",
"glossColor" : "#ffffff33"
}
},
"MyCustomButton" : {
"fonts" : {
"actionFont" : { "name" : "Helvetica-Bold", "size" : 10 }
}
},
"UIPopoverController/MyCustomButton" : {
"fonts" : {
"actionFont" : { "name" : "Helvetica-Bold", "size" : 14 }
}
},
"MyCustomButton-highlighted|selected" : {
"controls" : {
"actionColor" : "#ff00ccff"
}
}
}
Setup your styles like this:
NSDictionary<NSString *, BRUIStyle *> *styles = [BRUIStyle registerDefaultStylesWithJSONResource:@"styles.json" inBundle:nil];
[[BRUIStyleAppearanceLoader new] setupAppearanceStyles:styles];
The default
key represent the global default style settings. All other keys represent
arbitrary named styles you can reference as needed. By passing the styles
dictionary to
BRUIStyleAppearanceLoader
, the keys are interpreted in specific ways. If the keys are
class names that conform to UIAppearance
and either BRUIStylish
or BRUIStylishControl
the style will be set on the UIAppearance
proxy for that class.
You can configure UIAppearanceContainer
hierarchies by using slashes before the class
name you want to style. In the example above, the MyCustomButton
class's style will
be configured with an actionFont
of size 10 by default, but when contained in a
UIPopoverController
it will have size 14.
You can also configure specific control states with classes conforming to BRUIStylishControl
by appending a -
followed by the state name to the key. Multiple states can be specified
by delimiting them with a |
character. In the example above, the MyCustomButton
class's
style when it is in both the highlighted
and selected
states.
BRStyle designates three main categories of style properties:
-
Colors: the
BRUIStyleColorSettings
class defines a set of color style properties, such asprimaryColor
,textColor
,captionColor
, etc. -
Fonts: the
BRUIStyleFontSettings
class defines a set of font style properties, such asactionFont
,textFont
,captionFont
, etc. -
Control colors: the
BRUIStyleControlStateColorSettings
class defines a set of style properties for different control (button) states, such asnormalColorSettings
andhighlightedColorSettings
. Each state is configured by aBRUIStyleControlColorSettings
class, which defines properties such asactionColor
andborderColor
.
For the impatient, by default BRStyle hooks into common system classes such
as UIButton
and UINavigationBar
and applies the default global style to them
for you. By merely including BRStyle in your project, you'll start to see
the default style applied to things.
BRStyle defines a couple of special protocols, BRStylish
and
BRStylishHost
, that are used to flag objects as being interested in being
styled. The protocol looks like this:
@protocol BRUIStylish <NSObject>
/** A BRUIStyle object to use. If not configured, the global default style should be returned. */
@property (nonatomic, strong, null_resettable) IBOutlet BRUIStyle *uiStyle UI_APPEARANCE_SELECTOR;
@end
@protocol BRUIStylishHost <BRUIStylish>
@optional
/**
Sent to the receiver if the @c BRUIStyle object associated with the receiver has changed.
*/
- (void)uiStyleDidChange:(BRUIStyle *)style;
@end
BRStyle defines some core system class categories on UIBarButtonItem
, UIView
,
and UIViewController
gives them all a uiStyle
property. Here's the
UIViewController
category as an example:
@interface UIViewController (BRUIStyle)
/** A BRUIStyle object to use. If not configured, the global default style will be returned. */
@property (nonatomic, strong, null_resettable) IBOutlet BRUIStyle *uiStyle;
@end
Thus you can easily apply styles to view hierarchy objects from any view
controller by simply referring to its uiStyle
property. If your view
controller then conforms to BRUIStylishHost
then the uiStyleDidChange:
method will be called on those controllers when their views load.
One more protocol is used specifically for classes that extend UIControl
: BRUIStylishControl
.
This protocol defines some support for control state specific styling, for example to change
the background color of a button while it is being pressed (i.e. in the highlighted
state). The
protocol looks like this:
/**
API for objects that act like controls, with style settings based on a UIControlState.
*/
@protocol BRUIStylishControl <NSObject>
/** Manage the BRUIStyleControlStateDangerous state flag. */
@property (nonatomic, getter=isDangerous) IBInspectable BOOL dangerous;
/**
Get a style for a control state. If a style is not defined for the given state, then
the style configured for the @c UIControlStateNormal state should be returned. If no
state is configured for the @c UIControlStateNormal state, then the global default
style should be returned.
@param state The control state.
@return The style associated with the given control state, or the defalut style if
nothing specific configured.
*/
- (BRUIStyle *)uiStyleForState:(UIControlState)state;
/**
Set a style to use for a specific control state.
@param style The style to use.
@param state The control state to apply the style settings to.
*/
- (void)setUiStyle:(nullable BRUIStyle *)style forState:(UIControlState)state UI_APPEARANCE_SELECTOR;
@optional
/**
Notify the receiver that the control state has been updated.
*/
- (void)stateDidChange;
/**
Notify the receiver that the style has been changed for a specific state.
@param style The updated style.
@param state The state the style is associated with.
*/
- (void)uiStyleDidChange:(BRUIStyle *)style forState:(UIControlState)state;
@end
As you can see, it adds a new control state dangerous
which can be used to easily style
buttons that preform a destructive operation.
BRStyle goes one step further from providing an easy way to access style
properties from your app: it can automatically apply those properties to your
classes. This support comes from some additional categories added to core system
classes such as UIButton
. Using buttons as an example, the
UIButton+BRUIStylishHost
category makes every button a BRUIStylishHost
:
// UIButton+BRUIStylishHost.h
@interface UIButton (BRUIStylishHost) <BRUIStylishHost>
@end
// UIButton+BRUIStylishHost.m
@implementation UIButton (BRUIStylishHost)
@dynamic uiStyle; // implemented in UIView+BRUIStyle!
- (void)uiStyleDidChange:(BRUIStyle *)style {
const BOOL inverse = ([self nearestAncestorViewOfType:[UINavigationBar class]] != nil || [self nearestAncestorViewOfType:[UIToolbar class]] != nil);
self.titleLabel.font = style.fonts.actionFont;
BRUIStyleControlStateColorSettings *controlSettings = (inverse ? style.colors.inverseControlSettings : style.colors.controlSettings);
[self setTitleColor:controlSettings.normalColorSettings.actionColor forState:UIControlStateNormal];
[self setTitleColor:controlSettings.highlightedColorSettings.actionColor forState:UIControlStateHighlighted];
[self setTitleColor:controlSettings.selectedColorSettings.actionColor forState:UIControlStateSelected];
[self setTitleColor:controlSettings.disabledColorSettings.actionColor forState:UIControlStateDisabled];
}
@end
In this way, core interface components will have their style configured via BRStyle.
If you prefer to style your objects yourself, then you can use just the
BRStyle/Core
Cocopod subspec (or don't include any of the +BRUIStylishHost
categories in your project). Then you can implement classes, or add extensions
to existing ones, that conform to BRUIStylishHost
and style them in the
uiStyleDidChange:
callback.
The BRStyleSampler
project included in the source repository includes a sample
application that shows how the styling can be used, and includes simple editing
functionality to modify the colors and see the changes applied. It can also
generate JSON from your style, so you could use that as a starting point for
your own app's style needs.