Multiple view controllers with their own xibs, and when they load - iphone

Going through many tutorials and with the help of everyone here, I am trying to wrap my head around using multi view controllers with their own xib files.
I have one example where there is a : multiViewController and two others: aboutViewController, rulesViewController.
In both aboutViewController.m and rulesViewController.m files, I have placed the following code:
- (void)viewDidLoad {
NSLog(#"rules View did load"); // (Or About View did load, depending on the .m file)
[super viewDidLoad];
}
The mainViewController.m file contains:
-(IBAction) loadRules:(id) sender {
[self clearView];
[self.view insertSubview:rulesViewController.view atIndex:0];
}
-(IBAction) loadAbout:(id) sender {
[self clearView];
[self.view insertSubview:aboutViewController.view atIndex:0];
}
My question is, why is it when I run the application does the ViewDidLoad for both About and Rules fire? Meaning, I get the NSLog messages. Does this mean that regardless of the separate xib files, all views are loaded on start up?
My point of all this is: I want the multiViewController to be my main menu which will have separate buttons for displaying the other xib files when clicked. I had been placing my "initalize" code in the viewDidLoad but this seems wrong now as I don't want to hit until the users presses the button to display that view.
An example was to have a view that is: playGameViewController. In the viewDidLoad I was checking to see if a prior game was in progress and if so, prompt the user if they would like to resume. In the above case, when the app starts, it prompts right away (because the viewDidLoad is firing) even though I only wanted to display the main menu first.
Any explanation is greatly appreciated. Thanks in advance.
Geo...

My question is, why is it when I run
the application does the ViewDidLoad
for both About and Rules fire?
Meaning, I get the NSLog messages.
Does this mean that regardless of the
separate xib files, all views are
loaded on start up?
When you call
[self.view insertSubview:rulesViewController.view atIndex:0];
it's going to first call viewDidLoad for the initial view and then viewDidLoad once again for RulesViewController.
When your MainViewController, or any view for that matter loads, viewDidLoad is called automatically. ViewDidLoad is there in order for you to override or modify any objects in the nib, or you can create objects yourself. Views are only loaded on an as needed basis. If you were to load all your views initially when the app boots up, the user would just see a black screen until all the views are processed.
You say you are going through some tutorials, I don't know your area of expertise yet, but have you looked into navigation based apps using UINavigationController?
Just an example as your request if you want to have a button load a view you can do something like.
- (IBAction)pushSecondView:(id)sender {
SecondViewController *secondView = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
if (secondView != nil) {
secondView.title = #"Second View";
[self.navigationController pushViewController: secondView animated: YES];
}
}

Related

Navigate from one view to another using UIButton

I have an application with 2 views . In the first one I have a button which when I clicked the user should go to the second view. I tried what is explained before here from Karoley , but it does not work . When I click the button nothing happened?
Here is the code of my action :
-(IBAction)gotoSecondPage:(id) sender{
NSLog(#"In gotoSecondPage");
LeoActionViewController *aSecondPageController =
[[LeoActionViewController alloc]
initWithNibName:#"LeoActionViewController"
bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:aSecondPageController animated:YES];
}
LeoActionViewCOntroller is a controler for a second view.
It just do not switch to a second view. I do not know why
I put code your problem this will help you. First of all, you declare method and open .xib file and then connect to that button with selected touchupinside connection.
In the .h file:
- (IBAction)gotoSecondPage:(id) sender;
In the .m file:
- (IBAction)gotoSecondPage:(id) sender
{
NSLog(#"In gotoSecondPage");
LeoActionViewController *aSecondPageController =
[[LeoActionViewController alloc]
initWithNibName:#"LeoActionViewController"
bundle:nil];
[self.navigationController pushViewController:aSecondPageController animated:YES];
[aSecondPageController release];
}
I'm not sure in what capacity you want to switch views.
What immediately comes to mind is that you want a Navigation Controller. This is an object that lets you put view controllers on a stack and push and pop them to show and hide them. It creates a navigation pathway through your app and is easy to use. It also facilitates the 'standard' navigation bar which is found in many iphone apps.
If you just want to change one view for another view you can do many things including hiding and showing different views using setHidden:(bool)hidden. Otherwise you can use addSubview:(UIView *)view and removeFromSuperview to add and remove views completely from the superview.

Question about the mechanics of iPhone view controllers (i.e., explain why this crashes)

I am pretty new to iPhone programming, and was playing around with an app yesterday trying different scenarios with view controllers and nib files. So, I started a new app with a FirstViewController (FVC for short) and an FVC.xib.
I layed out a quick view in FVC.xib and ran the app - view displays, great.
I now wanted to have a second view I could add on top of the main view. So I went ahead and created SecondViewController.xib (SVC) but did not create the .m and .h files. I went about trying to load both these views from the same view controller, and here is where my question lies:
I created a button in FVC.xib and created an IBAction like this:
- (IBAction)loadSVC {
FirstViewController *viewController = [[FirstViewController alloc] initWithNibName:#"SecondViewController" bundle:[NSBundle mainBundle]];
secondView = viewcontroller.view;
[viewController release];
[self.view addSubView:secondView];
}
So this works great and adds the contents of SVC.xib, but when I try and remove that view from the superview, the app crashes:
[secondView removeFromSuperview];
If I actually create a view controller for SVC, use that to instantiate my view in FVC, and move the remove code to the SVC:
[self.view removeFromSuperview];
Everything works. My question - I kind of get why my first method crashes, but I was hoping someone could explain why and what goes on behind the scenes. I'm still a noob with object oriented programming, so what is actually happening in my first case where I create a new instance of FirstViewController and add its view to self.view? Why can't I release it (I assume because the original view is associated with FirstViewController, and when I create a new instance with the second xib it messes everything up) - I'd love a more technical explanation as to what is happening...
Thanks much!!
EDIT to add more info in response to Nick's reply below
Nick - so your answer did clear my thinking a bit in regards to the retain count, etc... I did another test app trying to get this working from a single view controller - think, for example, that I wanted to display an Alert or Welcome message to the user (I know in a real app there are different methods to accomplish this, but this is more of a learning experience) -- so I have my main view # MainViewController and layout my alert message in a xib called alert.xib -- so there is no logic behind the alert message, no reason for it to have a view controller that I can see, my end goal being loading/unloading this on top of my main view from the main view's view controller (or understanding why it is impossible)
I tried this using instance variables as you recommended:
In MainViewController.h:
#import <UIKit/UIKit.h>
UIViewController *secondController;
UIView *secondView;
#interface MainViewController : UIViewController {
}
#property(nonatomic, retain) UIViewController *secondController;
#property(nonatomic, retain) UIView *secondView;
- (IBAction)loadSecond;
- (IBAction)removeSecond;
#end
In MainViewController.m:
#import "MainViewController.h"
#implementation MainViewController
#synthesize secondController, secondView;
- (IBAction)loadSecond {
secondController = [[MainViewController alloc] initWithNibName:#"alert" bundle:[NSBundle mainBundle]];
secondView = secondController.view;
[self.view addSubview:secondView];
}
- (IBAction)removeSecond {
//I've tried a number of things here, like [secondView removeFromSuperview];, [self.secondView removeFromSuperview];, [secondController.view removeFromSuperview];
}
- (void)dealloc {
[secondController release];
[secondView release];
[super dealloc];
}
So - this works to load the alert view, but the removeSecond button does nothing (I did use NSLog to verify the removeSecond method is fired) - why?
Second, and most importantly - is this even possible, or is it horrible practice? Should every nib/view I am manipulating have their own view controller? Am I wrong to think I could just make a new instance of MainViewController and use it to display and remove this no-functionality, very temporary view? (And yes, I realize I could easily create this view programatically or accomplish the end goal in many different ways which would be easier, but I'm trying to really learn this stuff and I think figuring this out will help...
Thanks for the help!
You created a view controller
You accessed its view which caused controller to create the view and call the delegates (i.e. viewDidLoad)
Controller returns the view that you asked for
Now you add the view as a subview which increases its retain count
Controller is released and it releases the view, BUT since view's retain count was increased the view is still there
You try to remove the view, it is unloaded and delegates are to be called (e.g. viewDidUnload), however that messes up since the controller who created the view is released and that piece of memory is... smth else :)
That's why the first method doesn't work.
The second method is NOT correct either but it works because:
You remove controller's view from superview but since controller itself is not released (you didn't call [self release] or anything like that, not saying that you should :), just an example), then the view didn't reach 0 (zero) retain count and is still there - which means its subviews aren't removed
The proper way to do it is to save the reference to the controller as an instance variable (usually declare a synthesized property), and release it only when you are done with the view, making sure that the view is removed from superview before hand. The default templete for a View Based App shows how view controller should be managed
Hope this helps to understand why both methods behave differently
Based on your clarifications, you don't need secondView property or iVar. Also in your loadSecond instead of secontController = bla you need self.secondController = bla, otherwise you simply assign reference to the iVar instead of going through the setter.
Yes, it's possible to load subviews/other resources from a nib without having a dedicated controller
This is how you do it (one of the approaches):
UIView *result = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"MyNibName" owner:owner options:nil];
for ( id o in bundle ) {
if ( [o isKindOfClass:[UIView class]] ) {
result = (UIView *)o;
break;
}
}
Here the result will contain the first UIView in MyNibName. You can use other criteria to find out whether you got the view you wanted (tags, types...)

Understanding UIViewController hierarchy

Ok - my brain is being fried at the moment so any help would be appreciated.
I have multiple subclasses of UIViewController in my app. lets call them VC_A, VC_B, VC_C, VC_D.
The users interacts by touching buttons on each of the views.
So my AppDelegate adds in VC_A:
//Add the view controller's view to the window and display.
[self.window addSubview:viewController.view];
[self.window makeKeyAndVisible];
VC_A then loads VC_B by using presentModalViewController:
VC_B *tempView = [[VC_B alloc] initWithNibName:#"temploadingscreen" bundle:nil];
[self presentModalViewController:tempView animated:NO];
[tempView release];
and so until I get a hierarchy of
VC_A
- VC_B
- VC_C
- VC_D
but then when I call presentModalViewController on VC_D to take me to VC_C I want it to be a new instance of VC_C and not the original instance.
So my question is how to you go about doing this - do I need to use [self dismissModalViewControllerAnimated:NO]; to remove the old instances of the views.
Any help would be gratefully appreciated as I have done searches for this but all the tutorials and stuff use a navbar to control the navigation - and i cant use one because of the type of app. Any working code examples of properly moving between new instances of UIViewControllers would be great.
Just create a new instance with
ViewController_C *newVC_C = [[ViewController_C alloc] init]
[self presentModalViewController:newVC_C animated:NO];
[newVC_C release];
I decided to do this a different way which works perfectly for what I need.
What I did was I created the base ViewController with nothing in the xib and in the viewDidAppear method I called the other viewControllers (using presentModalViewController) based on the value of a global NSNumber.
Thus when I go to any of the other viewcontrollers rather than them call another viewController they simply set the global variable indicating which view to load and then close the current view (using dismissModalViewController).
This way each instance of the viewControllers are closed and the memory released.
I have created an example project and placed it on github https://github.com/sregorcinimod/Open
Just look in the Downloads you'll see it there

Update UITabBar Views?

I have an application where I was using buttons on a toolbar to call up the views, but I am switching it over to using a tab bar.
When using the buttons I was using the following code in MainViewController.m to update the values on the page and it was working just fine:
-(IBAction) loadSummaryView:(id) sender {
[self clearView];
[self.view insertSubview:summaryViewController.view atIndex:0];
//Update the values on the Summary view
[summaryViewController updateSummaryData];
[summaryViewController calculateData];
}
However, with the Tab Bar I can not figure out how to do this. I tried putting all of the necessary code in the Summary Views viewDidLoad and it loads the initial values, but it will not update the values when I change them in another view.
Any help is appreciated. I am a bit new at this, so please don't be cryptic as I may not understand the response.
Thank you.
By placing your code in viewDidLoad, it will only be called when the view is loaded from the nib. Unless you're running low on memory, this view will remain loaded for the duration of the life of your app.
So if you need to update values every time the view will appear, consider moving that code to an override of viewWillAppear
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// your stuff goes here...
[self updateSummaryData];
[self calculateData];
}
You can update your current view in the viewWillAppear:animated message of the view:
If you have everything setup correctly, there is nothing to do, the UITabBarController will show your view, an your UIViewController will receive a viewWillAppear message, where you can do your update:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self updateSummaryData];
[self calculateData];
}
I strongly recommend reading the View Controller Programming Guide for iOS which describes in detail the main interface paradigms supported by the iPhone.

How do I have a view controller run updating code when it is brought to the top of the stack of views?

I have a viewController (Planner) that loads two view controllers (InfoEditor and MonthlyPlan) when the application starts. MonthlyPlan is hidden behind InfoEditor (on load).
So my question is when I exchange InfoEditor for MonthlyPlan (MonthlyPlan gets brought to the top) how can I have data on the MonthlyPlan view be updated. An NSLog in viewDidLoad is being called when the application starts (which makes sense.) NSLogs in viewDidAppear and viewWillAppear aren't doing anything.
Any ideas?
Thanks!
-- Adding more details --
I'm creating the view hierarchy myself. A simple viewController that is just loading two other viewControllers. The two child viewControllers are loaded at the same time (on launch of application.) To exchange the two views I'm using this code:
[self.view exchangeSubviewAtIndex:1 withSubviewAtIndex:0];
The exchanging of the views is fine. The part that is missing is just some way of telling the subview, you're in front, update some properties.
There's a lack of details here. How are you "exchanging" the two views?
If you were using a UINavigationController as the container then viewWillAppear/viewDidAppear would be called whenever you push/pop a new viewController. These calls are made by the UINavigationController itself. If you ARE using a UINavigationController then make sure you have the prototypes correct for these functions.
- (void)viewWillAppear:(BOOL)animated
If you are trying to implement a view hierarchy yourself then you may need to make these calls yourself as part of activating/deactivating the views. From the SDK page of viewWillAppear;
If the view belonging to a view
controller is added to a view
hierarchy directly, the view
controller will not receive this
message. If you insert or add a view
to the view hierarchy, and it has a
view controller, you should send the
associated view controller this
message directly.
Update:
With the new details the problem is clear: This is a situation where you must send the disappear/appear messages yourself as suggested by the SDK. These functions are not called automagically when views are directly inserted/removed/changed, they are used by higher-level code (such as UINavigationController) that provides hierarchy support.
If you think about your example of using exchangeSubView then nothing is disappearing, one view just happens to cover the other wholly or partially depending on their regions and opacity.
I would suggest that if you wish to swap views then you really do remove/add as needed, and manually send the viewWillAppear / viewWillDisappear notifications to their controllers.
E.g.
// your top level view controller
-(void) switchActiveView:(UIViewController*)controller animated:(BOOL)animated
{
UIController* removedController = nil;
// tell the current controller it'll disappear and remove it
if (currentController)
{
[currentController viewWillDisapear:animated];
[currentController.view removeFromSuperView];
removedController = currentController;
}
// tell the new controller it'll appear and add its view
if (controller)
{
[controller viewWillAppear:animated];
[self.view addSubView:controller.view];
currentController = [controller retain];
}
// now tell them they did disappear/appear
[removedController viewDidDisappear: animated];
[currentController viewDidAppear: animated];
[removedController release];
}
I would just add an updataData method to each subview and call it at the same time you bring it to the front. You would need to add a variable to your root view controller to track the active subView:
[self.view exchangeSubviewAtIndex:1 withSubviewAtIndex:0];
if (subView1IsActive) [subView1Controller updateData];
else [subView2Controller updateData];