Still really new to iOS dev (have a background in asp.net / web), and I don't think I've quite gotten my head around how everything relates to everything else. For example, I'm building an app at the moment which starts with a NavigationController. I'm passing ViewControllers in and out of that quite happily and everything is working but now I need to add a rightbutton to the navigation controller. I have done this from within one of the ViewControllers like this:
- (void)viewDidLoad
{
UIBarButtonItem *change = [[UIBarButtonItem alloc] initWithTitle:#"CHANGE" style:UIBarButtonItemStylePlain target:self action:#selector(navAlert)];
self.navigationItem.rightBarButtonItem = change;
[change release];
[super viewDidLoad];
}
- (void)navAlert
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"nav pressed" message:#"You pressed the Change button" delegate:nil cancelButtonTitle:#"Yep" otherButtonTitles:nil];
[alert show];
[alert release];
}
but I don't want to have to do that in each controller - should something like this go into the AppDelegate and be called from there?
Also, suppose I didn't want to start with a NavigationController - could I use code in the AppDelegate to release ViewControllers and load new ones directly into the Window, by clicking a button which is set up in a ViewController?
Sorry if this is a bit of a stupid question, but I don't think I've quite grasped how the different layers relate, what code is available to which controller, etc.
Thanks
You should not put interface related code inside the appdelegate. That's what the ViewControllers are there for. If there's functionality which is common to all of your viewcontrollers, I'd suggest subclassing UIViewController (and then deriving all other instances from this subclass) or writing a category on it.
The appdelegate on the other hand is a good place to trigger basic events tied to the lifespan of the app, like setting up core data contexts or saving the application state when it is about to enter the background.
If what you are trying to do in viewDidLoad is have a proper "back" button in your UINavigationController, you don't need to do it that way. Simply define the title property of your controller to what you like. The controller's init method will do for that (no need in this case for viewDidLoad:
self.title = #"CHANGE";
or you can set it in the appDelegate:
controller.title = #"CHANGE";
As to your other question, best thing is start from scratch with a view based or window based project template.
Otherwise, you are quite right: you can release predefined controllers you don't need from the appDelegate and create your own. Additionally, you should take care of removing all those controllers' views from their parent:
[controller.view removeFromSuperview];
(for each controller you remove and for which there was a [self.window addSubview] in you delegate, possibly just the UiNavigationController).
Anyway, I don't know if I can suggest this path, because you would also need to fix all the dependencies related to the xib files, and would give you some headaches.
Related
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
I'm trying to learn Objective-C and iPhone programming, but I'm stuck with a problem. I have a utility application, and I have a text on my MainView and a button that change the text when I click it. Easy, and workes great. But what if I wan't to place the button on the "backside" in the FlipSideView, and still make it change the text on the frontside (MainView)? How do I get the views to talk together? I have tried a lot of different things, and searched for an answear, but can't seem to figure it out.
Would be great if someone had a answear, or maybe a link to a tutorial/example.
I suppose you've used the template which implements the following method in the MainViewController:
- (IBAction)showInfo:(id)sender {
FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:#"FlipsideView" bundle:nil];
controller.delegate = self;
...
}
As you can see it sets the delegate of the FlipSideController to the instance of the MainViewController.
A way would be to put an action into your FlipSideViewController, something like this:
- (IBAction)changeTextInMainView
{
[(MainViewController *)self.delegate changeText];
}
which is triggered when touching your button on the backside. You've got to wire it in IB as well as add the method to the header.
Then implement something like this in your MainViewController
- (void)changeText
{
self.myLabel.text = #"text changed to this";
}
Add this method to the header as well.
Another more elegant approach would be to save the text of your label in a property(maybe in it's own model class) which can be accessed from any view, by passing it by reference down the controllers. Then add a Key Value Observer from each viewController to the property which should show the text and update the view.
I'm new to iPhone development, and multiple views (xib or nib) are really confusing me. This is what i'm trying to achieve...
using TabBarControllerAppDelegate
Have 5 different TabBar Items and have created 5 different Views thru the TabBarController
The First View has a UIButton that is a Next button that needs to go to another view called View2.XIB.
I setup a new UIViewController which references the View2 and an IBAction for the switchPage, etc but not able to get it to do anything when clicking on the button.
All of My TabBar buttons work but not able to Navigate to anything outside of the Tabbar
Any help in this regard will be highly appreciated. Anyone have any examples
IBAction switchPageButtonPressed:(id)sender
{
[self.tabbarcontroller.tabBar setSelectedItem:[self.tabbarcontroller.tabBar.items objectAtIndex:1]];
here 1 means ur 2nd tabbar
}
It is difficult to find the problem without the code, but I will assume your action code for the switchPage button is incorrect. You should use code similar to the following:
- IBAction switchPageButtonPressed:(id)sender
{
ViewController2 *view2VC = [[ViewController2 alloc] initWithNibName:#"View2" bundle:nil];
[self presentModalViewController:nview2VC animated:YES];
[view2VC release];
}
If you are confident your method works, then you will want to verify that the action is hooked up correctly. The easiest way to do this is to place a breakpoint on the method and run the app in Debug. When you click the button, the debugger should break on your method, if it doesn't, you will need to check your connections in Interface Builder.
Before iOS4, my app's initial view controller would check a passcode on/off settings variable in viewWillAppear and if set on, present a modal passcode screen that would stay there until the correct passcode was entered or the Home button was pressed.
With iOS4, if my app has been in the background, I would like the user to feel comfortable that the data contained within the app is not easily accessible if they were to hand their phone to someone to use.
Since the app could return to any screen (the screen that the app was last on), I figured I would use the UIApplicationWillEnterForegroundNotification all over the place with local selectors (duplicate enterPasscode methods), each having the correct view controller to push based on the local screen, but there has to be a better way.
I may be having a slight conceptual misunderstanding here. Can someone suggest another approach or nudge me along to the next step. Can I have this as a shared method but still know the correct local view controller to push?
Thanks
EDIT/UPDATE:
Short version: It works, but may there still may be a better way (any help appreciated)...
I created a standard singleton with a view controller.
PasscodeView.h containing:
UIViewController *viewController;
#property (nonatomic, retain) UIViewController *viewController;
PasscodeView.m containing:
#synthesize viewController;
I put this in the AppDelegate:
-(void)applicationWillEnterForeground:(UIApplication*)application {
PasscodeView *passcodeView = [PasscodeView sharedDataManager];
UIViewController *currentController = [passcodeView viewController];
NSLog(#"%#", currentController); //testing
EnterPasscode *passcodeInput = [[EnterPasscode alloc] initWithNibName:#"Passcode" bundle:nil];
[currentController presentModalViewController:passcodeInput animated:NO];
[passcodeInput release];
}
and the following in all my viewDidLoad, updating the current view controller as I went into each screen (only 2 lines but still seems that there's a better way):
PasscodeView *passcodeView = [PasscodeView sharedSingleton];
passcodeView.viewController = [[self navigationController] visibleViewController];
I wish there were a way to have gotten the current view controller from applicationWillEnterForeground but I couldn't find it - any help here still appreciated.
For consistency, I changed a line and added a line to get a nav bar to match the rest of the app and to include a title.
UINavigationController *passcodeNavigationController = [[UINavigationController alloc] initWithRootViewController:passcodeInput];
[currentController presentModalViewController: passcodeNavigationController animated:NO];
You can centralize this behavior in your app delegate by implementing
-(void)applicationWillEnterForeground:(UIApplication*)application;
You might implement a singleton that stores the currently appropriate modal view controller, updating it in viewWillAppear in each of your view controllers.
Edit: I was assuming that you already had a series of view controllers that you wanted to show. I doubt you actually need it. If you have one called, say PasscodeInputController, then your applicationWillEnterForeground would look something like:
-(void)applicationWillEnterForeground:(UIApplication*)application {
UIViewController *currentController = [window rootViewController];
PasscodeInputController *passcodeInput = [[PasscodeInputController alloc] init];
[currentController presentModalViewController:passcodeInput animated:NO];
[passcodeInput release];
}
I hope that addresses your question more directly.
Your application's behaviour when reentering the foreground should be as similar as possible to when it launches for the first time. With this in mind, you could combine your applicationDidFinishLaunching: with applicationWillEnterForeground: but considering some views might already be loaded. The code is something like this:
id myState = (isReentering) ? [self validateState] : [self loadStateFromSave];
NSArray * keyForObjectToLoad = [self objectsToLoadForState:myState];
for(NSString * key in keyForObjectToLoad)
if(![self objectForKey:key]) [self loadObjectForKey:key];
Depending on your app's detail it might require initial work, but it has some benefits:
It will ensure launching and relaunching are similar, hence the user experience is not "random".
You can free many unwanted memory easier when in the background.
It's easier to manage your application's state from a central place.
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];
}
}