A view controller FooViewController needs to do some initialization when it's created (of itself, not of the managed view - before the view is even loaded).
I want to be able to create it in two ways:
Programmatically, possibly with some arguments:
FooViewController *fooViewController = [[[FooViewController alloc] initWithSomeData:data] autorelease];
[self.navigationController pushViewController:fooViewController animated:YES];
In Interface Builder.
For (1) I would write the initializers like this:
- (id)initWithSomeData:(Data *)data // designated initializer
{
if (self = [super initWithNibName:#"FooView" bundle:nil]) {
self.title = "Foo";
// perform initialization
}
return self;
}
- (id)init
{
return [self initWithSomeData:nilOrSomeDefaultValue];
}
// ... other initializers that call the designated initializer
(I hardcode the nib name since the controller always uses the same view configuration and because which view it uses doesn't concern the callers at all)
When created in Interface Builder, I would want the end result be the same as if the object was initialized with the parameterless init.
Now if I just do:
- (id)initWithCoder:(NSCoder *)decoder
{
return [self init];
}
The title, wantsFullScreenLayout and nibName properties which are set in the Inspector in Interface Builder would then be ignored, but that's OK. The nibName is hardcoded in init and title is set there in case the controller is instantiated programmatically anyways.
The problem is that parentViewController isn't set (the default initWithCoder: would set it based on the hierarchy of objects in the NIB).
How can I get the parent object from the nib? I would then change initWithCoder: to something like:
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [self init]) {
_parentViewController = [decoder parentObject];
}
return self;
}
Or should I be using some different approach to create controllers which can be instantiated both programmatically and in IB?
Don't, don't, don't try to make a viewcontroller that works with and without a nib. It will be a nightmare because nibloading uses some of the normal entry points and provides new ones, and it will be fragile with respect to OS revisions.
What you can do is it make the view controller always load from a nib, then give yourself a convenience initializer to go through the nib:
- (id) init {
return [[[self class] alloc] initWithNibNamed:#"MyNibName" bundle:nil];
}
Then you can reference it through other nibs the normal way, and just call the convenience init method when you don't want to explicitly deal with the nib.
Why not do init stuff in viewDidLoad - when creating outside of IB, you can set initial values with some other methods or properties after initialization, but before viewDidLoad is called.
Related
I am creating an instance of a view controller and then presenting it modally on another view as such:
[viewController receiveNumber1:number1 andNumber2:number2];
[viewController setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentModalViewController:viewController animated:YES];
What I would like to do is set two properties within this new view controller upon initialization.
I can do this by calling a custom method like (somethings are int variables here):
[viewController methodProperty1:something1 andProperty2:something2];
However, I really need to set those properties right upon initialization because the content of the new view is dependent on those numbers.
Is there a way to pass on information to a new view while it is being initialized? Something like:
[[UIViewController alloc] initWithValue1:something1 andValue2:something2];
Also, I do not understand why
CustomViewController *view = [[CustomViewController alloc] init];
view.property = newValue; / [view setProperty:newValue];
does not work in this configuration, meaning the properties' values do not change. I can call methods within the new view and set its properties that way but not set the properties directly?
I'm sorry if these are lame questions, but I am sort of a beginner.
Thanks a bunch!
view.property = newValue and [view setProperty:newValue] are equivalent terminology.
You can implement one or multiple custom initialization methods for your view controller there is nothing that should stop from doing so. Have a look to the official doc to make sure you respect a couple of rules of how you should implement your custom initializer.
Your code snippet:
-(id)initWithValue1:(NSInteger)value1 andValue2:(NSInteger)value2
{
if (self = [super init])
{
_firstValue = value1;
_secondValue = value2;
return self;
}
else return nil; // or handle the error
}
In your CustomViewController.h, define two properties you want set and an initialization method, for example:
#property (nonatomic, assign) NSInteger firstValue;
#property (nonatomic, assign) NSInteger secondValue;
-(id)initWithFirstValue:(NSInteger)value1 andSecondValue:(NSInteger)value2;
Then, in your CustomViewController.m, implement the method you have declared earlier:
-(id)initWithFirstValue:(NSInteger)value1 andSecondValue:(NSInteger)value2
{
self = [super init];
if (self)
{
// Supposing you haven't synthesized your property and you are using
// latest Xcode build, instance variable are automatically created
_firstValue = value1;
_secondValue = value2;
}
return self;
}
Finally, you can allocate your controller using this line of code:
CustomViewController *controller = [[CustomViewController alloc] initWithFirstValue:value1ToSet andSecondValue:value2ToSet];
Some final advice
When you implement your custom initialization method, you should properly choose which init has to be called on super.
For instance, I prefer calling self = [super initWithFrame:CGRectZero]; if my controller is a subclass of UIViewController, or self = [super initWithStyle:UITableViewStylePlain]; if it is a subclass of UITableViewController.
There are other custom init method but you can find other information reading documentation about your custom controller superclass.
I have a UIViewController called LaunchController that is launched in my iPhone app when the app first opens:
#interface LaunchController : UIViewController<UINavigationControllerDelegate, UIImagePickerControllerDelegate>
Then, when a button is clicked, I push another view controller:
MainController *c = [[MainController alloc] initWithImage:image];
[self presentModalViewController:c animated:NO];
MainController has the following constructor, which I use:
- (id)initWithImage:(UIImage *)img
{
self = [super init];
if (self) {
image = img;
NSLog(#"inited the image");
}
return self;
}
and then it has a viewDidLoad method as follows:
- (void)viewDidLoad
{
NSLog(#"calling view did load");
[super viewDidLoad];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
[self.view addSubview:imageView];
NSLog(#"displaying main controller");
}
When the program runs, I see that the constructor for MainController is called (due to the output of NSLog), however viewDidLoad never gets called, even though I am calling presentModalViewController. Why is this? Why isn't viewDidLoad being called?
I think it is something as followings. When you need the property of view inside UIViewController, it will be loaded with lazy manner.
- (UIView *)view
{
if (_view == nil) {
[self loadView]; //< or, the view is loaded from xib, or something else.
[self viewDidLoad];
}
return _view;
}
After the view initialized, it will call viewDidLoad to inform the UIViewController.
You aren't loading your view controller from a xib file, and from comments you don't have anything in loadView (which is where you would create your view controller's view if you were not using a xib file).
Therefore, your view isn't being loaded, so viewDidLoad is never called.
Typically you would use initWithNibName: to initialise a new view controller, and then set the image after it (so expose the image as a property).
viewDidLoad will be called as soon as your controller's view property is accessed, that is when you display it for the first time or request it (e.g. have some code that calls c.view.
The reason viewDidLoad is not being called is because you aren't loading a view.
In your init method:
self = [super init];
means that you are just creating a naked view from scratch. not loading one from a nib.
try this instead:
self = [super initWithNibName:nil bundle:nil];
If you have a xib or nib file with the same name as the view controller class it should find if. Otherwise, you can just give a nibName that works.
UPDATE:
If you are not using nib files, then the appropriate method is NOT viewDidLoad. You have to implement loadView instead of viewDidLoad.
In your specific case, just put everything that is currently in viewDidLoad into loadView.
I have passed the NSMutableDictionary into the FirstClass using custom init method.
Second Class,
firstClass = [[FirstClass alloc] initWithFeedData:feedDictionary];
[self.navigationController pushViewController:firstClasss animated:YES];
FirstClass,
- (FirstClass *) initWithFeedData:(NSMutableDictionary *) dataDictionary {
{
NSLog(#"%#", dataDictionary);
[[NSBundle mainBundle] loadNibNamed:#"FirstClass" owner:self options:nil];
//[self viewDidLoad]; Without call, ViewDidLoad method doesn't call.
}
- (void) viewDidLoad {
[super viewDidLoad];
NSLOG(#"Inside View DidLoad"); // This method never calls.
}
In my viewDidLoad method doesn't called, if i call "[self viewDidLoad]" then only, viewDidLoad method works properly. I donno why viewDidLoad method doesn't call directly without calls in another method? Please Help me out.
Thanks.
If you're subclassing a UIViewController, which I assume you are since you're expecting viewDidLoad, then you should override its designated initialiser, initWithNibName:bundle: if you're using a XIB, in which case viewDidLoad will be called after the XIB loads. If you're not using a XIB, you should implement the loadView method to create your view, and viewDidLoad will be called after loadView finishes.
#Stephen is right, at the very least you need to rewrite your init statement to return self. However, it's much easier and more robust to declare a property on your view controller and pass objects in that manner.
FirstClass *firstClass = [[FirstClass alloc] initWithNibNamed:nil];
firstClass.feedDictionary = feedDictionary;
[self.navigationController pushViewController:firstClasss animated:YES];
[firstClass release];
Your viewDidLoad method will now be called and your feedDictionary will be sitting there, waiting for you.
Your init method is not correct. It should look something like this:
- (id) initWithFeedData:(NSMutableDictionary *) dataDictionary {
if (self = [super init]) {
// TODO: do initialisation stuff with dataDictionary
}
return self;
}
This assumes that FirstClass is derived from UIViewController.
As long as your NIB file has the same name as your class it should be loaded in automatically -- there's no need for the loadBundle: call. Here's what the documentation says about the nibName property:
If the value of this property is nil
and you did not override the loadView
method in your custom subclass, the
view controller looks for a nib file
whose name (without the .nib
extension) matches the name of your
view controller class.
In the app im creating there are many pages that look mostly the same with some part which is different. To handle this kind of situation i created a container controller that contains a subview. I want this subview to be filled by the contents of another controller (and its associated nib) which i will created dynamically as needed based on context.
I have the following method somewhere
- (void) someAction {
UIViewController* contentController = [[MyContentController alloc] init];
UIViewController* containerController = [[MyContainerController alloc] initWithContentController:contentController];
[navigationController pushViewController:pageController animated:YES];
[contentController release];
[containerController release];
}
In MyContainerController.m i retain the controller in a property
- (id)initWithContentController:(UIViewController *)aContentController {
if ((self = [super initWithNibName:#"MyContainerController" bundle:nil])) {
contentController = aContentController;
}
return self;
}
Later in viewDidLoad i do the following
- (void)viewDidLoad {
[super viewDidLoad];
[contentViewContainer addSubview:contentController.view];
}
contentViewContainer is the view that's supposed to hold the page specific info.
Unfortunatly this fails with EXC_BAD_ACCESS.
The funny thing is that if i alloc and init the content controller from within viewDidLoad everything works. It seems that i cant pass a contoller i allocated from another place.
Can anyone assist.
Since you are releasing contentController in the actionMethod
you have to retain contentController in you init method
- (id)initWithContentController:(UIViewController *)aContentController {
if ((self = [super initWithNibName:#"MyContainerController" bundle:nil])) {
contentController = [aContentController retain];
}
return self;
}
But, why do you need this? Controllers are supposed to control views and no other controllers. If you think you really need that then you want to use UINavigationController or UITabBarController maybe.
You can also load views without a controller (see here)
I personally think that having UIViewControllers inside of simple UIViewController is not a preferable approach
Hope it helps
The App Delegate has an outlet property to the view controller, and the view controller is created in the nib.
Althoug the -viewDidLoad method of the view controller gets loaded, it seems that it designated initializer receives no call:
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle {
NSLog(#"iniwinib");
if (self = [super initWithNibName:nibName bundle:nibBundle]) {
// do stuff
}
return self;
}
I also tried with -init, but this also does not receive a call. No NSLog output. Is there another initializer that I must use in this case?
-initWithCoder: is the initializer in this case (because the object is being deserialized from the NIB), but the routine you actually want here is -awakeFromNib. That's what's called after all the objects in the NIB have been constructed and all the outlets have been wired.
Are you actually calling initWithNibName to create your ViewController somewhere in the code? If not then it will never get called, this method does not get called automatically you must call it to create your viewController from your nib. But you dont need to do call this method because you have already set the ViewController in the nib..