Style selectors are a way of specifying which views or view models should be targeted by your stylesheet. There are three main methods of targeting views:

  1. Object Class
  2. View Hierarchy
  3. Style Class

You can mix and match the above methods. This is an example that uses all three.

/* match views
 * where class is UIButton or UIButton subclass
 * and styleClass is "large"
 * and superview class is UITabBar
 */
 
UITabBar > ^UIButton.large { }
      

The term style selectors will be used when refering to selectors within Classy stylesheets. To avoid confusing them with Objective-C message selectors

This is an Objective-C class that you want to style. Often this will be one of the UIKit classes such as UIButton, UINavigationBar etc. However you can also style your custom project specific UIView subclasses and also NSObject subclasses such as UIBarButtonItem

Inheritance

UIKit classes utilise alot of inheritance, for example UISlider inherits from UIControl which in turn inherits from UIView. The default behaviour of style selectors is to ignore subclasses

/* match views of class UIControl
 * but not any subclasses such as UISlider, UIButton etc
 */

UIControl { }
        

However in some cases you may want to apply styling to all UIButtons including subclasses. So Classy introduces a new modifier character ^ which when place infront of a style selector will include subclasses

/* match views of class UIControl
 * OR any subclasses such as UISlider, UIButton etc
 */

^UIControl { }
        

An important way to distinguish between views is where they sit in the view hierarchy. For example a UILabel added to a UINavigationBar is likely to be styled alot differently to a UILabel which has been added to a UITableViewCell

You can choose whether or not the style selector should traverse up the view's superview chain when looking for matches. By default style selectors will traverse the superview chain.

/* match views of class UIActivityIndicatorView
 * where direct superview class is UINavigationBar
 * OR  ancestor superview class is UINavigationBar
 */

UINavigationBar UIActivityIndicatorView { }
        

However you can use the > modifier character to specify that you only wish to match direct superviews. This will be a bit quicker as the style selector will not traverse the superview chain.

/* match views of class UIActivityIndicatorView
 * where direct superview class is UINavigationBar
 */

UINavigationBar > UIActivityIndicatorView { }
        

You can inspect as many levels of the superview chain as you wish and mix both types of hierarchy style selectors.

UITableView ^UITableViewCell > UIImageView { }
        

You can also specify the type of UIViewController class/subclass you are expecting in the heirarchy

UITableViewController UIImageView { }
        

In some cases the object class and view hierarchy alone are not enough to distinguish a view. Sometimes its useful to give style selectors a bit more semantic meaning. For this reason every view has the option of setting an associated NSString property called cas_styleClass

From Code

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.cas_styleClass = @"primary";

From Interface Builder

You will need to set a runtime attribute for the element (in Identity Inspector > User Defined Runtime Attributes, click +). Set the Key Path = cas_styleClass, Type = String, and Value = primary

Stylesheet

The above button will be styled by the style selector below as it matches the style class .primary

/* match views of class UIButton
 * where cas_styleClass is set to @"primary"
 */

UIButton.primary { }
        

A fundamental principle of programming is to avoid repetition. This reduces duplication of code and leads to easier to maintain code. Classy has three features grouping, nesting and variables which help you to avoid repeating yourself. We will talk about variables in the next section of the docs.

Grouping

If two or more style selectors share common properties. For example

UISlider.info {
  minimum-track-tint-color black
  maximum-track-tint-color purple
}

UISlider.error {
  minimum-track-tint-color black
  maximum-track-tint-color purple
  thumb-tint-color red
}
        

We can extract the common properties into a grouped style selector

UISlider.info, UISlider.error {
  minimum-track-tint-color black
  maximum-track-tint-color purple
}

UISlider.error {
  thumb-tint-color red
}
        

Nesting

If two or more style selectors share common view hierarchy. For example

UICollectionView {
  background-color #a2a2a2
}

UICollectionView > UICollectionViewCell {
  clips-to-bounds NO
}

UICollectionView > UICollectionViewCell UILabel {
  text-color purple
}

UICollectionView > UICollectionViewCell UILabel.title {
  font 20
}

        

By using nesting we can express these view hierarchies in a more natural fashion

UICollectionView {
  background-color #a2a2a2
  
  > UICollectionViewCell {
    clips-to-bounds NO
    
    UILabel {
      text-color purple
      
      &.title {
        font 20
      }
    }
  }
}

        

& Modifier

The ampersand modifier character is a way of specifying that the nested style selector should be joined directly to its parent style selector with no spaces.

A view may be targeted by more than one style selector. The style selectors will be applied to the view in order of their precedence score. This score is generated by evaluating how specific a style selector is.

Consider the following button and stylesheet. The button will be targeted by all of the style selectors in the stylesheet

UIView *view = UIView.new;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.cas_styleClass = @"main";
[view addSubview:button];
UIView > UIButton {
  title-color blue
  background-image bg_button_blue
}

UIButton {
  title-color red
  background-image bg_button_red
}

UIButton.main {
  title-color yellow
}
        

The style selectors will be applied in the following order:

Style Selector Score
1. UIButton.main 3004
2. UIView > UIButton 7
3. UIButton 4

Which results in:

backgroundColor == yellow AND backgroundImage == bg_button_blue

Precedence Scoring

Score Description Conditions
+3000 styleClass match on view
+2000 styleClass match on direct superview
+1000 styleClass match on ancestor superview
+4 objectClass match on view
+3 objectClass match on direct superview
+2 objectClass match on ancestor superview
-2 objectClass matches subclasses as well