When should I use init: and when should I use initWithNibName:bundle: when creating a view controller?
-initWithNibName:bundle: is the designated initializer for UIViewController. Something should eventually call it. That said, and despite Apple's examples (which favor brevity over maintainability in many cases), it should never be called from outside the view controller itself.
You will often see code like this:
MYViewController *vc = [[MYViewController alloc] initWithNibName:#"Myview" bundle:nil];
I say this is incorrect. It puts implementation details (the name of the NIB and the fact that a NIB is even used) into the caller. That breaks encapsulation. The correct way to do this is:
MYViewController *vc = [[MYViewController alloc] init];
Then, in MYViewController:
- (instancetype)init
{
self = [super initWithNibName:#"Myview" bundle:nil];
if (self != nil)
{
// Further initialization if needed
}
return self;
}
- (instancetype)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle
{
NSAssert(NO, #"Initialize with -init");
return nil;
}
This moves the key implementation details back into the object, and prevents callers from accidentally breaking encapsulation. Now if you change the name of the NIB, or move to programmatic construction, you fix it in one place (in the view controller) rather than in every place the view controller is used.
Use initWithNibName: bundle: if you are... initializing with a nib file! That is, a file that you made using Interface Builder.
If you aren't using IB to layout your views, you can just use init.
You can just call init, as long as the xib has the same name as the view controller class. The encapsulation is not necessary. This saves typing, but may not serve clarity.
NUDMainViewController *mainVC = [[NUDMainViewController alloc] init];
using init when there is no nib/xib file, e.g. UI are created by coding
using initWithNibName , if we have an nib/xib or same controller share by more than 1 nib/xib
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController_iPhone" bundle:nil];
} else {
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController_iPad" bundle:nil];
}
that's what I think..
Related
I have a RootViewController that calls an AddQuoteViewController and there is a variable "subject_id" that I set in the RootViewController that does not show up in AddQuoteViewController and I need help understanding why and how to fix it.
Subject *sub = [self.tableDataSource objectAtIndex:indexPath.row];
self.subject_id = sub.subject_id;
[self addQuote_Clicked: sub];
...
- (void) addQuote_Clicked:(id)sender {
if(aqvController == nil)
aqvController = [[AddQuoteViewController alloc] initWithNibName:#"AddQuoteView" bundle:nil];
if(addNavigationController == nil)
addNavigationController = [[UINavigationController alloc] initWithRootViewController:aqvController];
[self.navigationController presentModalViewController:addNavigationController animated:YES];
}
Then in my AddQuoteViewController I try to access this variable like this:
RootViewController *rv = [RootViewController alloc] ;
NSLog(#"rv.Subject_id = %d", rv.subject_id);
But get nothing. There must be a simple way to do this.
First of all, there's an error in your third block of code. This:
RootViewController *rv = [RootViewController alloc] ;
Should be this:
RootViewController *rv = [[RootViewController alloc]init];
But strictly speaking that's not why you aren't seeing your instance variable.
If I understand correctly, the first two blocks of code are in RootViewController, and they instantiate an AddQuoteViewController and present it. Then, from your third block of code, which is in AddQuoteViewController, you want to access a member variable (subject_id) of the RootViewController that brought it up.
The approach of instantiating a RootViewController from within the AddQuoteViewController wouldn't work, because you're creating a different instance of RootViewController. What you're after is the value in the instance you just came from.
Perhaps the easiest way to do it is to create a corresponding property on AddQuoteViewController and set it when it's created:
- (void) addQuote_Clicked:(id)sender {
if(aqvController == nil)
aqvController = [[AddQuoteViewController alloc] initWithNibName:#"AddQuoteView" bundle:nil];
if(addNavigationController == nil)
addNavigationController = [[UINavigationController alloc] initWithRootViewController:aqvController];
aqvController.subject_id = self.subject_id;
[self.navigationController presentModalViewController:addNavigationController animated:YES];
}
You'll need to create the subject_id property on AddQuoteViewController the same way you did on RootViewController.
There are various ways of doing this, but a short answer - You can set a reference to the RootViewController as a property on your AddQuoteViewController
i.e.
in AddQuoteViewController.h
RootViewController *rvc
...
#property (nonatomic,assign) RootViewController *rvc;
and corresponding synthesize and release in your implementation class. (AddQuoteViewController.m)
Then when you create your AddQuoteViewController inside your RootViewController, also set this property:
- (void) addQuote_Clicked:(id)sender {
if(aqvController == nil)
aqvController = [[AddQuoteViewController alloc] initWithNibName:#"AddQuoteView" bundle:nil];
aqvController.rvc = self;
... etc.
Then you can access any property of the root view controller inside your AddQuoteViewController via this property:
NSLog(#"rv.Subject_id = %d", self.rv.subject_id);
As a side note there are a few things you are doing in your question that are a bit unusual, like trying to get a reference to an object by allocating a new one, and also creating a new navigation controller and presenting it as a modal view controller typically you would do one or the other (and wouldn't need to create a new navigation controller). i.e. you would either create a view controller and present it modally, or you would create a view controller and push it onto your current navigation controller stack.
You can't access variables across view controllers like that.
Have a look at creating a singleton class which will be globally accessible.
One exception I think is you can access them in the AppDelegate, but it's not advisable to have global vars.
I want to setup a UIViewController within a NavigationController programmatically, however the loadView nor viewDidLoad method get called.
This is my code in the app delegate:
MyViewController *viewController = [[MyViewController alloc] init];
UIView *view = [[UIView alloc] initWithFrame:window.frame];
viewController.view = view;
UINavgationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
[window addSubview:[navController view];
[window makeKeyAndVisible];
When I start the app I see a navigationbar, but no calls to loadView. What am I missing?
I thought loadView gets called after you call view
Edit
MyViewController *viewController = [[MyViewController alloc] init];
[viewController view]; // doesn't look right?
UINavgationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
[window addSubview:[navController view];
[window makeKeyAndVisible];
edited towards Jonah's comment, but loadView still doesn't get called.
A UIViewController will create its view (by loading it from a nib or implementing -loadView) when the controller's view getter is called and its view is currently nil.
In the code shown you never invoke the view property's getter, only its setter.
Also, you are assigning the controller's view from your app delegate. UIViewControllers are expected to create their own views on demand, not have them provided by some other class. This approach will cause you problems later when you realize that the controller unloads its view and attempts to recreate it in response to memory warnings. Let your controller create its view, don't try to pass it one.
maybe you were not facing this issue... but the other day I ran into the same irritating trouble.. loadView, viewDidLoad and viewWillAppear not being called in my UIViewController.
My issue was v. simple but bit tricky to catch if you are not very careful. Instead of writing
-(void) loadView
I wrote:
-(void) loadview
Please note that this won't fire any warning. The difference of "V" and "v" in loadView can easily be missed. And obviously, since loadView didn't get called, viewDidLoad/viewWillAppear won't get called either as there was no view that got loaded (am not using any nib..creating the view programmatically).
-Anshu
Another gotcha worth noting is if you define a
#synthesize view;
without a matching #property in your implementation, this can result in calls to your view controller's returning nil, and no call to your loadView method.
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
I have the following code and I want to load the UIViewController. How can I initialize and load the UIViewController.
- (void) applicationDidFinishLaunching:(UIApplication*)application
{
CC_DIRECTOR_INIT();
NSLog(#"applicationDidFinishLaunching");
MainViewController *controller = [[MainViewController alloc] init];
}
From your delegate you can do this (assuming you have IBOutlet UIWindow *window):
[window addSubview:[controller view]];
[window makeKeyAndVisible];
Once a controller is loaded, you can push others (from the UIViewController):
controller = [[MainViewController alloc] init];
[[self navigationController] pushViewController:controller animated:YES];
Here is a link to the documentation for UINavigationController.pushViewController
http://developer.apple.com/library/ios/documentation/UIKit/Reference/UINavigationController_Class/Reference/Reference.html#//apple_ref/occ/instm/UINavigationController/pushViewController:animated:
TestViewController *testController = [[TestViewController alloc] init];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.window setRootViewController:testController];
[self.window makeKeyAndVisible];
Although adding a subview will work fine, you will receive the following warning unless you set your controller as RootViewController:
Application windows are expected to have a root view controller at the
end of application launch
Are you using a nib file to set up the user interface of your view? The code you currently have does load and initialize the ViewController. But you would then need to add some user interface elements to your view, and present that view controller in your application. If you arre using a nib file for your user interface, then you want:
MainViewController *controller = [[MainViewController alloc] initWithNibName:#"nibFileName" bundle:nil];
This will associate your controller with the nib file. If you are not using a nib file, you need to programmatically add each element that you wish to display.
After your view is set up, you then need to present the view controller, by either adding it as a subview to your current view, or using a navigationController to push the new viewController. You need to be more specific about exactly what you are trying to do.
I think what you want to add is:
[[NSBundle mainBundle] loadNibNamed:#"nibWithMainViewControllerAsOwner" owner:controller options:nil];
loadNibNamed:owner:options: is the only method added to NSBundle by UIKit. See NSBundle UIKit Additions Reference. And if there's any problem with outlets not being wired up correctly then check all your outlets are key-value coding compliant (alternative answer: make sure they're correctly exposed as properties).
[viewController view]
That's how to load viewController. When the view is accessed it's lazy loaded.
I have an iPhone app with a tableviewcontroller. When you click a certain cell it opens a new uiviewcontroller with this code:
nextViewController = [[avTouchViewController alloc] initWithNibName:#"avTouchViewController" bundle:nil];
The uiviewcontroller above called avTouchViewController has a property that looks like:
IBOutlet SomeObject *controller;
SomeObject is an object with all relevant view properties.
I would like to pass an nsstring parameter from the tableviewcontroller I initialize the avTouchViewController with to someObject.
How can I do this?
I'm a little confused by your question; you say you're creating your avTouchViewControllers when a cell is tapped inside an existing UITableView, but your last part describes the inverse situation.
Basically, if you want to pass information to a view controller, just give it a property that can be set (which may already be the case), e.g.:
nextViewController = [[avTouchViewController alloc] initWithNibName:#"avTouchViewController" bundle:nil];
nextViewController.controller = theInstanceOfSomeObjectIWantToPass;
You also may want to rename your controller property. To a reader, it doesn't make sense that a view controller has a property called controller which is actually a SomeObject*. As well, your class names should be capitalized, i.e. use AvTouchViewController instead of avTouchViewController.
If I were doing this I would add my own initializer to my UIViewController subclass:
- (id)initWithController:(NSString *pController) {
if (self = [super initWithNibName:#"avTouchViewController" bundle:nil]) {
self.controller = pController;
}
return self;
}
And then just call the following (in tableView:didSelectRowAtIndexPath: or whereever):
NSString *controller = #"Sample String";
AVTouchViewController *nextViewController = [[AVTouchViewController alloc] initWithController:controller];
[controller release];
[self.navigationController pushModalViewController:nextViewController animated:YES];
[nextViewController release];
As a point of style, class names conventionally begin with uppercase letters (hence my change to AVTouchViewController).