viewController custom init method with storyboard - ios5

im having trouble overriding the initialization method for my CustomViewController thats designed in my Storyboard.
now im doing (in my mainViewController):
self.customViewController = [[UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil] instantiateViewControllerWithIdentifier:#"CustomVC"];
self.customViewController.myObject = someObject;
and i have in viewDidLoad (CustomViewController)
[self.label setText:self.myObject.someString];
This works ok.
But, is it the correct way? Should i add a custom init method (or override) to my CustomViewController ? Like initWithObject: ? I dont know how to call my custom init method instead of UIStoryboard instantiateViewControllerWithIdentifier:, and im not getting calls to init nor initWithNibName.
Maybe i should use: - (id)initWithCoder:(NSCoder *)decoder.
Please give me some advice!
Thank you!

The designated initializer for view controllers in storyboards is -initWithCoder:. Since most view controllers from a storyboard are created via segues, you usually see state set during -prepareForSegue:sender:.
If you're instantiating a view controller directly from the storyboard like in the example you provided, then the pattern you've selected is appropriate.

As another "workaround" for this problem, you could use delegation. Create a protocol that would act as data source for your UIViewController subclass from storyboard. Conform to this data source protocol, implement it and use it right after loading your UIViewController subclass:
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
//invoke your data source here
}
I know... I also don't like this but... ;)

Related

Dependency injection with storyboards

I am trying to pass dependency into a view controller instantiated from storyboard using the following code
init?(coder: NSCoder, alertPresenter: AlertPresenterProtocol = , viewModel: EmployeesViewModel) {
self.alertPresenter = alertPresenter
self.employeeViewModel = viewModel
super.init(coder: coder)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
But I get the fatalError message. How can I pass dependency to a view controller through the initializer when it is instantiated from storyboard
The short answer is, you can't.
You instantiate a view controller from the storyboard using the instantiateViewController(withIdentifier:) method of UIStoryboard. According to the docs:
Each time you call this method, it creates a new instance of the view controller using the init(coder:) method.
The method creates the view controller for you, and it does not provide any way for you to inject dependencies.
When the view controller is created by a segue, the prepareForSegue method provides you with a UIStoryboardSegue object that has a reference to the already-initialized destination view controller. No way to inject dependencies via the initializer here, either.
Some purists will throw this fact in your face as a reason why you shouldn't use storyboards.
Personally, I've never found that to be a compelling argument. I simply assign the dependencies as properties of the view controller after it's been instantiated. This works just fine for unit testing, too. This does mean that your dependencies will have to be declared as optionals, and this can make your code a little less elegant. Again, I don't see this as a compelling reason to give up the many conveniences of storyboards.
Your storyboard must use a method marked with #IBSegueAction which will accept with minimum coder argument and returns object which correspond to destination. Inside that method you should init your destination view controller with custom init method.
If the dependency can be derived from within the initializer init?(coder:), you do so, and set it there.
If it's not, you can't set it there.
In any case, since you said unit-tests were involved, you'll want to make the dependency an externally settable attribute, and set it from the unit-test.

Custom initialise subview added from storyboard

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.

UIViewController and XIB question

How do I prevent a UIVIewController from loading the xib?
I downloaded an XCode (iPhone) project online, and I want to stop the xib file from loading. The view did load method doesn't have any code in it which deals with xib. How do I force the UIViewController to be loaded from it's viewDidLoad method instead of the xib?
Overwrite loadview but DO NOT call [super loadview]. Example:
-(void)loadView{
self.view = [UIView new];
[self.view setBackgroundColor:[UIColor redColor]];
}
From the Documentation
loadView
Creates the view that the controller manages.
You should never call this method
directly. The view controller calls this method when the view property
is requested but is currently nil. If you create your views manually,
you must override this method and use it to create your views. If you
use Interface Builder to create your views and initialize the view
controller—that is, you initialize the view using the
initWithNibName:bundle: method, set the nibName and nibBundle
properties directly, or create both your views and view controller in
Interface Builder—then you must not override this method.
The default implementation of this method looks for valid nib
information and uses that information to load the associated nib file.
If no nib information is specified, the default implementation creates
a plain UIView object and makes it the main view.
If you override this method in order to create your views manually,
you should do so and assign the root view of your hierarchy to the
view property. (The views you create should be unique instances and
should not be shared with any other view controller object.) Your
custom implementation of this method should not call super.
If you want to perform any additional initialization of your views, do
so in the viewDidLoad method. In iOS 3.0 and later, you should also
override the viewDidUnload method to release any references to the
view or its contents.
Write your own init method. Later if you require the nib you can create a UINib object and when you need the view you can use instantiateWithOwner.
Using init when creating your view controller will prevent the nib loading. Another thing to do is to name the nib something other than the name of the view controller - because the nib can be loaded automatically if they match. I use ViewControllerName_iPad or ViewControllerName_iPhone and create the view required depending on device idiom.
The code to load from the xib file is not in the viewDidLoad method of the view controller itself.
You should usually find it in the application delegate's didFinishLaunchingWithOptions: method, or in the info.plist file, under the NSMainNibFile entry.
Try changing the initWithNibNameOrNil method to just init.
If that doesn't work, also override the loadView method by uncommenting it and setting your view there.
Here's an example:
- (id)init
{
self = [super init];
if (self) {
// Custom initialization
}
return self;
}
- (void)loadView {
[super loadView];
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,460)];
myView.backgroundColor = [UIColor redColor];
self.view = myView;
[myView release];
}
Perhaps I'm barking up the wrong tree here, but is the UIViewController loading its view from a XIB because the XIB that loads the UIViewController itself has a "NIB Name" set in the view controller's settings?
e.g. load up MainWindow.xib, see your view controller. Select it, then look in the view controller settings over on the right (4th tab). One of the settings is "NIB Name". Just make that blank to stop the view controller loading its view from that XIB/NIB.

When overriding initWithCoder is it always necessary to call [super initWithCoder: coder]

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.

Who should call viewDidLoad on programmatically loaded views?

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.