Reputation: 2231
All of the resources I have found so far suggest variations of this code for loading a view from xib
file, but always implementing the same logic:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
var view = (Bundle.main.loadNibNamed("MyCustomView", owner: self, options: nil)![0])
self.addSubview(view as! UIView)
}
Which doesn't look right to me because this function belongs to a subclass of UIView, and adds the intended design (xib
's design) as a subview. In this case if we added a different UI element to self
, it would belong to a different parent than the UI elements in the xib
file.
So the hierarchy would look like this:
---self (UIView)
---Other UI elements added by code in `self`
---xib (UIView)
---UI elements in xib file
Isn't this an unnecessary wrapping? If so, is there a way to add all the views in xib
file to self
directly? Or is there a different more elegant way of loading design from a xib
file?
Upvotes: 0
Views: 6726
Reputation: 359
Here's the answer you've wanted all along. You can just create your CustomView
class, have the master instance of it in a xib with all the subviews and outlets. Then you can apply that class to any instances in your storyboards or other xibs.
No need to fiddle with File's Owner, or connect outlets to a proxy or modify the xib in a peculiar way, or add an instance of your custom view as a subview of itself.
Just do this:
UIView
to NibView
(or from UITableViewCell
to NibTableViewCell
)That's it!
It even works with IBDesignable to refer your custom view (including the subviews from the xib) at design time in the storyboard.
You can read more about it here: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155
And you can get the open source BFWControls framework here: https://github.com/BareFeetWare/BFWControls
And here's a simple extract of the NibReplaceable
code that drives it, in case you're curious:
https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4
Tom 👣
Upvotes: -1
Reputation: 3439
One way to reuse a complex set of subviews is to define an embedded view controller. You start by defining your top-level view controller. In it, you define an outlet and connected it to an instance of your sub-controller (also defined in the nib). You also connect the sub-controller's view
to a placeholder UIView
in the top-level nib's view hierarchy.
class ViewController: UIViewController {
@IBOutlet var childController: ReusableViewController?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
The slight-of-hand occurs in the sub-controller. Its awakeFromNib
function gets invoked when the super-controller is loaded. The sub-controller then uses the "placeholder" UIView
it is connected to to insert it's view hierarchy into the top-level views.
class ReusableViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func awakeFromNib() {
super.awakeFromNib()
// This controller manages a reusable view defined by a seperate nib.
// When defined in the parent nib, its view is connected to a placeholder view object.
// When the nib is loaded, the secondary nib is loaded and replaces the placeholder.
let placeholder = self.view!
Bundle.main.loadNibNamed("ReusableViewController", owner: self, options: nil)
// (the nib connects the view property to the actual view, replacing the placeholder)
placeholder.superview?.insertSubview(self.view, aboveSubview: placeholder)
// (do something about constraints here?)
placeholder.removeFromSuperview()
}
}
The advantage to this arrangement is that the sub-controller can have whatever bindings, outlets, actions, connections, properties, and business logic that it needs, neatly encapsulated and reusable.
This is a bit of a hack because it short-circuits the sub-controller's view-did-load lifecycle (because the controller never loads its own view). To address that, you could define another property that pointed the sub-controller at either a placeholder view or the container view that is should insert itself into. Then replace the Bundle.main.loadNibName(blah, blah, blah)
stuff with just let replacement = self.view
and let the view controller do all of the loading.
Here's a finished solution using storyboards contributed by the original poster after getting this to work
Using the reusable view in storyboard
Add a Container View
(Not to confuse with View
) to ViewController
(your main view on storyboard), and set it's scene's (controller) class to the controller we just defined (ReusableViewController
).
That's it. This way you can add as many subviews to a view without having to wrap your subviews in storyboards as well as in code.
Container View
's intended use is actually documented as exactly for this purpose here
Upvotes: 1
Reputation: 3050
You can load the xib of a view during init:
init() {
super.init(nibName: "ViewController", bundle: Bundle(identifier: "domain.AppName")!)
self.loadView()
}
Is that what your are looking for?
Upvotes: 0
Reputation: 100549
Generally when you implement a xib for a view , it always contains all the needed elements for it . but if you have you can try to add the element like this
var view = (Bundle.main.loadNibNamed("MyCustomView", owner: self, options: nil)![0]) as! UIView
var lbl = UILabel()
lbl.frame = //.....
view.addSubview(lbl)
self.addSubview(view)
Upvotes: 1