I\'m relatively new in the XCode/iOS world; I\'ve done some decent sized storyboard based apps, but I didn\'t ever cut me teeth on the whole nib/xib thing. I want to use the sam
You're almost there. You need to override initWithCoder in your custom class you assigned the view to.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:@"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
}
return self; }
Once that's done the StoryBoard will know to load the xib inside that UIView.
Here's a more detailed explanation:
This is how your UIViewController
looks like on your story board:
The blue space is basically a UIView that will "hold" your xib.
This is your xib:
There's an Action connected to a button on it that will print some text.
and this is the final result:
The difference between the first clickMe and the second is that the first was added to the UIViewController
using the StoryBoard
. The second was added using code.
I've been using this code snippet for years. If you plan on having custom class views in your XIB just drop this in the .m file of your custom class.
As a side effect it results in awakeFromNib being called so you can leave all your init/setup code in there.
- (id)awakeAfterUsingCoder:(NSCoder*)aDecoder {
if ([[self subviews] count] == 0) {
UIView *view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil][0];
view.frame = self.frame;
view.autoresizingMask = self.autoresizingMask;
view.alpha = self.alpha;
view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
return view;
}
return self;
}
It's been a while on this one, and I've seen a number of answers go by. I recently revisited it because I had just been using UIViewController embedding. Which works, until you want to put something in "an element repeated at runtime" (e.g. a UICollectionViewCell or a UITableViewCell). The link provided by @TomSwift led me to follow the pattern of
A) Rather than make the parent view class be the custom class type, make the FileOwner be the target class (in my example, CycleControlsBar)
B) Any outlet/action linking of the nested widgets goes to that
C) Implement this simple method on CycleControlsBar:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if let container = (Bundle.main.loadNibNamed("CycleControlsBar", owner: self, options: nil) as? [UIView])?.first {
self.addSubview(container)
container.constrainToSuperview() // left as an excise for the student
}
}
Works like a charm and so much simpler than the other approaches.
A pretty cool and reusable way of doing this Interface Builder and Swift 4:
Create a new class like so:
import Foundation
import UIKit
@IBDesignable class XibView: UIView {
@IBInspectable var xibName: String?
override func awakeFromNib() {
guard let name = self.xibName,
let xib = Bundle.main.loadNibNamed(name, owner: self),
let view = xib.first as? UIView else { return }
self.addSubview(view)
}
}
In your storyboard, add a UIView that will act as the container for the Xib. Give it a class name of XibView
:
In the property inspector of this new XibView
, set the name of your .xib (without the file extension) in the IBInspectable field:
Add a new Xib view to your project, and in the property inspector, set the Xib's "File's Owner" to XibView
(ensure you've only set the "File's Owner" to your custom class, DO NOT subclass the content view, or it will crash), and again, set the IBInspectable field:
One thing to note: This assumes that you're matching the .xib frame to its container. If you do not, or need it to be resizable, you'll need to add in some programmatic constraints or modify the subview's frame to fit. I use snapkit to make things easy:
xibView.snp_makeConstraints(closure: { (make) -> Void in
make.edges.equalTo(self)
})
Bonus points
Allegedly you can use prepareForInterfaceBuilder()
to make these reusable views visible in Interface Builder, but I haven't had much luck. This blog suggests adding a contentView
property, and calling the following:
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
xibSetup()
contentView?.prepareForInterfaceBuilder()
}
File > New > New File > iOS > User Interface > View
File > New > New File > iOS > Source > CocoaTouch
loadNibNamed:
on NSBundle.mainBundle
and the first view returned can be added as a subview of self.view.The custom view loaded from the nib can be saved to a property for setting the frame in viewDidLayoutSubviews
. Just set the frame to self.view
's frame unless you need to make it smaller than self.view
.
class ViewController: UIViewController {
weak var customView: MyView!
override func viewDidLoad() {
super.viewDidLoad()
self.customView = NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)[0] as! MyView
self.view.addSubview(customView)
addButtonHandlerForCustomView()
}
private func addButtonHandlerForCustomView() {
customView.buttonHandler = {
[weak self] (sender:UIButton) in
guard let welf = self else {
return
}
welf.buttonTapped(sender)
}
}
override func viewDidLayoutSubviews() {
self.customView.frame = self.view.frame
}
private func buttonTapped(button:UIButton) {
}
}
Also, if you want to talk back from the xib to your UIViewController
instance then create a weak property on the custom view's class.
class MyView: UIView {
var buttonHandler:((sender:UIButton)->())!
@IBAction func buttonTapped(sender: UIButton) {
buttonHandler(sender:sender)
}
}
Here's the project on GitHub
While I don't recommend the path you're going down it can be done by placing an "embedded view controller view" where you want the view to appear.
Embed a view controller that contains a single view -- the view you want to be reused.