Preparing custom view for Interface Builder - @IBDesignable and @IBInspectable

Photo by Dose Media / Unsplash

If you use Storyboards or Xibs, there is added benefit when the preview of your custom view is rendered right in Xcode. We can achieve that using @IBDesignable and @IBInspectable.

@IBInspectable make property visible and available to be modified in Attributes inspector in Xcode.

@IBDesignable makes UIView render itself in interface builder.

Supported types

In interface builder, we can use @IBInspectable with the following property types:

  • Int
  • CGFloat
  • Double
  • String
  • Bool
  • CGPoint
  • CGSize
  • CGRect
  • UIColor
  • UIImage

The list is pretty comprehensive, but it will not work with custom enums, which is very inconvenient in some scenarios.

Border view

We all use it, view with border and rounded corners, wouldn't it be nice to see results in the preview? With power on designables our changes will be visible!

@IBDesignable class BorderView: UIView {
    
    @IBInspectable var borderWidth: CGFloat {
        set {
            layer.borderWidth = newValue
        }
        get {
            layer.borderWidth
        }
    }
    
    @IBInspectable var borderColor: UIColor? {
        set {
            layer.borderColor = newValue?.cgColor
        }
        get {
            guard let color = layer.borderColor else { return nil }
            return UIColor(cgColor: color)
        }
    }
    
    @IBInspectable var cornerRadius: CGFloat {
        set {
            layer.cornerRadius = newValue
            layer.masksToBounds = newValue > 0
        }
        get {
            layer.cornerRadius
        }
    }
}

By adding @IBInspectable to properties and @IBDesignable to a custom class, Xcode knows it has to render our view in interface builder preview.

Now, when I set view class to BorderView, interface builder will show me the results live in preview!

Gradient view

@IBDesignable works with inheritance too. I'll make a new view - GradientView. I don't have to mark it @IBDesignable the second time. The new view will add a gradient to itself:

class GradientView: BorderView {
    
    private var gradientLayer: CAGradientLayer {
        return layer as! CAGradientLayer
    }
    
    override open class var layerClass: AnyClass {
        return CAGradientLayer.classForCoder()
    }
    
    @IBInspectable var startColor: UIColor? {
        didSet { gradientLayer.colors = gradientColors }
    }
    
    @IBInspectable var endColor: UIColor? {
        didSet { gradientLayer.colors = gradientColors }
    }
    
    @IBInspectable var startPoint: CGPoint = CGPoint(x: 0.0, y: 0.0) {
        didSet {
            gradientLayer.startPoint = startPoint
            
        }
    }
    
    @IBInspectable var endPoint: CGPoint = CGPoint(x: 1.0, y: 1.0) {
        didSet {
            gradientLayer.endPoint = endPoint
        }
    }
    
    private var gradientColors: [CGColor]? {
        guard let start = startColor, let end = endColor else { return nil }
        return [start.cgColor, end.cgColor]
    }
}

And the effect is amazing! As soon as I set both required colors, preview renders!

Smile view

Rendering work also with custom drawing, next view will show a smiled face!

@IBDesignable class SmileView: UIView {
    
    @IBInspectable var faceColor: UIColor = .black

    override func draw(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        let size = min(bounds.width, bounds.height) - 1
        let faceRect = CGRect(x: (bounds.width - size) / 2, y: (bounds.height - size) / 2, width: size, height: size)
        context?.addEllipse(in: faceRect)
        
        
        let eyesSize = size * 0.2
        
        let leftEyeRect = faceRect.inset(by: UIEdgeInsets(top: size * 0.3, left: size * 0.3, bottom: size - eyesSize, right: size - eyesSize))
        let rightEyeRect = faceRect.inset(by: UIEdgeInsets(top: size * 0.3, left: size * 0.7, bottom: size - eyesSize, right: size * 0.2))
        
        context?.addEllipse(in: leftEyeRect)
        context?.addEllipse(in: rightEyeRect)
        
        let smileStartPoint = CGPoint(x: leftEyeRect.origin.x, y: size * 0.6)
        let smileEndPoint = CGPoint(x: rightEyeRect.maxX, y: size * 0.6)
        context?.move(to: smileStartPoint)
        context?.addCurve(to:smileEndPoint,
                          control1: CGPoint(x: smileStartPoint.x + size * 0.1, y: smileStartPoint.y + size * 0.1),
                          control2: CGPoint(x: smileEndPoint.x - size * 0.1, y: smileEndPoint.y + size * 0.1))
        context?.setStrokeColor(faceColor.cgColor)
        context?.strokePath()
    }
}

I'm drawing a head, two eyes, and a smile inside draw(_ rect: CGRect), and the interface builder will render it right in preview!

I hope you liked my post, you can add me on Twitter!

Artur Gruchała

Artur Gruchała

I started learning iOS development when Swift was introduced. Since then I've tried Xamarin, Flutter, and React Native. Nothing is better than native code:)
Poland