Handling of awakeFromNib for the object that is File's Owner - ios

I know there are many related questions why awakeFromNib is not called when instantiating some view.
The message that the certain view is awaken from Nib is sent to the view itself and this message is not delivered to the File's Owner.
I saw Why won't my awakeFromNib fire?.
So, what will happen if you create an view's instance whose File's Owner is itself in the xib file?
In other word, you have your own custom view named MyCustomView.swift and MyCustomView.xib. And in the xib file, you set the file's owner to MyCustomView.
So, when you create the instance of MyCustomView, does awakeFromNib get called?
In my case, the awakeFromNib doesn't seem to be called.
However, the view itself is really instantiated. So, to me it is strange that the awakeFromNib is not called.
Could anyone explain this thing?
FYI:
I prepared BaseCustomView.swift.
BaseCustomView has two init.
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
and
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
And commonInit() is like this.
private func commonInit() {
// load custom view's xib
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: self.className(), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
addSubview(view)
// make custom view's size the same size with itself
view.translatesAutoresizingMaskIntoConstraints = false
let bindings = ["view": view]
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[view]|",
options:NSLayoutFormatOptions(rawValue: 0),
metrics:nil,
views: bindings))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[view]|",
options:NSLayoutFormatOptions(rawValue: 0),
metrics:nil,
views: bindings))
}
And customView is just the class which overrides this BaseCustomView.
In addition, the customView's File's Owner is customView itself.
More EDIT:
The custom view class is like this. And actually the awakeFromNib() is not called.
final class MyCustomView: BaseCustomView {
override func awakeFromNib() {
// do something
}
}

The "File's Owner" item in a nib is a bit special: it isn't a real object in the archive, as the other items are. It's a placeholder, that's filled in with a preexisting object when the nib is instantiated.
So, the File's Owner is not really being "awoken" from the nib as the other objects are. It's created before the nib is unarchived. Therefore, it doesn't really make sense for it to receive awakeFromNib.

Building on Josh and Kazuya's comments, File's Owner in a xib is obviously located under Placeholder and it is exactly that, a placeholder for the compiler to know there's an ivar on that xib that will have an object in memory to connect to once the xib is instantiated. And actually if you leave it blank, the AppDelegate owns it. File's Owner in my opinion is really obsolete nowadays in IB.
In the inspector on the right hand side, if you set File's Owner -> Custom Class -> Class to MyCustomView but don't set Custom Class -> Class of the xib's top-level hierarchy UIView to MyCustomView, then IBOutlet won't be connected and awakeFromNib() won't be called.
Image attached shows where MyCustomView MUST be set in order for any IBOutlets and awakeFromNib() as well as any other methods in MyCustomView.swift to be called

Related

How to allow subclasses to use parent class's nib

Say I have a parent view class, that contains at least 1 property:
class BaseView : UIView {
#IBOutlet weak var myLabel: UILabel!
}
This class has a corresponding xib file with an outlet connection made from the xib to the myLabel property.
Now let's say we also have some child classes that inherit from this class:
class ChildView : BaseView {
func setup() {}
}
ChildView has some custom logic but can reuse all of the views from BaseView. It doesn't (or I'd prefer to avoid it having) its own corresponding xib file.
I'd like to be able to do something like this:
let childView = Bundle.main.loadNibNamed(String(describing: BaseView.self), owner: nil, options:nil)?.first as! ChildViewA
but this doesn't work. Neither does:
let childView = ChildView()
Bundle.main.loadNibNamed(String(describing: BaseView.self owner: childView, options: nil)
Is there anyway to allow a child view to inherit from its parent view's xib file in a similar way?
The problem is that the root view in the nib is of type BaseView, so as! ChildViewA fails. Since you don't have access to the NSKeyedUnarchiver that the nib loader uses to unarchive the xib, there is no easy way to substitute your own class during unarchiving.
Here's a workaround.
Do not embed the BaseView itself in the xib. Instead, make the top-level view in the xib be a plain UIView, and set the File's Owner custom class to BaseView. Then delete all of the connections to the top-level view and set them on the File's Owner instead. Also give BaseView a rootViewFromNib outlet, and connect it to the root view.
Then, give BaseView an initializer that loads its nib and adds that rootViewFromNib to itself as a subview, with its frame pinned to the BaseView's own bounds. You can use autoresizing to do it.
In the end, BaseView should look like this:
class BaseView: UIView {
#IBOutlet var myLabel: UILabel!
// other outlets, etc.
#IBOutlet private var rootViewFromNib: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
Bundle(for: BaseView.self).loadNibNamed("BaseView", owner: self, options: nil)
rootViewFromNib.frame = bounds
rootViewFromNib.autoresizingMask = [.flexibleWidth, .flexibleHeight]
rootViewFromNib.translatesAutoresizingMaskIntoConstraints = true
addSubview(rootViewFromNib)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
and BaseView.xib should look like this:

Can a UIView load another nib after it's initialization?

Just a question to feed my curiosity. Can a UIView after it's initialization with a nib, load another nib again?
For example, this is where I initialize my View with a certain nib:
private func selfInit(){
Bundle(for: type(of: self)).loadNibNamed(fileName, owner: self, options: nil)
self.addSubview(contentView)
}
override init(frame: CGRect){
super.init(frame: frame)
selfInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
selfInit()
}
can I after the initialization of the nib with Bundle(...) I change the Nib again?
No Doubt that overriding the initializer to setup the initial values for setting default values for the class variables (including the UIViews) would be a good idea, but when it comes to adding a subview I would recommend to do it in the awakeFromNib() method:
Prepares the receiver for service after it has been loaded from an
Interface Builder archive, or nib file.
The nib-loading infrastructure sends an awakeFromNib message to each
object recreated from a nib archive, but only after all the objects in
the archive have been loaded and initialized. When an object receives
an awakeFromNib message, it is guaranteed to have all its outlet and
action connections already established.
Seems to be more appropriate method to add a subview in it; For instance, imagine that subview should has constraints with other components in the main view, you would need to guarantee that all components has been established.
Thus:
override func awakeFromNib() {
super.awakeFromNib()
selfInit()
}

Nib IBOutlet causes app to crash

Really need your help! I've been through every possible post addressing this issue and nothing seems to work.
So I'm using a .xib file to create a subview within ProfileFoldingCellView.swift, which works perfectly until I attempt to add an IBOutlet in the same class. Here's the code:
import Foundation
import UIKit
class ProfileFoldingCellView: UIView {
#IBOutlet var mainLabel: UILabel!
public var cellIndex: Int = 0
init(index: Int) {
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
cellIndex = index
print("Index: \(cellIndex)")
setupContentView()
}
func setupContentView() {
let contentView = UINib(nibName: "ProfileFoldingCellView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(contentView)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupContentView()
}
}
As you can see, the label IBOutlet is created without any issues and there are no other outdated or unused outlets roaming around.
http://imgur.com/a/jb9CA
I've also made sure to set ProfileFoldingCellView.swift as the file owner's class.
Everything runs perfectly until I link the outlet! Then I get this error:
http://imgur.com/a/6hHPG
Trust me, I've tried everything. I've probably re-created the outlet a million times, nothing is working. Any help would be greatly appreciated!
Leave your File's Owner as NSObject and set your view class as ProfileFoldingCellView, your mainLabel should be connected to the UIView object and not File's Owner
I fixed it!
I simply switched the line
let contentView = UINib(nibName: "ProfileFoldingCellView", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
with the following:
let contentView = Bundle.main.loadNibNamed("ProfileFoldingCellView", owner: self, options: nil)?[0] as! UIView
I kept the file owner's class as ProfileFoldingCellView
According to the image you posted, your mainLabel outlet is not connected to the label... It's connected to the files owner object which is probably your cell view and not a UILabel.
The answer is already mentioned in the upper section. I just want to illustrate it with images so that it can be more understandable.
Step1: Do not assign the class to the file owner and leave it as NSObject
Step2: Select your main view and Assign the class to that view
Step3: Now connect your outlet
Now it will not cause any kind of error related to IBOutlet :)

Load custom UIView from xib programmatically

I have created a custom UIView in MySample.xib. I have added the class MyView to the File Owner of xib.
MyView.swift
class MyView: UIView {
#IBOutlet var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup() {
NSBundle.mainBundle().loadNibNamed("MySample", owner: self, options: nil)
self.addSubview(self.view)
}
}
I am now loading this MyView from MyController file like this:
MyController.swift
class MyController: UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
view.addSubview(MyView())
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now to display this view, I am using to following code from another controller's UIButton:
presentViewController(MyController(), animated: true, completion: nil)
This does display the view on screen. But the problem is, it doesn't accept any user interaction. In my custom view, I have a UITableView which does display the data but it doesn't scroll or get tapped due to lack of user interaction.
Any idea what I am doing wrong?
There are some unnecessary things in your example.
I am still not sure what are you trying to do, but if you want to add a custom view from xib to your view controller then:
Create a view in a xib file , you don't need to override init , and you can't init view from xib using the default init UIView() , so please remove init method from your MyView class.
In your xib make sure that your view that you see in the IB is of the class type you want to use (i guess MyView class).
In your view controller init the view like this:
class MyController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Get all views in the xib
let allViewsInXibArray = NSBundle.mainBundle().loadNibNamed("MySample", owner: self, options: nil)
//If you only have one view in the xib and you set it's class to MyView class
let myView = allViewsInXibArray.first as! MyView
//Set wanted position and size (frame)
myView.frame = self.view.bounds
//Add the view
self.view.addSubview(myView)
//TODO: set wanted constraints.
}
}
You don't have to re-instantiate this twice
already if you using the design pattern.
It's so simple. Just write:
class MyController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Get all views in the xib
let view = MyView()
self.view.addSubview(myView)
//TODO: set wanted constraints.
}}
And It will work.
Instead of linking xib File's Owner class to MyView, I have to change the class of root view in xib to MyView. Then based on #Oleg Sherman code, it works perfectly with small changes of adding MyView() as owner to get all it's events, otherwise it will throw an error this class is not key value coding-compliant for the key ****.:
let allViewsInXibArray = NSBundle.mainBundle().loadNibNamed("MySample", owner: MyView(), options: nil)
Using File's Owner class to MyView is only required when you have to use the xib in Storyboard.
Not sure if there is a workaround to use File's Owner class to MyView when programmatically loading xib from custom controller like in my original question.

Load view from an external xib file in storyboard

I want to use a view throughout multiple viewcontrollers in a storyboard. Thus, I thought about designing the view in an external xib so changes are reflected in every viewcontroller. But how can one load a view from a external xib in a storyboard and is it even possible? If thats not the case, what other alternatives are availble to suit the situation abouve?
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.
For a while Christopher Swasey's approach was the best approach I had found. I asked a couple of the senior devs on my team about it and one of them had the perfect solution! It satisfies every one of the concerns that Christopher Swasey so eloquently addressed and it doesn't require boilerplate subclass code(my main concern with his approach). There is one gotcha, but other than that it is fairly intuitive and easy to implement.
Create a custom UIView class in a .swift file to control your xib. i.e. MyCustomClass.swift
Create a .xib file and style it as you want. i.e. MyCustomClass.xib
Set the File's Owner of the .xib file to be your custom class (MyCustomClass)
GOTCHA: leave the class value (under the identity Inspector) for your custom view in the .xib file blank. So your custom view will have no specified class, but it will have a specified File's Owner.
Hook up your outlets as you normally would using the Assistant Editor.
NOTE: If you look at the Connections Inspector you will notice that your Referencing Outlets do not reference your custom class (i.e. MyCustomClass), but rather reference File's Owner. Since File's Owner is specified to be your custom class, the outlets will hook up and work propery.
Make sure your custom class has #IBDesignable before the class statement.
Make your custom class conform to the NibLoadable protocol referenced below.
NOTE: If your custom class .swift file name is different from your .xib file name, then set the nibName property to be the name of your .xib file.
Implement required init?(coder aDecoder: NSCoder) and override init(frame: CGRect) to call setupFromNib() like the example below.
Add a UIView to your desired storyboard and set the class to be your custom class name (i.e. MyCustomClass).
Watch IBDesignable in action as it draws your .xib in the storyboard with all of it's awe and wonder.
Here is the protocol you will want to reference:
public protocol NibLoadable {
static var nibName: String { get }
}
public extension NibLoadable where Self: UIView {
public static var nibName: String {
return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
}
public static var nib: UINib {
let bundle = Bundle(for: Self.self)
return UINib(nibName: Self.nibName, bundle: bundle)
}
func setupFromNib() {
guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
}
And here is an example of MyCustomClass that implements the protocol (with the .xib file being named MyCustomClass.xib):
#IBDesignable
class MyCustomClass: UIView, NibLoadable {
#IBOutlet weak var myLabel: UILabel!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupFromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupFromNib()
}
}
NOTE: If you miss the Gotcha and set the class value inside your .xib file to be your custom class, then it will not draw in the storyboard and you will get a EXC_BAD_ACCESS error when you run the app because it gets stuck in an infinite loop of trying to initialize the class from the nib using the init?(coder aDecoder: NSCoder) method which then calls Self.nib.instantiate and calls the init again.
Assuming that you've created an xib that you want to use:
1) Create a custom subclass of UIView (you can go to File -> New -> File... -> Cocoa Touch Class. Make sure "Subclass of:" is "UIView").
2) Add a view that's based on the xib as a subview to this view at initialization.
In Obj-C
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]) {
UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:#"YourXIBFilename"
owner:self
options:nil] objectAtIndex:0];
xibView.frame = self.bounds;
xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview: xibView];
}
return self;
}
In Swift 2
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
xibView.frame = self.bounds
xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(xibView)
}
In Swift 3
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
xibView.frame = self.bounds
xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(xibView)
}
3) Wherever you want to use it in your storyboard, add a UIView as you normally would, select the newly added view, go to the Identity Inspector (the third icon on the upper right that looks like a rectangle with lines in it), and enter your subclass's name in as the "Class" under "Custom Class".
I've always found the "add it as a subview" solution unsatisfactory, seeing as it screws with (1) autolayout, (2) #IBInspectable, and (3) outlets. Instead, let me introduce you to the magic of awakeAfter:, an NSObject method.
awakeAfter lets you swap out the object actually woken up from a NIB/Storyboard with a different object entirely. That object is then put through the hydration process, has awakeFromNib called on it, is added as a view, etc.
We can use this in a "cardboard cut-out" subclass of our view, the only purpose of which will be to load the view from the NIB and return it for use in the Storyboard. The embeddable subclass is then specified in the Storyboard view's identity inspector, rather than the original class. It doesn't actually have to be a subclass in order for this to work, but making it a subclass is what allows IB to see any IBInspectable/IBOutlet properties.
This extra boilerplate might seem suboptimal—and in a sense it is, because ideally UIStoryboard would handle this seamlessly—but it has the advantage of leaving the original NIB and UIView subclass completely unmodified. The role it plays is basically that of an adapter or bridge class, and is perfectly valid, design-wise, as an additional class, even if it is regrettable. On the flip side, if you prefer to be parsimonious with your classes, #BenPatch's solution works by implementing a protocol with some other minor changes. The question of which solution is better boils down to a matter of programmer style: whether one prefers object composition or multiple inheritance.
Note: the class set on the view in the NIB file remains the same. The embeddable subclass is only used in the storyboard. The subclass can't be used to instantiate the view in code, so it shouldn't have any additional logic, itself. It should only contain the awakeAfter hook.
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
}
}
⚠️ The one significant drawback here is that if you define width, height, or aspect ratio constraints in the storyboard that don't relate to another view then they have to be copied over manually. Constraints that relate two views are installed on the nearest common ancestor, and views are woken from the storyboard from the inside-out, so by the time those constraints are hydrated on the superview the swap has already occurred. Constraints that only involve the view in question are installed directly on that view, and thus get tossed when the swap occurs unless they are copied.
Note that what is happening here is constraints installed on the view in the storyboard are copied to the newly instantiated view, which may already have constraints of its own, defined in its nib file. Those are unaffected.
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!
for constraint in constraints {
if constraint.secondItem != nil {
newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
} else {
newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
}
}
return newView as Any
}
}
instantiateViewFromNib is a type-safe extension to UIView. All it does is loop through the NIB's objects until it finds one that matches the type. Note that the generic type is the return value, so the type has to be specified at the call site.
extension UIView {
public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
if let objects = bundle.loadNibNamed(nibName, owner: nil) {
for object in objects {
if let object = object as? T {
return object
}
}
}
return nil
}
}
Although the top most popular answers works fine, they are conceptually wrong. They all use File's owner as connection between class's outlets and UI components. File's owner is supposed to be used only for top-level objects not UIViews. Check out Apple developer document.
Having UIView as File's owner leads to these undesirable consequences.
You are forced to use contentView where you are supposed to use self. It’s not only ugly, but also structurally wrong because the intermediate view keeps data structure from conveying it’s UI structure. It's like going against the concept of declarative UI.
You can only have one UIView per Xib. An Xib is supposed to have multiple UIViews.
There's elegant way to do it without using File's owner. Please check this blog post. It explains how to do it the right way.
I think about alternative for using XIB views to be using View Controller in separate storyboard.
Then in main storyboard in place of custom view use container view with Embed Segue and have StoryboardReference to this custom view controller which view should be placed inside other view in main storyboard.
Then we can set up delegation and communication between this embed ViewController and main view controller through prepare for segue. This approach is different then displaying UIView, but much simpler and more efficiently (from programming perspective) can be utilised to achieve the same goal, i.e. have reusable custom view that is visible in main storyboard
The additional advantage is that you can implement you logic in CustomViewController class and there set up all delegation and view preparation without creating separate (harder to find in project) controller classes, and without placing boilerplate code in main UIViewController using Component. I think this is good for reusable components ex. Music Player component (widget like) that is embeddable in other views.
Best solution currently is to just use a custom view controller with its view defined in a xib, and simply delete the "view" property that Xcode creates inside the storyboard when adding the view controller to it (don't forget to set the name of the custom class though).
This will make the runtime automatically look for the xib and load it. You can use this trick for any kind of container views, or content view.
Solution for Objective-C according to steps described in Ben Patch's response.
Use extension for UIView:
#implementation UIView (NibLoadable)
- (UIView*)loadFromNib
{
UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
xibView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:xibView];
[xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
[xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
[xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
[xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
return xibView;
}
#end
Create files MyView.h, MyView.m and MyView.xib.
First prepare your MyView.xib as Ben Patch's response says so set class MyView for File's owner instead of main view inside this XIB.
MyView.h:
#import <UIKit/UIKit.h>
IB_DESIGNABLE #interface MyView : UIView
#property (nonatomic, weak) IBOutlet UIView* someSubview;
#end
MyView.m:
#import "MyView.h"
#import "UIView+NibLoadable.h"
#implementation MyView
#pragma mark - Initializers
- (id)init
{
self = [super init];
if (self) {
[self loadFromNib];
[self internalInit];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self loadFromNib];
[self internalInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self loadFromNib];
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self internalInit];
}
- (void)internalInit
{
// Custom initialization.
}
#end
And later just create your view programatically:
MyView* view = [[MyView alloc] init];
Warning! Preview of this view will not be shown in Storyboard if you use WatchKit Extension because of this bug in Xcode >= 9.2: https://forums.developer.apple.com/thread/95616
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:
Import BFWControls framework
Change your superclass from 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 👣
This solution can be used even if your class does not have the same name as the XIB.
For example, if you have a base view controller class controllerA which has a XIB name controllerA.xib and you subclassed this with controllerB and want to create an instance of controllerB in a storyboard, then you can:
create the view controller in the storyboard
set the class of the controller to the controllerB
delete the view of the controllerB in the storyboard
override load view in controllerA to:
*
- (void) loadView
{
//according to the documentation, if a nibName was passed in initWithNibName or
//this controller was created from a storyboard (and the controller has a view), then nibname will be set
//else it will be nil
if (self.nibName)
{
//a nib was specified, respect that
[super loadView];
}
else
{
//if no nib name, first try a nib which would have the same name as the class
//if that fails, force to load from the base class nib
//this is convenient for including a subclass of this controller
//in a storyboard
NSString *className = NSStringFromClass([self class]);
NSString *pathToNIB = [[NSBundle bundleForClass:[self class]] pathForResource: className ofType:#"nib"];
UINib *nib ;
if (pathToNIB)
{
nib = [UINib nibWithNibName: className bundle: [NSBundle bundleForClass:[self class]]];
}
else
{
//force to load from nib so that all subclass will have the correct xib
//this is convenient for including a subclass
//in a storyboard
nib = [UINib nibWithNibName: #"baseControllerXIB" bundle:[NSBundle bundleForClass:[self class]]];
}
self.view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
}
}
class BYTXIBView: UIView {
var nibView: UIView?
// MARK: - init methods
override init(frame: CGRect) {
super.init(frame: frame)
commonSetup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonSetup()
}
func commonSetup() {
guard let nibView = loadViewFromNib() else { return }
nibView.frame = bounds
nibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(nibView)
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let className = type(of: self).className
let view = bundle.loadNibNamed(className, owner: self, options: nil)?.first as? UIView
return view
}
}

Resources