Using IBDesignable & IBInspectable classes in Swift for Live Rendering Views in Interface Builder

This new journey with Swift and iOS has been awesome so far. The other day, I came across this super-helpful feature that enables you to see your “dynamic” view changes in Interface Builder! It is made possible through two declaration attributes used by the interface builder — IBDesignable and IBInspectable. These are available for use both in Swift and Objective-C. I decided to get some trials up with Swift.

The Past and the New:

If you can bear with a little history (no? Jump to the next section) -

If I (and I believe most developers) wanted a view-level change in the app,

  • had to code or manipulate in IB
  • and had to build and run the app,
  • navigate to the screen,
  • perform the logical actions

to see the change we wanted to have in the view.

Now, say I wanted to change a label’s text based on some custom flag, I needn’t go through these steps anymore. I can

  • have the label’s text and my custom flag exposed to IB
  • and then from IB, can change the values
  • and with a little bit of code,

voila, I can see how it changes in IB, let me stress, “in IB” not after running the app. In case you wonder, yes, the changes are available at run-time as well, this isn’t just a design-time tool.

The Possibilities:

  1. We can have any property of a view exposed to IB for tweaking and for rendering the view based on the tweaks in IB and during run-time.
  2. We can write code and have the “code” render the changes real-time in the IB. (super cool, if you ask me, no one around the web seems to be as excited by this point as the first, sigh!)

The first ensures that we needn’t set a lot of values through code and the second ensures that we needn’t run the app and navigate to the required screen to see what happens with the view. This power is immense since we are able to manipulate these with custom logic (not just simple getters and setters, mind you) and XCode still builds it and renders in real-time in IB.

Now, a little in detail. I created a simple view controller with a simple custom view. This view contained a label. I wanted to change the view’s properties and the label’s properties in run-time and in design-time.

Exposing the custom variables to IB:

All it needs is a little attribute called @IBDesignable in front of the class definition and @IBInspectablein front of the variable definition. For example,

@IBDesignable class RustyCustomView: UIView {
     @IBInspectable var labelBorderWidth: CGFloat = 5 {
         didSet {
             textLabel.layer.borderWidth = labelBorderWidth
          }
      }
}

And saving this class adds this new variable in the IB for the view. See this screenshot.

before you lose your enthusiasm with my example pick of “borderWidth”, let me tell you that you can add @IBInspectable to any variable and it will show up. Like,

@IBDesignable class RustyCustomView: UIView {
       @IBInspectable var labelBorderWidth: CGFloat = 5 {
            didSet {
                textLabel.layer.borderWidth = labelBorderWidth
            }
        }
        @IBInspectable var randomCustomVariable: Bool = false
}

And the result,

So, what is the impact of having these in IB? It is easier to work with custom properties and change their values in IB and see immediately how the view will look like. It saves us a lot of build-run-test cycles for simple view changes and layouts.

What if we needed a little more, say we wanted to change the label’s properties based on a custom flag setting? We need to code a little for this, but the plus-point is, with the view able to live-render, the end result is much more satisfying. That’s what is explained next.

Having code render in IB real-time:

I have the label’s text defaulted to a value first and then when the custom flag changes, I change it to something else by code. Since, the flag is exposed in IB, when I change its value there, I can see it change the label’s text immediately.

Since a picture can speak a thousand words, check the following two screenshots of my code and IB. Notice the three arrows and how the view changes based on the randomCustomVariable being true or false.

and now, change the flag in IB and..

Some Nuances:

  1. Change anything in the layoutSubViews() method and that code reflects in the IB. Immediately.
  2. XCode continuously builds the class modified by @IBDesignable and this might potentially cause some lags and discomfort in projects with a lot of resources.
  3. If we want to see changes only in IB, for testing different cases and not actually reflect in run-time, can add all we add to layoutSubViews() method inside another method called prepareforInterfaceBuilder(). The code here affects only during design-time.
  4. The @IBInspectable properties need an initial value. They throw compile errors when not set. (Or, I haven’t found a way to circumnavigate this problem yet)
  5. If we have the view’s definition controlled in a viewcontroller and still want to make use of this features, I guess this answer in Stackoverflow provides the easiest of the alternatives. In case the answer isn’t available anymore, here is a screenshot of what was suggested —

Code Sample:

The trial I have with these is at this Github location. I hope to update this repo if I find anything new with this feature. If you can’t access the link, here is the gist of the custom view.

import UIKit
@IBDesignable class RustyCustomView: UIView {
   let textLabel = UILabel()
   required init(coder aDecoder: NSCoder) {
         super.init(coder: aDecoder)!
         setUpTheView()
   }
   override init(frame: CGRect) {
          super.init(frame: frame)
          setUpTheView()
   }
   @IBInspectable var lableTextColor: UIColor = .blackColor() {
        didSet {
             textLabel.textColor = lableTextColor
        }
    }
   @IBInspectable var labelBorderWidth: CGFloat = 5 {
         didSet {
              textLabel.layer.borderWidth = labelBorderWidth
         }
    }
   @IBInspectable var randomCustomVariable: Bool = false
    private func setUpTheView() -> Void {
            self.backgroundColor = .clearColor()
            textLabel.text = “Hey, magic!”
            textLabel.textColor = lableTextColor
            textLabel.font = .systemFontOfSize(21)
            textLabel.textAlignment = .Center
            textLabel.numberOfLines = 0
            addSubview(textLabel)
      }
      override func layoutSubviews() {
             super.layoutSubviews();
             textLabel.frame = bounds
             self.syncWithIB();
      }

/* override func prepareForInterfaceBuilder() {
            self.syncWithIB();
     }*/
      func syncWithIB(){
            self.backgroundColor = UIColor.lightGrayColor();
            if(randomCustomVariable == true){
                    textLabel.text = “Wow, this works too”;
            }
       }
}

There is also an Apple Guide that explains live-rendering in Interface Builder.

I don’t think am going back to “not” using this super helpful feature anymore. Will save a load of time.


Originally published at gtlcodes.blogspot.in.