-
Notifications
You must be signed in to change notification settings - Fork 10
Using InterfaCSS
A UI element can be associated with multiple style classes and a single elementId. This information, along with the actual (implementation) class of the element is then used to match declaration in stylesheets with the element. The easiest way of manipulating the styling information of an element is to use the UIView+InterfaCSS
category.
The most commonly used properties and methods are probably these:
-
styleClassISS
- convenience property for setting a single style class for the element, or multiple classes separated by whitespace. -
elementIdISS
- property for getting/setting the element identifier -
addStyleClassISS
- adds a single style class, and schedules re-styling of the element -
removeStyleClassISS
- adds a single style class, and schedules re-styling of the element -
applyStylingISS
- applies styling to this view and its subviews, if not already applied.
If you need more control over how/when styling is applied, have a look at these properties and methods also:
-
willApplyStylingBlockISS
- specify a block to be run before styling. -
didApplyStylingBlockISS
- specify a block to be run after styling. -
disableStylingISS
- disable styling entirely of view. -
enableStylingISS
- re-enable styling of view. -
applyStylingOnceISS
- apply styling only once for the view (then disable styling). -
disableStylingForPropertyISS:
- disable styling of a specific property in a view. -
enableStylingForPropertyISS:
- re-enable styling of property. -
customStylingIdentityISS
- specify a custom styling identity to increase performance.
Whenever the styles of a view are changed (for instance when you call self.view.styleClassISS = @"myGroovyStyleClass"
), InterfaCSS will re-apply styles automatically. This is normally done asynchronously (to avoid doing too much re-styling), so if you need the styles to be applied right away, you have to call the method applyStylingISS
in UIView+InterfaCSS.h
.
There are also other cases when you might have to manually tell InterfaCSS to re-apply styles. For instance, if a view is thrown around in the view hierarchy (i.e. moved to a new super view), styling might not be applied to that view directly, and you must again call a method like applyStylingISS
to initiate styling. You can also use an ISSRootView.h
as the root of a view (sub) tree - this class makes sure that styling is applied whenever the view moves to a new super view, window or if the frame
property is changed is changed
Tip: If you create your view hierarchy using a view builder (and use
[ISSViewBuilder rootViewWithStyle:]
to create the root view) or if you use an XML view definition file, anISSRootView
is automatically created.
Table view and collection view cells are other examples of cases when you might have to manually apply styling. And depending on the complexity of your selectors, you may have to apply the styling at a specific time, namely after the cell has been added to the view hierarchy. For example, say that you have a defined a style for a label in a table view cell like this:
UITableView.mySpecialTable UITableViewCell .myStylishTitleLabel {
}
For the above selector to match your label, the cell must first have been added added to the view hierarchy, somewhere under its tableview. Thankfully, there's an easy way to ensure this - apply the styling in the willDisplayCell
delegate method:
// UITableViewDelegate:
- (void) tableView:(UITableView*)tableView willDisplayCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexPath {
[cell applyStylingISS];
}
// UICollectionViewDelegate:
- (void) collectionView:(UICollectionView*)collectionView willDisplayCell:(UICollectionViewCell*)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
[cell applyStylingISS];
}
Alternatively, you could also apply the styling in the didMoveToWindow
method of a custom cell class for instance.
Well, as a matter of fact, it can - by the use of a devious runtime manipulation technique called swizzling (note: InterfaCSS uses swizzling in a bit safer way than what is described in the linked article). But since InterfaCSS doesn't want to force such things on you, this feature needs to be enabled explicitly - you just have add ENABLE_INTERFACSS_VIEW_SWIZZLING=1
as a Preprocessor Macro (if you're using CocoaPods - do it in the Build Settings of the Pods project).
If you enable this feature, styling is automatically applied to any instance of UIView
(and its subviews), as soon as the view is added to a window.
To get styling information into your app, you must first tell InterfaCSS what stylesheet file(s) to load. The most common method is to use a stylesheet located in your application bundle (loaded using the method -[InterfaCSS loadStyleSheetFromMainBundleFile:]
), but you can also load stylesheets from an absolute file path in the local file system (-[InterfaCSS loadStyleSheetFromFile:]
). You can of course load as many stylesheets as you wish, and for larger applications you will likely want to use more than one stylesheet (and probably also scoped stylesheets - see below).
The most common place to load stylesheets is in the AppDelegate
, but you can also defer loading of stylesheets until the are actually needed. When a stylesheet is no longer needed, you can simply unload it (-[InterfaCSS unloadStyleSheet:refreshStyling]
) or mark it as inactive (set active
to NO
on the ISSStyleSheet
object).
Sometimes it can be useful to compartmentalize style information, and prevent it from leaking out and possibly interfering with other parts of the application. This can be accomplished by specifying a scope when loading a stylesheet. Doing so will make sure the styles it in are only processed for views under a particular view controller (for instance), and will make it possible for views outside the scope to quickly ignore the stylesheet (which will benefit performance).
ISSStyleSheetScope* scope = [ISSStyleSheetScope scopeWithViewControllerClasses:@[BraveNewViewController.class, MyOtherBraveNewViewController.class]];
[[InterfaCSS interfaCSS] loadStyleSheetFromMainBundleFile:@"stylesForOnePartOfTheApp.css" withScope:scope];
InterfaCSS also supports using "live", auto-refreshable stylesheets, which can be very useful to avoid those frustrating compile
/deploy
/launch
/returnToWhereYouWere
-cycles when you are fine-tuning your styles. Auto-refreshable stylesheets can either be loaded from a local file (that will be monitored for changes) or from a file located on a remote (HTTP) server (that will be polled at regular intervals). Note though that this feature is only intended for use during development, and it's recommended that you use this type of stylesheet in addition to your regular stylesheets (i.e. not instead of).
/* For local (simulator) use, you can for instance load the actual css file used in your project as an auto-refreshable stylesheet: */
[[InterfaCSS interfaCSS] loadRefreshableStyleSheetFromLocalFile:@"/Users/username/myCoolXcodeProject/myDazzlingStyles.css"];
/* Or if you want to be able to run on a device, you can for instance simply upload the file to your cloud provider of choice: */
[[InterfaCSS interfaCSS] loadRefreshableStyleSheetFromURL:
[NSURL URLWithString:@"http://www.mygroovycloudprovider.com/user/directory/mymyDazzlingStyles.css"]];
For more control over how auto-refreshable stylesheets are managed, have a look at the properties stylesheetAutoRefreshInterval
and processRefreshableStylesheetsLast
in InterfaCSS
.
View setup can be done in a few different ways, depending on you preference:
- In code with the help of the builder methods defined in
ISSViewBuilder
- By using an XML-based view definition file
- Using Interface Builder
ISSViewBuilder lets you programmatically create a view hierarchy in a quick and convenient way, that also makes it very easy to understand the layout of the view tree. You can easily assign created view to properties (see examples below) and you may optionally specify multiple style classes for each view (separated by space or comma). You can also associate views with elementIds, which primarily is used by ISSLayout
, but also makes it possible to auto populate properties in a view controller - all you have to do is specify the owner parameter when creating the root view.
Example of using ISSViewBuilder in the `loadView method of a view controller:
- (void) loadView {
self.view = [ISSViewBuilder rootViewWithStyle:@"mainView" withOwner:self andSubViews:^{
return @[
self.mainTitleLabel = [ISSViewBuilder labelWithStyle:@"mainLabel stdLabel"],
[ISSViewBuilder labelWithId:@"subLabel"],
[ISSViewBuilder viewWithStyle:@"contentView" andSubViews:^{
return @[
[ISSViewBuilder labelWithStyle:@"contentTitleLabel"],
[ISSViewBuilder labelWithStyle:@"contentSubtitleLabel"]
];
}]
];
}];
}
Another way of creating a view hierarchy is by using an XML-based view definition file. This way also have the benefit of making the view hierarchy very easy to understand, and just like the programmatic way, you can specify multiple style classes in the class
attribute and you can assign views to properties by using the property
attribute (fileOwner
is first attempted, then superview).
Using a view definition file, you also have the option of creating prototype views (use the prototype
attribute), which can be useful for table view cells for instance.
Example of loading a view hierarchy from a view definition file in the loadView
method of a view controller:
- (void) loadView {
self.view = [ISSViewBuilder loadViewHierarchyFromMainBundleFile:@"views.xml" withFileOwner:self];
}
And the file itself could look like this:
<view class="myGroovyRootView">
<label id="mainTitleLabel"/>
<label property="mainSubtitleLabel" class="mainSubtitleLabel"/>
</view>
When creating your view hierarchy using a view definition file, you have the option to use something called prototype views. This is pretty much what it sounds like - definitions of prototypes for creating multiple views with the same configuration (and subviews). This can be useful for many things, but one of the more common uses are for table and collection view cells. Example:
...
<tableViewCell prototype="exampleCell" class="exampleCellStyle">
<label property="label1" class="exampleCellLabelStyle1"/>
<label property="label2" class="exampleCellLabelStyle2"/>
</tableViewCell>
...
For dequeuing cells defined as prototypes, have a look at the methods provided by the categories UICollectionView+InterfaCSS.h
and
UITableView+InterfaCSS.h
.
Even if you decide to use Interface Builder to create your views, you can still get help from InterfaCSS with styling. By using IBInspectable
, InterfaCSS exposes the properties styleClassISS
and elementIdISS
to you, directly on the Attributes inspector "tab" in IB.
Note 1: To be able to use the
IBInspectable
properties exposed by InterfaCSS, your pod file needs to include the following:
platform :ios, "8.0"
use_frameworks!
Note 2: You can of course also use
User Defined Runtime Attributes
on the Identity Inspector tab in IB to set the above properties.
Until recently, InterfaCSS only provided a rather limited support for expressing layouts for views. However, as of version 1.0, InterfaCSS now supports a more advanced layout system, aptly dubbed ISSLayout.
What ISSLayout is/does:
- An easier way of expressing layouts, while still maintaining layout flexibility and power
- Lets you define your layout by using constant values combined with relationships to other elements and layout guides
- Resolves layouts to frames and respects transforms, which makes it easier to do view animations
- Lets you easily handle the most common layout scenarios, and provides support for post processing to handle edge cases
- Suitable for people who can visualize the layout from just abstract definitions
- Good if you are struggling with storyboards/nibs that are getting to cumbersome to work with
What ISSLayout is not:
- A direct replacement of Auto Layout
- Capable of expressing really complex layout definitions that can fully solve any conceivable layout scenario though a single massive Chuck Norris style layout definition
You can find more documentation on how to define layouts here. Also, have a look at the HelloISSLayout
sample code to see ISSLayout in action.
If you still prefer the more direct approach of setting frame
and center
properties etc, InterfaCSS has you covered here also - have a look at Rect and Point in the stylesheet reference. Using these properties can take you further than you think, as InterfaCSS provides support for more than simple absolute values.
Whenever a variable declaration is encountered in a stylesheet, it is not just used within the actual stylesheet - it is also registered for use in other stylesheet. But that's not all - it is also made available for you to use in the code:
// The raw, unprocessed value:
NSString* rawValue = [[InterfaCSS sharedInstance] valueOfStyleSheetVariableWithName:@"someVariableName"];
// Value transformed to a specific type:
UIColor* someColor = [[InterfaCSS sharedInstance] transformedValueOfStyleSheetVariableWithName:@"someVariableName" asPropertyType:ISSPropertyTypeColor];
// You can also set/change variables (raw string values)
[[InterfaCSS sharedInstance] setValue:@"red" forStyleSheetVariableWithName:@"someVariableName"];
You can extend InterfaCCS by for instance registering custom styleable properties and custom prefix key paths for nested properties, via the class ISSPropertyRegistry
, accessible through the property propertyRegistry
in InterfaCSS
.
Registering a simple KVC accessible property is easy:
[[InterfaCSS sharedInstance].propertyRegistry registerCustomProperty:@"radius" propertyType:ISSPropertyTypeNumber];
You can also register more advanced properties, by creating and registering an ISSPropertyDefinition
object.