Replacing IBDesignable with Xcode Previews

If you’re still using IBDesignable and IBInspectable it’s time to migrate to Xcode Previews.

What Was IBDesignable?

I first wrote about IBDesignable back in 2015 when Apple introduced the feature in Xcode 6. The idea was to preview custom views live in Interface Builder. The example I used was a level indicator view that changed colour depending on the value of the level:

Power level at 50% with green progress bar

Power level at 20% with red progress bar

The LevelView is a custom UIView with some configurable properties:

class LevelView : UIView {
  var value: CGFloat { get set }
  var threshold: CGFloat { get set }
  var borderWidth: CGFloat { get set }
  var borderColor: UIColor { get set }
  var fullColor: UIColor { get set }
  var emtpyColor: UIColor { get set }
}

The problem is that when you add a custom view like this to Interface Builder it shows as an empty view. The only way to see what the view looked like was to build and run. Apple’s solution was to introduce designable views with inspectable properties. Adding the @IBDesignable annotation to the view and @IBInspectable to the properties:

@IBDesignable
class LevelView : UIView {
  @IBInspectable var value: CGFloat { get set }
  ...
}

The view implementation should then be visible live in the Interface Builder canvas allowing you to preview changes to the inspectable properties from the attributes inspector:

Level in Interface Builder with red progress bar and value property set to 0.2

IBDesignable Problems

When they worked, designable views did speed up development by avoiding the need to build and run on device or the simulator. The problem was that they were often unreliable, any time saved being lost debugging cryptic error messages.

In Xcode 15, designable views broke for me again. The simulator seems to expect an intel x86_64 architecture even when running on Apple Silicon:

LevelView IBDesignable error

Limiting the debug build to the arm64 architecture works around the problem but it’s not a good sign.

Architectures Debug setting is arm64

IBDesignable Deprecation

If you weren’t already thinking about moving away from IBDesignable the Xcode 15.1 release notes should convince you:

@IBDesignable views are deprecated and will be removed in a future release. (115873872)

Xcode doesn’t yet show a deprecation warning for views marked with @IBDesignable but I’m guessing it will not survive into Xcode 16.

Using Xcode Previews With Storyboards

The good news is that with Xcode 15 and iOS 17 the new Xcode Preview macro also supports UIKit view controllers and views. If you’re building your view programmatically you can return the view or view controller directly in the Preview macro:

@available(iOS 17, *)
#Preview {
  LevelView()
}

It’s not so simple if you’re building your view in a storyboard. For example, to show the view controller that contains my custom view I need to manually load it from the storyboard:

@available(iOS 17, *)
#Preview {
  let storyboard = UIStoryboard(name: "Main", bundle: nil)
  let vc = storyboard.instantiateInitialViewController() as! LevelViewController
  vc.loadViewIfNeeded()
  vc.levelView.value = 0.2
  return vc
}

Note also that you need to make sure the view controller has loaded its view before you can access any of the subviews added in the storyboard.

Xcode preview showing view controller in landscape left

It’s not as convenient as having the preview directly in the Interface Builder canvas but it will hopefully be a more robust solution than IBDesignable.

Learn More