With the release of Xcode 6 Apple has greatly expanded the power of Interface Builder and the NIB loading runtime. Some of the changes include the support for design time previewing of custom components via an IBDesignable annotation, changing custom properties of views via IBInspectable annotations, and universal XIBs and storyboards. This article is the first in a series that details these changes and describes how you can use them in your apps.

For many years an argument has raged amongst Cocoa developers; use Interface Builder (IB) or build the views by hand in code. The proponents of IB like the benefit of being able to see what you're building without firing up the app. The proponents of build-by-hand like the absolute level of control that this approach gives. With the release of Xcode 6, it appears that Apple has strongly sided with the IB proponents. There are now many things that you can do in IB that are very difficult to accomplish in code, including building a universal interface specification that adapts to varying device sizes and orientations. But, that is for a later article in this series.

One of the great new capabilities of IB is the ability to preview and modify custom components at design time via @IBDesignable and @IBInspectable. This capability also provides a way to extend existing components and allow more complete configuration of these components without code.

Previewing Components

To create a component that can be previewed in IB you must extend UIView or a subclass of UIView. As an example for this article we'll make a UIView that draws a 5-pointed star of any color.

Because the iOS development community is in a transition between Swift and Objective-C this article will provide code samples in both languages. The code for the example project can be found in github at https://github.com/jack-cox-captech/IBDesignables

To define a designable component in Objective-C you'll need to annotate your header (.h) file with the IB_Designable annotation. In the example below, EllipseView.h, I'm building a view that draws a simple ellipse.

IB_DESIGNABLE

<span class="kw1">@interface</span> EllipseView <span class="sy0">:</span> UIView

<span class="kw1">@property</span> <span class="br0">(</span>nonatomic, retain<span class="br0">)</span> IBInspectable UIColor <span class="sy0">*</span>fillColor;

<span class="kw1">@end</span>

The IB_DESIGNABLE text before the interface definition tells IB that this view can be previewed. IB_DESIGNABLE and IBInspectable may be used together or separately within your views. The designable views for this article are all used in the Main.storyboard file. If you've included designable views in a XIB or Storyboard it can take a few seconds for those items to refresh each time you start editing the layout in IB. This delay will get longer as you add more classes and XIBs to your project.

For the Swift example, I'm making a view that draws a 5-pointed star. Because there is no header file for Swift classes, the annotations are specified on the class implementation. The example below, StarView.swift, shows how to annotate the class definition and any inspectable properties.

@IBDesignable class StarView : UIView {

@IBInspectable var starColor:UIColor

required init(coder aDecoder: NSCoder) {
starColor = UIColor.yellowColor();
super.init(coder: aDecoder)
}

override init(frame: CGRect) {
starColor = UIColor.yellowColor();
super.init(frame: frame)
}

Notice, that the starColor property is a non-optional property. Because it is not optional the class must provide a default value in the init methods. Inspectables may be either optional or required properties. They cannot be immutable properties.

In both EllipseView.m and StarView.swift, the classes implement the drawRect: method (see the full example project linked below) as any normal UIView class would. drawRect: is called by IB to render the contents of the view when it is being previewed.

In the view initialization of an IBDesignable view you can create complex subview hierarchies with constraints which the preview functionality will render in IB. For the subview hierarchies to be rendered correctly in IB you must implement them with auto-layout constraints, not with old-style auto-resizing properties. If you use auto-resizing properties they view will render correctly when displayed on the device, but they will not display correctly in IB.

The example below shows a view that uses the Ellipse view created above and adds a star view in the middle of it. The source file, StarEllipseView.swift is included in the code linked with this article.

@IBDesignable
class StarEllipseView: EllipseView {

var starView:StarView!

func buildHiearchy() {
starView = StarView(frame: CGRectInset(self.bounds, 5, 5))
starView.setTranslatesAutoresizingMaskIntoConstraints(false)
starView.backgroundColor = UIColor.clearColor()
self.addSubview(starView)

self.addConstraints([
NSLayoutConstraint(item: starView, attribute: .CenterX,
relatedBy: .Equal, toItem: self, attribute: .CenterX,
multiplier: 1, constant: 0),
NSLayoutConstraint(item: starView, attribute: .CenterY,
relatedBy: .Equal, toItem: self, attribute: .CenterY,
multiplier: 1, constant: 0),
NSLayoutConstraint(item: starView, attribute: .Width,
relatedBy: .Equal, toItem: self, attribute: .Width,
multiplier: 1, constant: -5),
NSLayoutConstraint(item: starView, attribute: .Height,
relatedBy: .Equal, toItem: self, attribute: .Height,
multiplier: 1, constant:-5)
])

}

required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
buildHiearchy()
}

override init(frame: CGRect) {
super.init(frame: frame)
buildHiearchy()
}
}

In my experience, the IBDesignable views must implement both initWithCoder: and initWithFrame: methods to display correctly within IB.

Extending IB

Notice the both StarView and EllipseView have properties that are marked with the IBInspectable attribute. Any compatible property marked with IBInspectable will have an entry in IB's attribute inspector panel. This allows you to set these properties at design time in IB rather than having to add code to the view controller's viewDidLoad: or view's init methods.

The example class, InspectableView.swift, creates several inspectable properties to show how they appear in IB. The basic classes of Int, String, Double, Bool can all be inspected and set in IB. Some not so basic classes, like UIColor and CGRect can also be inspected and set in IB. Other classes that are occasionally used to configure a view, like UIEdgeInsets, and CGAffineTransform are not supported by the IBInspectable functionality.

The code snippet below shows the types that may and may not be inspected.

@IBDesignable
class InspectableView: UIView {

@IBInspectable var myColor:UIColor!
@IBInspectable var myString:String = "A String"
@IBInspectable var anInteger:Int32!
@IBInspectable var floatingPointNumber:Double!
@IBInspectable var isHappy:Bool = true
@IBInspectable var anchorSpot:CGPoint!
@IBInspectable var usableArea:CGRect = CGRect(x: 0, y: 0, width: 100, height: 100)
@IBInspectable var aSize:CGSize!
@IBInspectable var nsString:NSString = "NSString"
@IBInspectable var badlynamedvariable:String!

@IBInspectable var cornerRadius:CGFloat = 0 {
didSet {
self.layer.cornerRadius = cornerRadius
}
}


// items that don't display in IB
@IBInspectable var style:StyleEnumeration = .StyleA
@IBInspectable var superConstraint:NSLayoutConstraint!
@IBInspectable var listValues:[String]!
@IBInspectable var fancyBorder:UIEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
@IBInspectable var victorVector:CGVector!
@IBInspectable var customTransform:CGAffineTransform! = CGAffineTransformIdentity
@IBInspectable var date:NSDate!

In IB, the InspectableView, renders a set of attribute controls in the attribute inspector panel, as shown below.

Notice that IB takes the property name that you define in the class and makes it a human readable value based on the camel casing of the name. So, name your inspectable property well and you'll be rewarded with a more meaningful IB inspector.

Views with IBInspectable do not have to be annotated with IBDesignable, but if you do combine them you get some powerful real-time design capabilities. For example, the cornerRadius property of the InspectableView implements a didSet method in Swift. Because the class is IBDesignable, Interface Builder is rendering the view live. So, when the didSet method alters the corner radius for the CALayer it changes the rendering of it live in IB.

Making Standard Views Inspectable and Designable

This capability exposes a very powerful feature of IB. There are many properties of standard views that are not exposed in IB. These include corner radius, border color and width, accessibility ID (valuable for test automation), and other. You can expose those values in IB without having to subclass all of the standard view components.

If you create an Objective-C category or Swift extension of UIView you can add additional controllable attributes to IB for all subclasses of UIView.

The example category, UIView+Inspectables.h, adds three new control points to any UIView in your app. These control points allow you to set the border width, border color, and accessibility ID. The interface for this category is:

@interface UIView (Inspectables)

@property (nonatomic, assign) IBInspectable NSString *testingId;
@property (nonatomic, assign) IBInspectable CGFloat borderWidth;
@property (nonatomic, strong) IBInspectable UIColor *borderColor;

@end

I'm calling the accessibility identifier, testingId since that is a common use of this value. borderWidth and borderColor will both map directly to CALayer properties. Notice that the category is not IBDesignable. Rather it just has IBInspectable properties.

To make it change values in real-time in IB, there needs to be a little implementation behind the property definition, shown below. Real-time changes are only supported on custom views, the standard views, UILabel, UIButton, UIImageView, etc. bypass these values so you do not see the effect at design-time, but you will see the effect at run-time.

- (void) setTestingId:(NSString *)testingId {
self.accessibilityIdentifier = testingId;
}
- (NSString *)testingId {
return self.accessibilityIdentifier;
}

- (void) setBorderColor:(UIColor *)borderColor {
self.layer.borderColor = borderColor.CGColor;
}
- (UIColor *) borderColor {
return [UIColor colorWithCGColor:self.layer.borderColor];
}
- (void) setBorderWidth:(CGFloat)borderWidth {
self.layer.borderWidth = borderWidth;
}
- (CGFloat) borderWidth {
return self.layer.borderWidth;
}

When IB previews any view, these methods are called and the new values set for the preview. At runtime, the NIB loader calls these methods as the NIB is decoded.

Keep in mind that it is not easy or recommended to add properties to a class via a category, but you can add methods that control existing properties. If you need to add properties then you should subclass UIView.

Once this category is added to your project you'll see a new section in IB for any subclass of UIView, shown below.

Again, IB interprets the camel cased names into pretty values and provides value controls that are appropriate for the type of the property.

With this capability you can control properties that normally require code. You can also add pseudo-properties that apply multiple values to a view so you can implement view styles that are consistent across your app.

In the example file, UIView+Inspectables.m, I define a pseudo-property named bigBorder. If this value is true then the view will have a 5px wide red border.

#define kBigBorderWidth 5
#define kBigBorderColor [UIColor redColor]
- (BOOL) bigBorder {
if ((self.layer.borderWidth == kBigBorderWidth) && (self.layer.borderColor==(kBigBorderColor.CGColor))) {
return YES;
}
return NO;

}

- (void) setBigBorder:(BOOL)bigBorder {
if (bigBorder) {
self.layer.borderColor = kBigBorderColor.CGColor;
self.layer.borderWidth = kBigBorderWidth;
}
}

Notice that the extension does not add a property to the UIView class, it computes the value for the bigBorder property by examining the current values of underlying properties. When bigBorder is set it only changes the underlying properties. It is possible at this point to use the Objective-C runtime functions objc_setAssociatedValue and objc_getAssociatedValue to add additional properties if required.

Also, notice in the code that the actions for setting bigBorder to false do nothing. If you use IB and set the value to false the border does disappear. This is apparently because IB will discard and then recreate the view object each time an inspectable property is changed.

Conclusion

With IBDesignable and IBInspectable, Interface Builder gives you the capability to preview custom views within IB. This capability should allow UI designers who are not coders to have a better view of the end results of their layouts.

The ability to add new properties via IBInspectable opens up some powerful capabilities to extend Interface Builder and control your views with less coding and more consistent styles.

Code for this project can be found at: https://github.com/jack-cox-captech/IBDesignables