What is the right way to change XIB View which loaded at app start depending on some app settings. Of course I know how to get all settings I need.
In your application's delegate, in the method
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if(yourSettings)
myViewController = [[MyViewController alloc] initWithNibName:#"FirstNibName" bundle:nil];
else
myViewController = [[MyViewController alloc] initWithNibName:#"SecondNibName" bundle:nil];
self.window.rootViewController = self.myViewController;
[self.window makeKeyAndVisible];
return YES;
}
And that's it. Probably you may want to save your settings in your user defaults so you can load the view properly.
However, I would use 2 different view controllers, as you probably want them to do different things, not just modify some graphics.
EDIT-
Let me see if I understand. You will always load the first view controller and, if some conditions are met, you modally want to present the second view controller that will get dismissed at some point, returning the user to the first view controller. If this is the case, I suggest you move the code in your first view controller, in viewDidLoad or better yet in viewDidAppear, as this view controller will always get loaded. Also this way the user can see that he will eventually go to that view controller. I use something like this in applications the user needs to login to so that it will be obvious for him that he cannot continue until he does login.
I can't say that this is the right way to do it, because it's up to the programmer how he arranges his code, but it would seem to me that the place that controls what view and how it appears belongs in a view controller and not in the delegate, especially considering that your first view controller always gets loaded. It should be up to that view controller to see if it presents the second one or not.
Related
I am a newbie to iOS world and have started building custom code on top of a templated code.
So excuse me for the obvious.
The View chain starts with a MainWindow.xib which contains a App Delegate Object, a Window Object and Application ViewController. I dont understand why those objects are needed over there. But what I understand, I need to mention starting ViewController in the "Nib Name" Property to initiate my custom View Controller (called "EmptyViewController"). Its a dummy view controller, just there to avoid crash to happen as a result of missing valid viewcontroller.
I initiate a separate Modal View Controller(MainViewController) inside didFinishLaunchingWithOptions.
Code for initiating modal View Controller --
self.window.rootViewController = self.viewController;
mainView = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
// present the viewcontroller
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:mainView];
[self.viewController presentModalViewController:navController animated:NO];
// release it, because it's retained as modalViewController
[navController release];
I do not put this MainViewController inside MainWindow.xib as I want to have navigation at the root of MainViewController.
Inside MainViewController, I push HelpViewController when "help" button is pressed.
But HelpViewController does not show any navigation bar. I do not understand why?
Code for Pushing Navigation bar --
HelpViewController *helpVC = [[HelpViewController alloc] init];
[self.navigationController pushViewController:helpVC animated:YES];
[helpVC release];
So I would like to understand --
1) Why is MainWindow.xib needed? Can I remove it? (Note: I tried to remove it, but then I get blank screen)
1.a) Why are all the controls/objects App Delegate Object, a Window Object and Application ViewController objects needed?
2) Why doesnt HelpViewController show Navigation bar?
3) Another thing I noticed, if I say self.presentingViewController, EmptyViewController handle is returned while popViewController returns me back to MainViewController.
Thanks
The App Delegate simply implements some app-level 'callbacks' by which iOS communicates with your own code. In main.m you can see how iOS is told which of your classes implements UIApplicationDelegate. iOS creates an instance of this class and call these delegate methods ('callback') whenever appropriate (e.g. when the app goes to background).
The Window is something iOS provides, your app needs to tell what to display on it. And, as you saw, this is usually done in didFinishLaunchingWithOptions (which is called by iOS to inform your app things are ready to get started).
A View Controller is a class that handles states of stuff you show on the Window. You don't show stuff directly on the Window, but instead use Views. Every View Controller has a View with UI elements.
The XIB or NIB is a UI description/layout file. A XIB and View are linked together; you need to tell the XIB to which View Controller member (e.g. a UILabel) a UI element belongs, and you tell the XIB which View Controller method to call on a certain UI event (e.g. user taps on a button).
These are the basics. I'm aware it does not answer all your questions; I suggest you read the very good Apple documentation. Don't try to understand everything immediately as things, as you're experiencing, indeed can seem illogical at start.
I'm sure this has been asked countless times, and I've seen similar questions though the answer still eludes me.
I have an application with multiple view controllers and as a good view controller does its own task. However I find myself stuck in that I can't switch from one view controller to another. I've seen many people say "use a navigation controller" but this isn't what I want to use due to the unwanted view elements that are part and parcel to view controller.
I've done the following and have had limited success. The view controller is switched but the view does not load and I get an empty view instead:
- (IBAction)showLogin:(id)sender
{
PPLoginViewController *login = [[PPLoginViewController alloc] initWithNibName:#"PPLoginViewController" bundle:nil];
PPAppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
appDelegate.window.rootViewController = login;
[self.view insertSubview:login.view atIndex:0];
}
Using UINavigationController as a rootViewController is a good tone of creating iOS application.
As i understand unwanted view elements is a navigationBar? You can just hide it manually, setting:
[self.navigationController setNavigationBarHidden:YES];
And about your case, if you want to change you current viewController(targeting iOS 6), you can just present new one:
[self presentViewController:login animated:YES completion:nil];
or add child (Here is nice example to add and remove a child):
[self addChildViewController:login];
Why to set UINavigationController as a root?
1) First of all it makes your application visible viewcontrollers to be well structured. (Especially it is needed on iPhone). You can always get the stack and pop (or move) to any viewController you want.
2) Why I make always make navigation as a root one, because it makes the application more supportable, so to it will cost not so many code changes to add some features to the app.
If you create one (root) viewcontroller with a lot of children, or which presents other viewcontrolls, it will make your code really difficult to support, and make something like gode-object.
Listen to George, UINavigationController is the way to go. Your reasons for not wanting to use it are not valid.
However, the reason your code doesn't work might have to do with the unnecessary line after setting the rootViewController to the login vc.
Per Apple's documentation, setting rootViewController automatically sets the window's view to the view controller's view.
I have a problem with the UINavigationController. It about a client or serverside logout.
The idea for serverside logout is this, every 15 seconds a function is called that checks if the App is still logged in. If that is not the case then jump to the LoginViewController.
The Logout can also happen from the App itself. It executs simular code.
There are three relevant Controllers, LoginViewController is where we want to end up, SignOutController is where the 'Sign out'-Button is located and MainViewController.
Here are the relevant code parts.
First, the UINavigationController gets allcated like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
splashScreenViewController_ = [[SplashScreenViewController alloc] initWithNibName:#"SplashScreenViewController" bundle:nil];
uiNav_ = [[UINavigationController alloc] initWithRootViewController:splashScreenViewController_];
uiNav_.navigationBar.barStyle = UIBarStyleBlackTranslucent;
uiNav_.navigationBarHidden = YES;
[window_ setRootViewController:uiNav_];
[window_ makeKeyAndVisible];
return YES;
}
When the 'Sign out'-Button is pressed or the App figures out that the Server has forced a logout, this code is executed (same code, diffrent functions):
LoginViewController *loginView = [[LoginViewController alloc]initWithNibName:#"LoginViewController" bundle:nil];
[self.navigationController pushViewController:loginView animated:YES];
[loginView release];
If its a Serverside Logout it just reloads the MainViewController, every 15 seconds you see the animation of MainViewController sliding in. Its goes in a cycle from there, every 15 seconds it reloads.
If you click the 'Sign out'-Button it jumps to MainViewController instead of LoginViewController and starts the same cycle discribed above.
P.S. I have checked if any importend variable is nil, and I have checked that initalisation code is actually executed.
Edit: I think I did not mention that. My app works 99% of the time. Just one in a while this happen that the Sign-out button does not work and I start this cycle. Normally it works fine.
For the 15 second cycle where a new LoginViewController slides in it just seems you are not stopping to check if the app is logged in after realizing it wasn't. You should have some sort of boolean to store that and cancel the timer or whatever you use.
I don't understand what you say happens when you press the logout button, but I don't think you are making a good user interface.
I suggest you start the application by adding to the navigation controller the loginViewController as root. Then you add without animation the one you want to start with (for example MainViewController). Whenever the application is logged out of the service you pop the view controllers until the first one, which sould be the login one.
You have the method popToRootViewControllerAnimated: for that.
If you want to preserve the splash screen you can set it as the root view controller of the app, and chenge it to the uiNavigationController when you have finished loading.
The timer won't stop automatically just because you've pushed a view on top of another. It will be there until the controller it started on is released, which will only happen after it has been removed from the stack.
Also, you don't need to push MainViewController onto the stack after every check, you'll end up with multiple instances of it, each on top of another.
Also, without really knowing much about the architecture of the app, it would seem like a good idea to make LoginViewController modal, they really can't do anything if they've not logged in right? A modally presented viewcontroller wouldn't be affected by the navigation stack, and would also retain the users navigation stack much easier than having to manually push/pop controllers.
I will like my application that always starts as the first time I open it. I have several view controllers and when I exit my application and I open it again I see the view controller where I left of. Maybe I have to call the method applicationWillTerminate method.
I use this code to open a new view:
UIViewController *control = [[SomeViewController alloc] initWithNibName:#"SomeViewController"
bundle:nil];
UINavigationController *navControl = [[UINavigationController alloc]
initWithRootViewController:control];
[self presentModalViewController:navControl animated:NO];
[navControl setNavigationBarHidden:YES];
[control release];
[navControl release];
this code works great when linking it to buttons. But when I place that code in the applicationDidBecomeActive method it does not work.
The easiest way is to set UIApplicationExitsOnSuspend in Info.plist.
That really isn't the expected behaviour, though. Users expect to see the app "where they left off", especially if they've only briefly left the app (e.g. because they got a phone call).
Your code snippet adds a view controller, but is unlikely to work since your app delegate is not a UIViewController. It also doesn't do anything about removing the old view controllers.
EDIT: If all you need to do is display a splash screen (or something), then it's something like this:
In -applicationDidEnterBackground:, add a "splash screen" view (not a view controller) to self.window. (iOS takes a "screenshot" after you return from -applicationDidEnterBackground: and uses this in the app-switch animation; you want this to be what the user sees when switching back to your app)
In -applicationWillEnterForeground:, do whatever animations you want and eventually remove the view from the window (call -removeFromSuperview).
EDIT 2: The same will work in -applicationWillResignActive:/-applicationWillBecomeActive:, except this happens on a sleep/wake event, which might not be what you want...
I'd avoid using view controllers for this, because trying to shoehorn a view controller in the view controller hierarchy is likely to be problematic (for example, you have to figure out which VC to present it from, and you have to do the "right thing" if the user backgrounds your app while the VC is on screen, and...)
The reason it doesn't work in applicationDidBecomeActive is that method is only sent to the Application delegate, which doesn't know about presentModalViewController.
I suggest instead that in your appDelegate, implement applicationWillEnterForeground:, which should restore the state to a newly launched application (equivalent to what the state is at the end of application:didFinishLaunchingWithOptions: ).
OR...(edits)
If you just want a certain viewController to run (which is still loaded, right?)...For example, if you have a tab controller and just want to go to the root of the first view controller, put the following code into applicationWillEnterForeground:
UITabBarController * myTabBar = self.tabBarController;
myTabBar.selectedIndex = 0;
[[myTabBar.viewControllers objectAtIndex:0] popToRootViewControllerAnimated:NO];
Temporary solution:
I made my application crash on applicationWillResignActive method and it works. My application needs to run an animation when I launch it. But this works because next time the application runs it starts like the first time I opened it.
I'm in the process of porting an existing Android app to iOS, and I'm pretty inexperienced in this environment. The examples I've read so far for navigating multiple views all use some kind of visual user control for triggering the loading and unloading of views (tab bar, nav bar). This application's navigation needs to be pretty strict and not allow the user to freely move around between the three views.
The app needs to have a full screen splash view, a main view that the user interacts with, and a third view for data collection. The splash screen should appear, and the user should be navigated to the main view when tapping on the splash image. There will be custom logic in the main view controller for determining if data is required at which point it should navigate to the data collection view. Once valid data is entered and the user clicks OK, it should navigate back to the main view.
What I've read so far is that all Views should have an associated UIViewController class, and the easy way to do this is to create the XIB and UIViewController class in one shot and link them together (I have plenty examples/books/tutorials that I can reference for that part). I believe what I've read is that the app should have a root UIViewController that handles loading the others and navigating between them.
My questions are:
What class should I derive from for my main view controller that I use to load the others?
How do I wire that up to the app so that it knows to load this as the main controller?
What is the accepted standard way of having a navigation controller in the app and allowing the other views to obtain a reference to it? Should my UIViewControllers hold a reference to their parent controller, or should they ask the UIApplication for a reference to it when needed? How do I make sure I don't instantiate extra copies of the views and their controllers as the user navigates?
What class should I derive from for my
main view controller that I use to
load the others?
UIViewController
How do I wire that up to the app so
that it knows to load this as the main
controller?
Read the "Defining Your Subclass" section of View Controller Programming Guide for iOS. Scratch that -- read the whole thing. It's all important, you might as well start to learn it now. Also read App Programming Guide for iOS. Again, read the whole thing, but the application lifecycle part is the most relevant to your question.
What is the accepted standard way of
having a navigation controller in the
app and allowing the other views to
obtain a reference to it?
Again, this is explained well in View Controller Programming Guide. Views should never care about the navigation controller, but any view controllers that are part of a navigation stack have direct access to the nav controller via their respective navigationController properties.
Should my UIViewControllers hold a
reference to their parent controller,
or should they ask the UIApplication
for a reference to it when needed?
A view controller already has a reference to its parent controller in its (surprise!) parentController property. It's best for a controller to avoid assuming too much about its
parent, though. If the controller expects its parent to be a certain type or respond to certain messages, it becomes more difficult to reuse that controller or reorganize your application. Try to give the controller what it needs to do its thing when you create it. If the controller will need to ask for additional data or something like that, delegation is a good way to go.
How do I make sure I don't instantiate
extra copies of the views and their
controllers as the user navigates?
Exercise caution. There's not much danger of creating extra copies of views in a properly structured application because each view controller should take care of its own views. If you find yourself loading or otherwise creating views outside the context of the view controller that owns them, stop that.
It sounds like you can accomplish what you need with a couple of basic calls. To programmatically call a view controller:
- (void)showController {
MyViewController *myController = [[MyViewController alloc] initWithNibName:#"MyViewControllerXIB" bundle:nil];
[self.navigationController pushViewController:myController animated:YES];
[myController release];
}
To return to the previous view just call from any view controller:
- (void)goBack {
[self.navigationController popViewControllerAnimated:YES];
}
Read up on the documentation for the UINavigationController for more ways to move through views. This method is just one of many ways to do this and may not be suitable for all situations.
Not quite right — each UIViewController should know how to trigger its children. Apple's preferred navigation path through views is a branching tree, with the caveat of tab bars that collapse multiple view controllers into a single node on the tree.
You don't explicitly handle loading. Normally you have a sufficient relationship between your NIBs that the container classes are loaded automatically. Cocoa will then load the views whenever they're needed but not yet loaded (which is the purpose of loadView and viewDidLoad), and keep them unless and until a low memory warning requires them to be purged (leading to viewDidUnload). It's relatively rare that you explicitly load a NIB yourself (though table view cells are an obvious example where programmatically loading a NIB is quite common).
So you'd probably have:
a splash screen or preview of the first view controller, as the Default.png
a view controller that probably displays Default.png, and has two outlets going to the data collection controller and the main controller
when the user taps the button on the main screen, ask the model whether data collection is necessary. If so then navigate to the data collection controller, otherwise navigate to the main controller
give the data collection controller an outlet to the main controller and let it perform a navigation there at the appropriate moment
You get a MainWindow.xib for free when creating a new view based project. Probably the easiest thing to do is to put references to the three UIViewController subclasses in there, but set each of them to load from other files. Set the links between them in MainWindow.xib, set the links to things within the relevant views within the relevant XIBs.
That will prevent you from keeping multiple instances of any controllers about, and the built-in Cocoa loading mechanisms will ensure that the stuff that occupies significant amounts of memory — the views — is loaded only on demand and kept for no longer than space allows.
There's no need to link to parent view controllers. Every view controller already knows who presented it, via the parentViewController property. So if a view controller wants to dismiss itself and return to whoever presented it, you can just issue:
[self.parentViewController dismissModalViewControllerAnimated:YES];
Because the model is ideally a separate sovereign thing, all controllers really need to know is which other controllers they can present, how to populate themselves from the model and how to push data back to the model. You rarely end up with particularly complicated links between view controllers.
I think you should have the view loading/unloading in the application delegate and then each view should send notifications to the application delegate.
Here is the official introduction from Apple:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html
You can set before which ViewController to load first
If the application is navigation based use the following code:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MainViewController *mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
self.nav = [[UINavigationController alloc] initWithRootViewController:mainViewController];
[_window addSubview:nav.view];
[_window makeKeyAndVisible];
}
If the application is View based use the following code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}