In this code I am loading a View Controller (and associated View) from a .xib:
-(id)initWithCoder:(NSCoder *)coder
{
// add custom initialisation code here
[super initWithCoder:coder];
return self;
}
This successfully works, but I do not really understand what the line [super initWithCoder:coder] is accomplishing. Is that initializing my View Controller after my View has been initialized?
Please be as explicit as possible when explaining. Thanks.
Your class is a subclass of UIViewController. The call is telling your super class (UIViewController) to do the steps it needs to accomplish so that you can do your init steps. This would be setting up any properties that the UIViewController provides or registering for notifications that the UIViewController needs to do its work.
It is suggested almost every time you override a method from the super class to call the super class's method in addition to the steps you need to take.
Edit: Also if you don't need to do anything in a method the superclass provides, you can just leave it out and the super class's method will be used instead. In this case I would not provide the initWithCoder: method unless there was some code you need to preform in addition to what you showed.
Related
I have a subclass of UIViewController that I want to add from the storyboard.
So I'm using what seems the standard methodology:
SubViewController *svc = [self.storyboard instantiateViewControllerWithIdentifier:#"SubViewControllerID"];
[self addChildViewController:svc];
[self.view addSubview:svc.view];
Which is fine but what if I want to call a custom init method on the subview?
I can do something like:
svc = [svc initWithFoo:#"Hello"];
Which seems to have to go after the addSubview call inorder for it to work.
Is this the best way to do this?
Seems a bit unorthodox. Calling an init method on an object that has already been created seems like its no longer truly an init method.
Maybe I should call it setWithFoo: or something and not have it return anything?
SubViewController *svc = [self.storyboard instantiateViewControllerWithIdentifier:#"SubViewControllerID"];
will cause the SubViewController to be inited with it's - (id)initWithCoder:(NSCoder *)decoder {} method.
Override that method (don't forget to call super)
If you want to do additional setup to your view controller after you instantiate it form the storyboard you can create some methods in the view controller's class and call them after the instantiate method fo the storyboard.
But be careful, if you try to make changes on any UI component in those methods, they wont be applied, and probably the app will crash. So use those methods to set params to the View Controller like array of objects, or any kind of data, and apply the UI changes for the view controller's view in viewDidLoad/viewWillAppear/viewDidAppear methods of your view controller.
Essentially I think the answer is that you can't use custom initialisers on ViewControllers added from the storyboard. Instead you have to set properties directly or through a method at the appropriate time in the life cycle as stated above.
Also as mentioned, the VC will be instantiated through initWithCoder, so calling an additional initialiser might be superfluous(?).
I encountered problems trying to use a custom initialiser that contains a call to super if I called it before the subview was added. I would just get a blank view added, I think because the superclass doesn't seem to know about the storyboard at that point. I had more success removing the call to super but that seems wrong.
This case would be more pertinent when adding subviews to a scrollview. For simplicity I left this out of my example.
-(void)viewwillAppear
{
[super viewwillAppear:animated];
}
What does mean of calling [super viewwillAppear:animated] and what happen if we not call it?
By using super you are calling the base class version of the method. You will see similar call in init, dealloc, viewDidLoad etc. methods. The reason is in base class's implementation something important may be carried out without which the derived class will not work properly. When you have overridden the method in derived class, you will need to make a call to the base version by using super.
The only situation you will not call base class's method by using super is when you know that you don't need the tasks carried out by base class, in other words you are overriding completely. This is not the situation with viewWillAppear:animated or viewDidLoad etc. So we always call super in these cases.
Apple's documentation for viewWillAppear: just says:
If you override this method, you must call super at some point in your implementation.
It will probably lead to some unexpected behavior if you don't call it. Note that 'at some point' means you don't have to call it first.
The reference clearly states
This method is called before the
receiver’s view is about to be
displayed onscreen and before any
animations are configured for showing
the view. You can override this method
to perform custom tasks associated
with presenting the view. For example,
you might use this method to change
the orientation or style of the status
bar to coordinate with the orientation
or style of the view being presented.
If you override this method, you must
call super at some point in your
implementation.
when overriding the loadView method in UIViewController, should one call [super loadView] in the beginning of the method or at the end of the method? And why?
According to the UIViewController class reference, you should not call [super loadView] at all:
Your custom implementation of this method should not call super.
Normally you should not call loadView directly. It merely sets your self.view property and is called by the view controller only.
You should call [super loadView] only if you need the view created by your super class, because you want to include it in your decoration view hierarchy or something like that.
Just to be really sure, you didn't mean viewDidLoad, right? Because they are two very different methods... as of 3.0, the docs reccomend always calling viewDidLoad at the start.
You can call it either before or after, but usually it is placed at the end unless you have a reason to do otherwise.
When I need to load a view programmatically I do the following:
MyController* myController = [[MyController alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"myNib" owner:myController options:nil];
// use my controller here, eg. push it in the nav controller
This works fine, but my controller's viewDidLoad is never called. So I resorted to manually calling it after the loadNibNamed call, but it doesn't seem correct. I was expecting the framework to call the viewDidLoad on my behalf. Is this the right way or I'm missing something?
I am new to Stack Overflow, but I discovered this question and discovered a couple other methods to load a nib file that ensure that the viewDidLoad method gets called automatically. Ed Marty's answer is correct, but it requires you to go outside the ViewController's code to load the nib file, these two examples I offer here allow you to keep your code inside the ViewController's implementation. Not necessarily better ways, just different. If you know of any drawbacks to doing it this way, please let me know your thoughts.
First, inside the initWithNibName:bundle: method of your UIViewController subclass, you can replace the call:
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
with something like this: self = [super initWithNibName:#"NameOfAlternateNibFile" bundle:nibBundleOrNil];
Or, you can accomplish what appears to do exactly the same thing by the following:
[[NSBundle mainBundle] loadNibNamed:#"NameOfAlternateNibFile" owner:self options:nil];
[self setView:self.view]; //this line ensures that the viewDidLoad method is called
The key is in understanding what is written in the comments above the function definition for $initWithNibName:bundle: in the UIViewController.h file (included at the bottom of my answer here, see italics).
The nice thing about doing this using either of these methods is that viewDidLoad gets called in either scenario.
Here are the directives listed in UIViewController.h:
The designated initializer. If you
subclass UIViewController, you must
call the super implementation of this
method, even if you aren't using a
NIB. (As a convenience, the default
init method will do this for you, and
specify nil for both of this methods
arguments.) In the specified NIB, the
File's Owner proxy should have its
class set to your view controller
subclass, with the view outlet
connected to the main view. If you
invoke this method with a nil nib
name, then this class' -loadView
method will attempt to load a NIB
whose name is the same as your view
controller's class. If no such NIB in
fact exists then you must either
call -setView: before -view is
invoked, or override the -loadView method to set up your views programatically.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;
You should load view controllers with
MyController* myController = [[MyController alloc] initWithNibName:#"myNib" bundle:nil];
Make sure your MyController extends from UIViewController, and the View property is set properly in Interface Builder.
The views are loaded lazyly by UIViewController. If you use the accessor myController.view in your code, the view should be loaded and viewDidLoad be called.
I noticed the same thing. I think ViewDidLoad must be called by the view CONTROLLER. Since you don't have a view controllr in your nib, you have to call the viewdidload manually. I"m having to do the same thing.
I had an nice UIScrollView inside my nib, which worked nicely. Then I had some very special needs and subclassed UIScrollView. In my Nib, I changed the class in the identity inspector to my subclass.
But for some reason, my -initWithFrame: method gets never called when the nib loader builds up all those objects from the nib. Actually I didn't change anything right now in my subclass. And the scroll view just works fine. Expect that it seems to be a blank UIScrollView even if I told the nib it should be an SpecializedUIScrollView for testing purposes.
Is there something else I must consider when subclassing a UIScrollView while still using a Nib file to bring it into perspective?
My dedicated initializer looks like this:
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
NSLog(#"Hello !!!!!!!!!!!!!");
}
return self;
}
I'm never seeing that Hello in the console, if I try to load that from the Nib. Of course, if I alloc and initialize that by myself, it works. But I dont want to position my scroll view programmatically around, if I can use that damn cool Interface Builder instead.
Objects in a nib or xib are stored as serialized objects, this may mean you have to use the awakeFromNib method because init methods are never called.
Extend initWithCoder, be sure to call the super method. It is during this super call that setFrame will be called on your class.
You can then re-use your standard initWithFrame call in initWithCoder, and the interface builder will control the frame size.
- (id)initWithCoder:(NSCoder *)aDecoder {
[super initWithCoder:aDecoder]; // Required. setFrame will be called during this method.
return [self initWithFrame:[self frame]];
}