Endless recursive calls to initWithCoder when instantiating xib in storyboard - ios

In order to re-use a certain subview throughout my application (which is storyboard based), I decided to build the subview as a nib, and load it in. To do this, I have done the following:
I have a UIView subclass we can call Widget. I create a corresponding xib file, set the File owner property to my new subclass, hook up the IBOutlets.
Then, in my storyboard, I have a uiview inside of a view controller, and I set its class to the Widget class I created.
Within the widget class, I override initWithCoder, and in there load the nib as follows:
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])){
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"Widget" owner:self options:nil] objectAtIndex:0]];
}
return self;
}
The app would crash at this point, and setting a break point here revealed that initWithCoder was being called over and over again.
It seems like I have mixed two methods for using a nib in this situation, but I'm unclear as to where I went wrong. I can throw up a stack trace if necessary, but it's basically endless nested calls to the same function.

Did you happen to set the View's "Custom Class" in your .xib file to "Widget"?
That would explain the behaviour you're seeing, because initWithCoder: is the initializer for all things loaded from a xib:
Your parent view, which contains a Widget object, is loaded from the xib
The Widget's initWithCoder: method gets called, and tries to load the Widget xib
The Widget xib contains a UIView with the Custom
Class "Widget", so again, a Widget object is being initialized with
initWithCoder:, etc.
If that is indeed the case, all you have to do is remove the "Custom Class" entry for the UIView in your Widget's xib.

I had same problem. My mistake was in empty File's Owner class. In File's Owner custom class must be NOT empty (Widget), and root view must be empty.
IBActions and IBOutlets were fine.

Related

Nib loaded, outlets missing

In my project some UITableViewCell have some complex subview structure and gesture action, so I decided to use a layer of "cell controller" objects (subclasses of NSObject) to manage the cells and cell-layer gestures, as well as drawing the cell in a xib file.
Now when I load the cell from the nib with cell controller as its owner (the xib is properly set up) I found the outlets not connected. Why?
Or, is there any method that is called on the owner object when a nib is loaded and outlets is set up?
Nib is loaded like this:
cell = [[NSBundle mainBundle] loadNibNamed:#"HistoryCellActive"
owner:self
options:nil][0];
The nib-loading infrastructure sends an awakeFromNib message to each object recreated from a nib archive. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet and action connections already established.
You may try to override awakeFromNib method in the HistoryCellActive implementation file and put a breakpoint here. If cell xib file is correct the debugger stops in this breakpoint and you can check all cell's outlets. If no then you have to check xib for mistakes.
Don't forget to call [super awakeFromNib]; at the beginning of awakeFromNib implemetation.

Connect UIView in XIB and load it using View Controller in the Story board

I have a XYZViewController (simple UIViewController in storyboard) that is loaded up with the default view. I have a type XYZView for which I have UIView in a .xib file.
In the XYZViewController class, I have defined property for XYZView as an IBOutlet. What is tricky is I don't know how to connect this property to the UIViewController in storyboard (or UIVIew in .xib file) such that —
the IBOutlet is connected to the right UIView
the view in the xib becomes an added subview for the default view of the UIViewController.
(I under the question sounds dodgy and/or I may not have the very right way to explain it, but that's the best I could.)
EDIT: Further clarification may make it easier. I just don't want to myself say:
XYZView *xyzView = [[XYZView alloc] initWithFrame...];
or
[self.view addSubview:xyzView];
Maybe that helps.
OK, from what I tell you have the following...
XYZViewController
The code of this is in XYZViewController.h and .m files.
A .storyboard file
In the storyboard file you have a view controller that you have set the subclass to XYZViewController.
A .xib file
In the xib file you have a single view that you have defined as the subclass XYZView.
Right?
I'm guessing what you have done is the following...
In the .xib file you have laid out the XYZView and put labels, buttons, etc... on it.
The view controller is being created by the storyboard. But now you want to attach the labels and buttons to it.
Right?
If all this is correct then you have a couple of options.
The easiest option
Drop the xib file. Unless that XYZView is being used in multiple places in the app (i.e. inside different view controllers) then you should really be doing all of that layout in the storyboard. Add the buttons and labels to the XYZViewController in the storyboard.
This will then allow you to connect the IBOutlets and IBActions and it will all just work because the storyboard is creating and then setting the outlets and actions.
Next option
Because you have created the view in a xib file you have to load it from that xib file in code and then add it to you view controller.
Something like...
- (void)viewDidLoad
{
[super viewDidLoad];
self.xyzView = [[[NSBundle mainBundle] loadNibNamed:#"XYZView" owner:self options:nil] objectAtIndex:0];
[self.view addSubview:xyzView];
}
Then you can do stuff like ...
self.xyzView.someLabel.text = #"This is the text";
You still won't be able to connect outlets and actions but that's because the view controller is not being created by the xib. It's being created by the storyboard.
This can get all messy though. I'd really only recommend creating a view in a separate xib if it's something that you reuse over and over (like a 5star rating view or something).
What you absolutely can't do
OK, I think I may have thought of what you are doing.
In the storyboard you have set the subclass of the view as XYZView and you are expecting it to pick up the labels and buttons etc... that you have defined in the xib file for XYZView.
This absolutely will not work, ever.
The storyboard and the xib are completely separate objects. If you want to use them together then code is involved in loading a view from a nib and then adding it to a view controller created in a storyboard.

How does NSBundle set pointers to view objects in instance variables when loading a nib file?

I'm working through a book and currently adding a view to a tables header programmatically. So I've setup the XIB file added a view resized it and added two subViews/UIButtons.
Interface file:
#import <Foundation/Foundation.h>
#interface ItemsViewController : UITableViewController
{
IBOutlet UIView *headerView;
}
- (UIView *)headerView;
- (IBAction)addNewItem:(id)sender;
- (IBAction)toggleEditingMode:(id)sender;
#end
Part of my implementation:
- (UIView *)headerView
{
// If we haven't got the headerView yet
if (!headerView) {
[[NSBundle mainBundle] loadNibNamed:#"HeaderView" owner:self options:nil];
}
return headerView;
}
In the Big Nerd book it states:
“The first time the headerView message is sent to the ItemsViewController, it will load HeaderView.xib and keep a pointer to the view object in the instance variable headerView.”
What I'd like to know is how is how it sets/keeps a pointer to the view object in the headerView instance variable?
I know the nib file is being is being loaded by passing it into the loadNibName parameter as a string but how is the pointer being set to the object in my headerView instance variable?
When I connect the file owner to headerView is that like creating an instance of UIView and storing it in the headerView instance variable programmatically?
I found it really hard to word the question but I hope it makes sense after reading this post.
Kind regards
The -loadNibNamed:owner:options: method on NSBundle will read a nib file, instantiate the objects defined in it, and set any connections the nib file defines between those objects or between the loaded objects and the available proxy objects (like "File's Owner"). In this case you are passing self (the current ItemsViewController instance) as the owner argument. That ItemsViewController will then be the "File's Owner" when objects are loaded from this nib file.
If you look at the nib in Xcode you should see that one of the UIViews defined in that nib is connected to the headerView outlet of the "File's Owner". As a result when the nib is loaded it will attempt to set that headerView ivar to reference the loaded UIView object.
When I connect the file owner to headerView is that like creating an instance of UIView and storing it in the headerView instance variable programmatically?
Yes, this is equivalent to creating the view programmatically and setting the ivar yourself. You could write a method to create views on demand instead of using a nib as a factory for view objects if you prefer. Nibs are handy because they can allow you to configure view properties in a visual editor which may be easier to adjust than setting those properties progammatically. Nibs also offer a way to loosely couple the source of your view instances to the controller using those views which is convenient when you might want to switch which view your controller uses (iPad vs iPhone view, or different views for different locales).

Is it ok to call [super loadView]?

I thought that I should never call [super loadView] but something is confusing me.
In description of loadView (UIViewController Class Reference) it is said that "Your custom implementation of this method should not call super.",
but in ZoomingPDFViewer example that they gave, loadView implementation (ZoomingPDFViewerViewController) is calling [super loadView].
I have tried to call it from my loadView method and it works ok, but I just don't understand then what does it mean to not call super.
You definitely should not be calling [super loadView]. I'd say you found a bug in the ZoomingPDFViewer example.
You override loadView when you want to programatically create the view hierarchy for your view controller (not using a xib).
As you pointed out, the docs clearly state that you should not call super.
Your custom implementation of this method should not call super.
I assume this is to avoid loading both from a xib and programatically creating a view as this method is used by the base to load a view from a xib:
If the view controller has an associated nib file, this method loads
the view from the nib file.
Note also that even if during allocation of your UIViewController object you pass nil for the nibNameOrNil parameter that the UIViewController implementation of loadView will try to load any xib with the associated class name in it.
A view controller has an associated nib file if the nibName property
returns a non-nil value, which occurs if the view controller was
instantiated from a storyboard, if you explicitly assigned it a nib
file using the initWithNibName:bundle: method, or if iOS finds a nib
file in the app bundle with a name based on the view controller’s
class name. If the view controller does not have an associated nib
file, this method creates a plain UIView object instead.
The real intent of this method is to give you full control of building the view hierarchy without relying on the built in xib loading mechanism.:
You can override this method in order to create your views manually.
Personally, I override loadView if: 1.) The xib I would make for it is really trivial or 2.) The layout of the control is very dynamic, so creating a xib with a static layout has little benefit.
NSViewController tries to initialize a view from a nib in -loadView. Since your nib name is not set for your controller, it will just give you a self.view = nil; I would assume UIViewController works the same way.
So it should be safe, but you it's completely unnecessary.
If you dont have a view created in your IB, then you should call [super loadView] in your code to give a view to your program.
In case of your custom views, you are suppose to create a view with the interfaz builder, so you dont need to call it.
If you create your ViewController programmatically, you could call super.loadView() instead of self.view = UIView(frame: UIScreen.main.bounds) at the beginning of override func loadView().
However, do NOT call self.view before super.loadView(), since the former will trigger the latter if view did not been loaded.
I don't think it's a good idea to explain Apple's documentation like a robot.

About the initialization of the UIView

Support there are 2 views in the XIB file: UIView1, UIView2.
And the UIView1Owner is the owner of the UIView1. I guess when the XIB is loaded, it will automatically load the 2 views. However, the constructor (initWithFrame:) in the UIView1Owner will not be triggered.
It seems there is some magic behind the scene, and I am wondering how to add some stuff to the constructor of the UIView.
From the "initWithFrame:" section of http://developer.apple.com/library/ios/ipad/#documentation/uikit/reference/UIView_Class/UIView/UIView.html
If you use interface builder to design your interface, this method is not called when your view objects are subsequently loaded from the nib file. Objects in the nib are reconstituted and initialized using their initWithCoder: method...
Place any code you want in the initWithCoder: method.
initWithFrame will be called only for two created from xib objects (UIView1, UIView2). xib owner is assumed to exist before xib load.

Resources