Preparing custom view for Interface Builder - @IBDesignable and @IBInspectable
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!