Chapter 40: @IBDesignable & @IBInspectable

Interface Builder is an amazing platform for building app UIs. But, in this chapter, you will learn how to make it even more useful.


What you will learn

  • Use @IBInspectable to allow for customization of properties in Interface Builder.
  • Use @IBDesignable to view your changes live in Interface Builder without running your app.

Key Terms

  • @IBInspectable
  • @IBDesignable

When building apps in Xcode, developers take one of two routes: 1) using Interface Builder and Storyboards or 2) creating everything from code.

Apple has done a lot of the hard work to bring us Interface Builder and Storyboards so that we can easily and visually create an amazing UI/UX for users. There are certainly upsides to creating user interfaces from code – such as making your code decisions very explicit and readable or preventing tiny, frustrating inconsistencies which can arise when working with numerous Storyboards.

But regardless of the potential nuisances of working with Interface Builder, it ultimately is a powerful tool that we can supercharge with the help of @IBInspectable and @IBDesignable.

In this chapter, we will create part of an onboarding experience where a user will need to grant permissions for certain device services. We will use @IBInspectable/@IBDesignable to customize and modify our design directly within Interface Builder – all without needing to build or run our app.

Creating a New Project and Set Up User Interface

To begin, open up Xcode and double-click Single View Application to create a new project. Name it whatever you'd like and save it anywhere.

Next, open up Main.storyboard and click on the ViewController in Interface Builder (Figure 5.8.1) and change the Background Color property to any color. I used #FFCC66.

Figure 5.8.1 Screen Shot 2016-10-07 at 8.40.22 PM.png

To start building our UI, drag a UIView into the ViewController. This will eventually house several buttons for granting permissions for location, camera, and notification access.

Select the view you've dragged in and position it so that it looks like the view in Figure 5.8.2 below.

Figure 5.8.2 Screen Shot 2016-10-07 at 8.44.08 PM.png

We want this view to stay in the center of our ViewController and to maintain it's distance from the edges of the screen, so click on our white UIView to select it and then click the Pin button(Screen Shot 2016-10-07 at 8.45.13 PM.png) at the bottom of the Xcode window.

Give the UIView constraints to ensure that it maintains its height and width on any screen size (Figure 5.8.3) and click Add 2 Constraints.

Figure 5.8.3

Screen Shot 2016-10-07 at 8.51.41 PM.png

Afterward, click the Align button (Screen Shot 2016-10-07 at 8.56.45 PM.png) to the left of the Pin button and select Horizontally in Container and Vertically in Container to force our UIView to stay in the center of the ViewController (Figure 5.8.4). Click Add 2 Constraints to set them in place.

Figure 5.8.4

Screen Shot 2016-10-07 at 8.57.40 PM.png

Once you have set up constraints on our view, the base interface for this app is now complete. We will now move on to create a custom class for this view and customize it with @IBInspectable and @IBDesignable.

Creating a Custom View Class

To allow us to modify our UIView we need to create a custom class which will house the default properties we want our view to adopt.

Right-click on your project's folder in the Navigator and click on New File... (Figure 5.8.5). Click iOS and then select Cocoa Touch Class. Click Next and name your class RoundedShadowView.

Pay attention to how the class is named with capital letters for each word – this is best practice when naming classes. In the Subclass section, delete whatever text is there and type UIView. It should auto-complete, but in case it doesn't ensure that you have typed it with the appropriate capital letters. Leave the Language drop-down menu with Swift selected. Click Next and click Create as this will save a new Swift file in your project directory.

Figure 5.8.5

Screen Shot 2016-10-07 at 9.05.19 PM.png

You now should have a new file called RoundedShadowView.swift in your project folder (Figure 5.8.6) which is a custom class we will use to customize our view.

Figure 5.8.6 Screen Shot 2016-10-07 at 9.12.03 PM.png

Delete all commented-out boilerplate code as we will not be needing it.

@IBInspectable

First, we want our view to have rounded corners so we will need properties for that. Create an @IBInspectable for the corner radius and a variable to store it's value like so:

import UIKit

class RoundedShadowView: UIView {

    @IBInspectable var cornerRadius: CGFloat = 0.0 {
        didSet {

        }
    }

}

We use didSet because it is a property observer meaning that it executes our view code when the cornerRadius property has been set by Interface Builder. Inside of didSet, add code to set the cornerRadius property of our view's layer to whatever value is set in Interface Builder.

import UIKit

class RoundedShadowView: UIView {

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

Just like that, we can now modify the corner radius of our view in Interface Builder! Don't believe me? Check it out!

Go to Main.storyboard and click on the white view you created earlier. Click on the Identity Inspector (Screen Shot 2016-10-07 at 9.29.14 PM.png) and change the class by typing RoundedShadowView and pressing the Return key as in Figure 5.8.7 below.

Figure 5.8.7

Screen Shot 2016-10-07 at 9.30.07 PM.png

Click on the Attributes Inspector icon (Screen Shot 2016-10-07 at 9.31.53 PM.png) and you will see that – ouila! – there is now a customizable property for Corner Radius!

Screen Shot 2016-10-07 at 9.37.45 PM.png

We now have an easy way to modify the cornerRadius of our view without needing to change the code inside RoundedShadowView.swift. Enter a value (like 25) into the Corner Radius text field and press Enter to set it. You probably noticed that nothing has changed in Interface Builder. That is because we have not yet implemented @IBDesignable.

In the next section, we will do just that.

@IBDesignable

Click on RoundedShadowView.swift and above the class declaration add @IBDesignable.

import UIKit

@IBDesignable
class RoundedShadowView: UIView {

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

Next, you need to override the method prepareForInterfaceBuilder() by adding the following code to your RoundedShadowView class.

import UIKit

@IBDesignable
class RoundedShadowView: UIView {

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

    override func prepareForInterfaceBuilder() {

    }
}

The method prepareForInterfaceBuilder() allows our view code to run inside of Interface Builder. But in order to do this, we must run the same code in two places. Once in our @IBInspectable and once in prepareForInterfaceBuilder().

So we don't violate the DRY principle (Don't Repeat Yourself), create a function called setupView() beneath prepareForInterfaceBuilder() like so:

import UIKit

@IBDesignable
class RoundedShadowView: UIView {

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

    override func prepareForInterfaceBuilder() {

    }

    func setupView() {

    }
}

We want to add our custom view code to be inside of setupView() instead of being inside of @IBInspectable like so:

import UIKit

@IBDesignable
class RoundedShadowView: UIView {

    @IBInspectable var cornerRadius: CGFloat = 0.0 {
        didSet {

        }
    }

    override func prepareForInterfaceBuilder() {

    }

    func setupView() {
        layer.cornerRadius = cornerRadius
    }
}

Finally, to allow for @IBInspectable to access our cornerRadius property and to allow for the changes to be viewable in Interface Builder (with the help of @IBDesignable) call the function setupView() in both places like so:

import UIKit

@IBDesignable
class RoundedShadowView: UIView {

    @IBInspectable var cornerRadius: CGFloat = 0.0 {
        didSet {
            setupView()
        }
    }

    override func prepareForInterfaceBuilder() {
        setupView()
    }

    func setupView() {
        layer.cornerRadius = cornerRadius
    }
}

Return back to Main.storyboard and allow for your app to build. Interface Builder is basically running your custom view code and seeing if there are any changes that need to be displayed.

Click on the white view we created earlier and in the Attributes Inspector (Screen Shot 2016-10-07 at 9.31.53 PM.png), set the Corner Radius property to 25.

Screen Shot 2016-10-07 at 9.59.32 PM.png

You should see the corners become rounded in Interface Builder without needing to run our app on a device (Figure 5.8.8)!

Figure 5.8.8

Screen Shot 2016-10-07 at 10.02.13 PM.png

Adding A Shadow to RoundedShadowView

Now that you understand the basic principles of adding an @IBInspectable to a custom view class, we will add several more lines of code to handle creating a shadow for our view.

Click on RoundedShadowView.swift and add the following lines of code to the RoundedShadowView class.

import UIKit

@IBDesignable
class RoundedView: UIView {

    @IBInspectable var cornerRadius: CGFloat = 0.0 {
        didSet {
            setupView()
        }
    }

    @IBInspectable var shadowColor: UIColor? {
        didSet {
            setupView()
        }
    }

    @IBInspectable var shadowRadius: CGFloat = 0.0 {
        didSet {
            setupView()
        }
    }

    @IBInspectable var shadowOffset: CGSize = CGSize(width: 0.0, height: 0.0) {
        didSet {
            setupView()
        }
    }

    @IBInspectable var shadowOpacity: Float = 0.0 {
        didSet {
            setupView()
        }
    }

    func setupView() {
        layer.cornerRadius = cornerRadius
        layer.shadowColor = shadowColor?.cgColor
        layer.shadowRadius = shadowRadius
        layer.shadowOpacity = shadowOpacity
    }

    override func prepareForInterfaceBuilder() {
        setupView()
    }
}

As you can see, we can create @IBInspectable properties for values of type UIColor, Float, CGSize, & CGFloat.


Helpful Tip: You can also modify values of type(s): Int, CGFloat, Double, String, Bool, CGPoint, CGSize, CGRect, UIColor, & UIImage.


Return to Main.storyboard and click on our white RoundedShadowView. In the Attributes Inspector (Screen Shot 2016-10-07 at 9.31.53 PM.png), you should now see various properties to modify our view's shadow (Figure 5.8.9).

Figure 5.8.9

Screen Shot 2016-10-07 at 10.20.20 PM.png

Set the following values to give our view a shadow (Figure 5.8.10):

  • Shadow Color: Dark Gray Color
  • Shadow Radius: 10
  • Shadow Opacity: 0.3

Figure 5.8.10

Screen Shot 2016-10-07 at 10.23.40 PM.png

Adding UIButtons & UILabels To Our RoundedShadowView

To add some interactivity to our pop-up view, we need to add a couple UIButtons and UILabels to allow our user to grant permissions for the camera, location services, and notifications.

Drag a UIButton onto our white RoundedShadowView and size it to fit as in Figure 5.8.11 below. My example has a height of 50 and a width of 250.

Figure 5.8.11

Screen Shot 2016-10-08 at 8.45.34 AM.png

Drag on two more UIButtons, resize them to match the first, and position them to have a bit of spacing as in Figure 5.8.12 below.

Figure 5.8.12

Screen Shot 2016-10-08 at 8.57.32 AM.png

To provide a message asking the user for permissions, drag on two UILabels positioned as in Figure 5.8.13 and make sure that the text is center aligned for both labels. Choose a font other than the System font (yuck).

Figure 5.8.13

Screen Shot 2016-10-08 at 9.01.38 AM.png

The top-most label should have a friendly, personal greeting like, "Hey! We need your help!" to give our app some personality. Feel free to change the font size to something larger or bolder to help it stand out.

The bottom label should say something about what the app needs to do like, "We need your permission to move forward. This lets our app work nicely for you." Typing a message that long will cause our label to stretch out beyond the bounds of our screen, so click and drag to resize the label so it fits back into our white RoundedShadowView (Figure 5.8.14). You will also need to change the Lines property of the UILabel to something like 3 or 4 so that it can fit inside nicely.

Screen Shot 2016-10-08 at 9.09.06 AM.png

Figure 5.8.14

Screen Shot 2016-10-08 at 9.10.41 AM.png

We haven't set up any constraints yet to keep our UIButtons or UILabels in place. Let's do that now. We will make this process much easier on ourselves by using a UIStackView.

Hold the Shift key and click on both UILabels and all three UIButtons. Then click the Stack button (Screen Shot 2016-10-08 at 9.13.39 AM.png) at the bottom of the Xcode window to put all five elements into a UIStackView.

When we do this, our design becomes really messy (Figure 5.8.15). It doesn't look like it should, but don't worry! We will fix that.

Figure 5.8.15

Screen Shot 2016-10-08 at 9.14.45 AM.png

Select your UIStackView from the Document Outline on the left side of the Interface Builder window (Figure 5.8.16). It is easier to select it from here than inside the Interface Builder at times.

Figure 5.8.16 Screen Shot 2016-10-08 at 9.19.34 AM.png

Now we need to constrain our UIStackView to fit nicely within RoundedShadowView. While it is still selected click the Pin button (Screen Shot 2016-10-07 at 8.45.13 PM.png) and set up your constraints to match mine in Figure 5.8.17 below:

Figure 5.8.17

Screen Shot 2016-10-08 at 9.26.25 AM.png

Now our UIStackView is looking better, but not exactly how we want it.

Screen Shot 2016-10-08 at 9.28.54 AM.png

Ensure that the UIStackView is selected and in the Attributes Inspector (Screen Shot 2016-10-07 at 9.31.53 PM.png) click on the drop-down menu for the Distribution property. Select Fill Proportionally to give each element in the UIStackView a proportional fill determined by it's size. After that, change the Spacing property to be equal to 50 (Figure 5.8.18).

Figure 5.8.18 Screen Shot 2016-10-08 at 9.34.10 AM.png

One thing to note is when creating a UIStackView, any constraints on any of the items placed inside the UIStackView are removed. So we need to re-add constraints for each of our UIButtons.

Hold Shift and select all three buttons. Then click the Pin button (Screen Shot 2016-10-07 at 9.31.53 PM.png) and give each button a height constraint of 50 as in Figure 5.8.19a/b.

Figure 5.8.19a

Screen Shot 2016-10-08 at 9.37.23 AM.png

Figure 5.8.19b

Screen Shot 2016-10-08 at 9.39.34 AM.png

Once that is complete, we can now create a custom class for our buttons to modify the view properties.

Creating a RoundedButton Class

In the Navigator, right-click on your project folder and click New File... (Figure 5.8.20a). Double-click Cocoa Touch Class and give the class a name of RoundedButton. Set it to subclass UIButton (Figure 5.8.20b). Click Next then click Create to save the class into your project directory.

Figure 5.8.20a

Screen Shot 2016-10-08 at 11.59.18 AM.png

Figure 5.8.20b

Screen Shot 2016-10-08 at 12.00.53 PM.png

Click on RoundedButton.swift and delete the commented out boilerplate code. We want this class to operate very similarly to our RoundedShadowView class, so we actually can copy and paste the @IBInspectable's, setupView() function, and prepareForInterfaceBuilder() function we already wrote from RoundedShadowView.swift to RoundedButton.swift.

The code inside of our class in RoundedButton.swift should now look identical to RoundedShadowView.swift (Figure 5.8.1a). But we need to add @IBDesignable to the top of our class. Now, add two extra @IBInspectable's. Create an @IBInspectable for both of UIButton's borderWidth and borderColor properties (Figure 5.8.1b).

Figure 5.8.21a

import UIKit

@IBDesignable
class RoundedButton: UIButton {

    @IBInspectable var cornerRadius: CGFloat = 0.0 {
        didSet {
            setupView()
        }
    }

    @IBInspectable var shadowColor: UIColor? {
        didSet {
            setupView()
        }
    }

    @IBInspectable var shadowRadius: CGFloat = 0.0 {
        didSet {
            setupView()
        }
    }

    @IBInspectable var shadowOffset: CGSize = CGSize(width: 0.0, height: 0.0) {
        didSet {
            setupView()
        }
    }

    @IBInspectable var shadowOpacity: Float = 0.0 {
        didSet {
            setupView()
        }
    }

    func setupView() {
        layer.cornerRadius = cornerRadius
        layer.shadowColor = shadowColor?.cgColor
        layer.shadowRadius = shadowRadius
        layer.shadowOpacity = shadowOpacity
    }

    override func prepareForInterfaceBuilder() {
        setupView()
    }
}

Figure 5.8.1b

//
//  RoundedButton.swift
//  IB-View
//
//  Created by Caleb Stultz on 10/8/16.
//  Copyright © 2016 Caleb Stultz. All rights reserved.
//

import UIKit

@IBDesignable
class RoundedButton: UIButton {

    @IBInspectable var borderWidth: CGFloat = 0.0 {
        didSet {
            setupView()
        }
    }

    @IBInspectable var borderColor: UIColor? {
        didSet {
            setupView()
        }
    }

    ...

    func setupView() {
        layer.borderWidth = borderWidth
        layer.borderColor = borderColor.cgColor
        ...
    }

    override func prepareForInterfaceBuilder() {
        setupView()
    }
}

Modifying UIButton Properties in Interface Builder

Now that we have our RoundedButton class finished, let's head over to Main.storyboard and set our buttons to inherit from the RoundedButton class.

With the Shift key held, click on all three buttons, then click on the Identity Inspector (Screen Shot 2016-10-07 at 9.29.14 PM.png) and in the Class field, type RoundedButton and press Enter.

All three buttons now have access to customizable properties in Interface Builder! Hold Shift and select all three UIButtons then open up the Attributes Inspector (Screen Shot 2016-10-07 at 9.31.53 PM.png). Set the following properties below (Figure 5.8.22). The yellow color used is the same as before (#FFCC66).

Figure 5.8.22

Screen Shot 2016-10-08 at 12.41.31 PM.png

Our buttons now have a rounded yellow border. But we want to change the text to look nice as well. Set the font to something other than the System font. I tend to favor Avenir. Next if you're looking for ideas, change the words to say something about the permissions you want to request. Set the font color to the same yellow color as the borderColor as in Figure 5.8.3.

Figure 5.8.23

Screen Shot 2016-10-08 at 12.48.04 PM.png

I went on to add an extra Done button (and added a shadow with our custom controls) at the bottom of RoundedShadowView to give the user a way to tell the app they were finished granting permissions (Figure 5.8.24). But that is entirely optional. I also had to reduce the UIStackView spacing value to 30 instead of 50 because of spacing issues.

Figure 5.8.24

Screen Shot 2016-10-08 at 12.53.15 PM.png

Wrapping Up

In this chapter, you've learned how to make your UI customizations show up in Interface Builder as well as give them custom controls in the Attributes Inspector. I'm sure you can see how powerful this can be for designing beautiful and custom UI controls. This is a really nice way for you to see what your UI will look like without needing to build and run your app over and over again to see small design changes.

Exercise

Create a new Xcode project and in Interface Builder, do your best to recreate this app's User Interface with the knowledge you now have regarding @IBDesignable and @IBInspectable. You should be able to complete this all without having to build the app once.

Slack for iOS Upload (3).jpg