Skip to content
MyFiziq edited this page Jun 11, 2018 · 7 revisions

Using InterfaCSS

How styling works

Giving elements style

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:

Applying style

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, an ISSRootView is automatically created.

Styling of cells in table views and collection views

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.

Hey, why can't InterfaCSS just take care of all this styling business for me?

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.

Stylesheets

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).

Scoped stylesheets

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];

"Live", auto-refreshable stylesheets

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.

Creating a view hierarchy

View setup can be done in a few different ways, depending on you preference:

Using ISSViewBuilder view builder methods

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"]
                ];
            }]
        ];
    }];
}

Using a view definition file

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>

Prototypes

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.

Using Interface Builder

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.

Layout of views

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.

Using variables

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"];

Extending InterfaCSS

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.