Search code examples
iosswiftuiviewxibibdesignable

Getting @IBDesignable UIView to draw without crashing Interface Builder


I'm attempting to configure a custom UIView in interface builder and get the view to render. Every time I attempt to do this with a custom Xib, I can get the xib to look fine in interface builder, but the moment I attempt to drop my MyCustomView into a UIVewController in interface builder, I get all sorts of compiler errors.

Every tutorial I've looked at goes something like this: "Just add @IBDesignable in front of your class declaration and put @IBInspectable in front of your vars and then watch the magic happen" (example: WWDC 2015 video). Or it's in Objective-C, or it's a 30 minute video in Aramaic with Chinese subtitles, etc.

My end objective is to be able to drag a UIView in, assign my custom class in identity inspector and have the contents of xib fill the UIView I dragged in and be able to tweak @IBInspectable properties.

Where I've looked for answers:

SO Post: @IBDesignable crashing agent

NS Hipster

A Medium Post

Errors I get:

Base.lproj/Main.storyboard: error: IB Designables: Failed to render and update auto layout status for PlaylistViewController (o1G-QR-OwX): The agent crashed

Base.lproj/Main.storyboard: error: IB Designables: Failed to update auto layout status: The agent crashed

Identity Inspector for my CurrentlyPlayingView: enter image description here

Storyboard of my view after the I get the aforementioned errors: enter image description here

The view that I'm trying to get to appear at the bottom of the above storyboard:

enter image description here

My code:

import UIKit

@IBDesignable class CurrentlyPlayingView: UIView {

    var view = UIView()

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var subtitleLabel: UILabel!
    @IBOutlet weak var progressSlider: UISlider!

    // IBInspectable attributes

    @IBInspectable public var titleLabelColor: UIColor = .black {
        didSet {
            self.titleLabel.textColor = titleLabelColor
        }
    }

    @IBInspectable public var subtitleLabelColor: UIColor = .black {
        didSet {
            self.subtitleLabel.textColor = subtitleLabelColor
        }
    }

    @IBInspectable public var sliderMinColor: UIColor = .blue {
        didSet {
            self.progressSlider.minimumTrackTintColor = sliderMinColor
        }
    }

    @IBInspectable public var sliderMaxColor: UIColor = .red {
        didSet {
            self.progressSlider.maximumTrackTintColor = sliderMaxColor
        }
    }

    // MARK: - Initialization

    override init(frame: CGRect) {
        super.init(frame: frame)
        xibSetup()
        initialConfig()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        xibSetup()
        initialConfig()
    }

    private func initialConfig() {
        self.titleLabel.textColor = titleLabelColor
        self.subtitleLabel.textColor = subtitleLabelColor
        self.progressSlider.minimumTrackTintColor = sliderMinColor
        self.progressSlider.maximumTrackTintColor = sliderMaxColor
    }

    // MARK: - Nib handlers

    private func xibSetup() {
        view = loadViewFromNib()
        view.frame = bounds

        // Make the view stretch with containing view
        view.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]

        // Adding custom subview on top of our view (over any custom drawing > see note below)
        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: "CurrentlyPlayingView", bundle: bundle)

        // Assumes UIView is top level and only object in CustomView.xib file
        let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
        return view
    }
}

If you've made it this far, thank you for reading. If you see what I'm missing, I welcome your suggestions.


Solution

  • I think this is because you are missing your prepareForInterfaceBuilder function.

    Override this function something like this.

    override func prepareForInterfaceBuilder() {
            super.prepareForInterfaceBuilder()
            //setup your view with the settings
        }
    

    Addendum by OP:

    Once you drag a UIView out to YourViewController and set its Custom Class in Identity Inspector to YourCustomView, ensure "Inherit from Target" is ticked (as of Xcode 8.x).