Live Render Iboutlet Connected Subviews via Ibinspectable Properties

Live Render IBOutlet Connected Subviews Via IBInspectable Properties

Unfortunately you can't see IBOutlet objects in interface builder for your custom views which are marked as IBDesignable. If you want to see your outlets in interface builder, you have to use regular variables instead IBOutlet and you have to create your objects programmatically.

Also please note that, if you need to change something from interface builder for your objects, you have to define your properties as IBInspectable. Currently following variables types are valid for IBInspectable:

Bool, CGFloat, CGPoint, CGRect, CGSize, NSInteger, NSString, UIColor, UIImage

I hope this answer is adequately clear for you.

Edit: I found following article which is describing a way how to do what you need:

http://justabeech.com/2014/07/27/xcode-6-live-rendering-from-nib/

2nd Edit: I tried the article and it works. Now I can see my outlets on interface builder

IB Designables for storyboard UITableViewCell: Failed to render and update auto layout status for CountdownViewController The agent crashed

This is happening because your dummyView is an IBOutlet and is implicitly unwrapped. prepareForInterfaceBuilder will be called before dummyView is initialized. You can prevent a crash by changing your code to dummyView?.backgroundColor = .red but then nothing will be rendered because dummyView == nil.

It doesn't make a ton of sense to mix IBDesignable with IBOutlet. In general, IBDesignable is meant to make run time layout and drawing visible at design time. But IBOutlets are necessarily already visible at design time. This might however be desirable in a xib. For a discussion of that see here and here.

Xcode - is it possible to debug crashes in the Interface Builder's Live Rendering process (IBDesignable)?

I watched the WWDC video again (§411 @22:00 or so). You have to

  • edit a view in IB, and set its custom class to a class in your codebase
  • set breakpoints as desired in your custom class
  • select the view in IB, then select Editor -> "Debug Selected Views" (at bottom)

Oddly, in my tests today, it is creating my view via (initWith)Frame instead of (initWith)Coder. This causes subsequent constraint configuration logic to fail as the sub views have not been set (as they would had 'withCoder been called).

Load view from an external xib file in storyboard

My full example is here, but I will provide a summary below.

Layout

Add a .swift and .xib file each with the same name to your project. The .xib file contains your custom view layout (using auto layout constraints preferably).

Make the swift file the xib file's owner.

Sample Image
Code

Add the following code to the .swift file and hook up the outlets and actions from the .xib file.

import UIKit
class ResuableCustomView: UIView {

let nibName = "ReusableCustomView"
var contentView: UIView?

@IBOutlet weak var label: UILabel!
@IBAction func buttonTap(_ sender: UIButton) {
label.text = "Hi"
}

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

guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
contentView = view
}

func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}

Use it

Use your custom view anywhere in your storyboard. Just add a UIView and set the class name to your custom class name.

Sample Image

Custom UIView subclass with XIB in Swift

I was able to work it around but the solution is a little bit tricky. It's up to debate if the gain is worth an effort but here is how I implemented it purely in interface builder

First I defined a custom UIView subclass named P2View

@IBDesignable class P2View: UIView
{
@IBOutlet private weak var titleLabel: UILabel!
@IBOutlet private weak var iconView: UIImageView!

@IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}

@IBInspectable var image: UIImage? {
didSet {
if iconView != nil {
iconView.image = image
}
}
}

override init(frame: CGRect)
{
super.init(frame: frame)
awakeFromNib()
}

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

override func awakeFromNib()
{
super.awakeFromNib()

let bundle = Bundle(for: type(of: self))

guard let view = bundle.loadNibNamed("P2View", owner: self, options: nil)?.first as? UIView else {
return
}

view.translatesAutoresizingMaskIntoConstraints = false
addSubview(view)

let bindings = ["view": view]

let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"V:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat:"H:|-0-[view]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: bindings)

addConstraints(verticalConstraints)
addConstraints(horizontalConstraints)
}

titleLabel.text = title
iconView.image = image
}

This is how it looks like in interface builder

Xib definition

This is how I embedded this custom view in the example view controller defined on a storyboard. Properties of P2View are set in the attributes inspector.

Custom view embedded in the view controller

There are 3 points worth mentioning

First:

Use the Bundle(for: type(of: self)) when loading the nib. This is because the interface builder renders the designables in the separate process which main bundle is not the same as your main bundle.

Second:

    @IBInspectable var title: String? {
didSet {
if titleLabel != nil {
titleLabel.text = title
}
}
}

When combining IBInspectables with IBOutlets you have to remember that the didSet functions are called before awakeFromNib method. Because of that, the outlets are not initialized and your app will probably crash at this point. Unfortunatelly you cannot omit the didSet function because the interface builder won't render your custom view so we have to leave this dirty if here.

Third:

    titleLabel.text = title
iconView.image = image

We have to somehow initialize our controls. We were not able to do it when didSet function was called so we have to use the value stored in the IBInspectable properties and initialize them at the end of the awakeFromNib method.

This is how you can implement a custom view on a Xib, embed it on a storyboard, configure it on a storyboard, have it rendered and have a non-crashing app. It requires a hack, but it's possible.



Related Topics



Leave a reply



Submit