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.
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.
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
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.
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
Screen Recording When My iOS App Is in Background with Replaykit
Avplayerlayer Shows Black Screen But Sound Is Working
How to Properly Handle a Nil Uiapplication.Sharedapplication().Keywindow
Allow Users to Send Messages to Multiple Users Simultaneously in a Messaging App
Convert Uiview to .Png in Swift
Custom Table View Row Action (Image)
Scrolling in Uicollectionview Selects Wrongs Cells - Swift
Uitextview as Inputaccessoryview Doesn't Render Text Until After Animation
Creating a Rtsp Client for Live Audio and Video Broadcasting in Objective C
iOS Swift App with More Photos: Performance and Storage Suggestions
How to Change the Number of Decimal Places iOS
Swift Computed Properties Cannot Be Used in Init
Horizontal Paging Uicollectionview with Automatic Item Size in a Vertical Stack View
Load Offline Cached JSON Using Afnetworking
Wkwebview Decidepolicyfornavigationaction Being Call After the Long Press Recogniser Ended
Sharing Image and Text to Facebook Messenger with Uiactivityviewcontroller Failing