Is there a way to open a new screen after the NStimer is on 0?
e.x:
-(void) randomMainVoid {
if (mainInt <= 0)
{
[randomMain invalidate];
//after counting a code to open a new file (new .h/.m/.xib file)
} else {
//something
}
}
If you're using the standard view controller/nib model, it's pretty easy to load a new screen. How you present it, however, will really depend on your application. But, as an example, if you wanted to present a new modal screen after the timer finished, and you had a view controller class named AfterTimerViewController with an associated nib file, you would present it as such:
-(void) randomMainVoid
{
if (mainInt <= 0) {
[randomMain invalidate];
// This assumes this method is defined in the current view
// controller. If not, replace self with the appropriate reference
AfterTimerViewController* controller = [[AfterTimerViewController alloc] initWithNibName:#"AfterTimerViewController" bundle:nil];
// Uncomment and use for pushing onto a navigation controller's stack
[[self navigationController] pushViewController:controller animated:YES];
// Uncomment and use if you want the new view controller to replace the root of your
// current navigation controller stack
//[[self navigationController] setViewControllers:[NSArray arrayWithObject:controller] animated:YES];
// Uncomment and use for presenting the new controller as a modal view controller
//[self presentModalViewController:controller animated:YES];
[controller release]; // change this if you're using ARC or taking ownership of this controller accordingly
} else {
//something
}
}
For more information on these methods, see the UIViewController documentation and the View Controller Programming Guide for iOS.
edit: I added sample code for a number of different common transitions. The best thing to do is to read the guides about View Controllers and their interactions so that you can use the pattern that best fits the design of your application. In general, tho, the sample code above shows how to react to events, create a view controller programmatically and present it.
Related
I'm working on my first app. Here's what I want to accomplish:
There will be a menu with several different options. For simplicity, assume this is comprised of UIButtons with IBAction outlets and the functionality exists to pull up the menu at any time.
Each menu button, when pressed, should display a different navigation controller's content. If the user brings up the menu and makes a different selection, the navigation controller in which he is currently operating should not be affected; the newly selected navigation chain is displayed on top of the old, and through the menu, the user can go back to the view where he left off on the previous navigation chain at any time.
visual illustration (click for higher resolution):
Please note that there are 3 different navigation controllers/chains. The root view controller (which is also the menu in this simplified version) is not part of any of them. It will not suffice to instantiate one of the navigation chains anew when it has been previously instantiated, and here's why: if the user was on screen 3 of option 2 and then selects option 1 from the menu and then selects option 2 (again) from the menu, he should be looking at screen 3 of option 2--right where he left off; the view controller he was viewing when he previously left the navigation chain should be brought back to the top.
I can make a button instantiate and present a view controller from the storyboard if there is NOT a navigation controller:
- (IBAction)buttonPressed:(id)sender {
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"View 2"];
[self presentViewController:controller animated:YES completion:nil];
}
However, I can't figure out how to make those two methods work with a navigation controller involved. Moreover, I'm not sure those two methods are the right choice, because I won't always want to instantiate a new view controller: when a menu button is pressed, a check should be performed to see if the view (navigation?) controller with the corresponding identifier has already been instantiated. If so, it should simply be made the top view controller.
In summary, here are my questions:
1) How should I instantiate and display a view controller that is embedded in a navigation controller, preferably using a storyboard ID? Do you use the storyboard ID of the navigation controller or of the view controller?
2) How should I check whether an instance already exists? Again, should I check for an extant navigation controller or for a view controller, and what's the best method to do so?
3) If the selected navigation chain has already been instantiated and is in the stack of view controllers somewhere, what is the best method for bringing it to the top?
Thank you!!
side note -- it would be nice to know how to paste code snippets with indentation and color formatting preserved :)
As Rob has suggested, a tab bar controller would make a good organising principle for your design.
Add a UITabBarController to your storyboard, give it a storyboard iD. Assign each of your three sets of viewControllers ( with their respective navController) to a tab item in the tabBarController.
UITabBarController
|--> UINavigationController --> VC1 ---> VC2 -->
|--> UINavigationController --> VC1 ---> VC2 -->
|--> UINavigationController --> VC1 ---> VC2 -->
In you app delegate make a strong property to hold your tab bar controller's pointer. As the tab bar controller keeps pointers to all of it's tab items, this will take care of state for each of your sets of viewControllers. You won't have to keep separate pointers for any of them, and you can get references to them via the tabBarController's viewControllers property.
#property (strong, nonatomic) UITabBarController* tabVC;
Initialise it on startup
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UIStoryboard storyBoard =
[UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
self.tabVC = [storyBoard instantiateViewControllerWithIdentifier:#"tabVC"];
//hide the tab bar
for (UINavigationController* navController in self.tabVC.viewControllers)
[navController.viewControllers[0] setHidesBottomBarWhenPushed:YES];
return YES;
}
An alternative way to hide the tab bar is to check the "Hides bottom bar on push" box in the Attributes Inspector for each of the (initial) viewControllers. You don't have to do this for subsequent viewControllers, just the first one that will be seen in that tab item.
Then when you need to navigate to one of your navController groups…
- (IBAction)openTab:(UIButton*)sender {
AppDelegate* appDelegate =
(AppDelegate*)[[UIApplication sharedApplication] delegate];
if ([sender.titleLabel.text isEqualToString: #"Option 1"]) {
appDelegate.tabVC.selectedIndex = 0;
}else if ([sender.titleLabel.text isEqualToString: #"Option 2"]){
appDelegate.tabVC.selectedIndex = 1;
}else if ([sender.titleLabel.text isEqualToString: #"Option 3"]){
appDelegate.tabVC.selectedIndex = 2;
}
[self presentViewController:appDelegate.tabVC
animated:YES completion:nil];
}
(this example uses presentViewController, your app design may navigate this in other ways…)
update
If you want to do this without a tab bar controller, you can instantiate an array holding pointers to each of your nav controllers instead:
UINavigationController* ncA =
[storyboard instantiateViewControllerWithIdentifier:#"NCA"];
UINavigationController* ncB =
[storyboard instantiateViewControllerWithIdentifier:#"NCB"];
UINavigationController* ncC =
[storyboard instantiateViewControllerWithIdentifier:#"NCC"];
self.ncArray = #[ncA,ncB,ncC];
Which has the benefit of not having a tab bar to hide…
Then your selection looks like…
- (IBAction)openNav:(UIButton*)sender {
AppDelegate* appDelegate =
(AppDelegate*)[[UIApplication sharedApplication] delegate];
int idx = 0;
if ([sender.titleLabel.text isEqualToString: #"option 1"]) {
idx = 0;
}else if ([sender.titleLabel.text isEqualToString: #"option 2"]){
idx = 1;
}else if ([sender.titleLabel.text isEqualToString: #"option 3"]){
idx = 2;
}
[self presentViewController:appDelegate.ncArray[idx]
animated:YES completion:nil];
}
1 / You can instantiate a viewController in your viewDidLoad method of your main viewController, so it will be instantiate 1 time only.
Now if you want display your controller, you would better push it :
- (IBAction)buttonPressed:(id)sender {
// Declare your controller in your .h file and do :
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"View 2"];
// Note you can move this line in the viewDidLoad method to be called only 1 time
// Then do not use :
// [self presentViewController:controller animated:YES completion:nil];
// Better to use :
[self.navigationController pushViewController:controller animated:YES];
}
2 / I'm not sure, but if you want to check if an instance already exist just check :
if (controller) {
// Some stuff here
} // I think this checks if controller is initiated.
3 / I know it's not a good advice but I would tell you to not worry about checking if your controller already exist, because I think it's easier to access your viewController by using the 2 lines again :
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"View 2"];
[self.navigationController pushViewController:controller animated:YES];
4 / I'm not sure if colors can be used here because of a specific style sheets.
I'm not sure to really have the good answer to your question but I hope this will help you.
Reusable button bars? gets me part of the way here, but now I'm having trouble with the "back button" requirements.
I need a layout solution that:
will work on iOS 5.0 and 6.0
has a custom view at the top with several buttons; this view should be reusable across every screen (scene), as opposed to duplicating the buttons manually in Interface Builder for each scene.
has a custom "back" button in that top custom view. With the design I have to implement, I cannot just use the default navigation bar
works well with the UINavigationController; when the user taps the "back" button, the main view controller (with the button bar) should stay, but the child view controller representing the actual scene content should go back to the previous scene.
The problem currently is that the "back" button won't change the child controller--it changes the parent controller, returning to the previous scene before the scene with the button bars. I've tried this several different ways. I'm not sure if I'm not doing it right, or if it can't be done.
One possibility is to implement my own "back" functionality, keeping a stack of child view controllers and manually changing them when the user taps "back." This is awkward, however, and poor design compared to using UINavigationController.
Perhaps I am going the wrong way with this. I can't accept duplicating the button bar across every single scene in Interface Builder... but perhaps I should create it programmatically, and then I can easily call that code from each and every scene. Then I would have "normal" view controllers, and using UINavigationController would be easier. But before I go that route and completely scrap what I have so far, I wanted to see if there was another way.
Here's an overview of some parts of my solution:
I created a ButtonBarController, laying out the Storyboard with a UIView for the buttons I wanted, and a UIView for the content pane. I also layered a button with the app logo (to go to the app's main screen) on top of a back button.
Then I created a controller for each of those other screens. In those subscreens/child view controllers, I would first add a UIView at the correct size to fit in my content pane, and then would add all the other controls I wanted. I had all of those child view controllers inherit from another controller, which took care of a few common tasks--such as procuring a reference to the button bar controller, and code to help resize the views for 3.5" versus 4" screens.
I created a changeToControllerWithIndex method; I call this when the app loads, when the user clicks one of the buttons in the main button bar to change scenes, or when anything happens in a scene requiring another scene change. I overload this method to provide two additional pieces of information: providing an NSDictionary with any extra information the child view controller needs, and to tell it whether this is a top-level scene, or whether we need a back button.
(Note: it's important to set the Storyboard ID for those child view controllers in the Identity Inspector. I kept accidentally setting the Title in the Attribute Inspector instead)
- (void)changeToControllerWithIndex:(NSInteger)index {
[self changeToControllerWithIndex:index withPayload:nil isRootView:YES];
}
// This is the method that will change the active view controller and the view that is shown
- (void)changeToControllerWithIndex:(NSInteger)index withPayload:(id)payload isRootView:(BOOL)isRootView
{
if (YES) {
self.index = index;
// The code below will properly remove the the child view controller that is
// currently being shown to the user and insert the new child view controller.
UIViewController *vc = [self setupViewControllerForIndex:index withPayload:payload];
if (isRootView) {
NSLog(#"putting navigation controller in");
childNavigationController = [[UINavigationController alloc] initWithRootViewController:vc];
[childNavigationController setNavigationBarHidden:YES];
[self addChildViewController:childNavigationController];
[childNavigationController didMoveToParentViewController:self];
if (self.currentViewController){
[self.currentViewController willMoveToParentViewController:nil];
[self transitionFromViewController:self.currentViewController toViewController:childNavigationController duration:0 options:UIViewAnimationOptionTransitionNone animations:^{
[self.currentViewController.view removeFromSuperview];
} completion:^(BOOL finished) {
[self.currentViewController removeFromParentViewController];
self.currentViewController = childNavigationController;
}];
} else {
[self.currentView addSubview:childNavigationController.view];
self.currentViewController = childNavigationController;
}
[self.currentView addSubview:childNavigationController.view];
//We are at the root of the navigation path, so no back button for us
[homeButton setHidden:NO];
[backButton setHidden:YES];
} else {
//Not a root view -- we're in navigation and want a back button
[childNavigationController pushViewController:vc animated:NO];
[homeButton setHidden:YES];
[backButton setHidden:NO];
}
}
}
Then I have an overloaded method to set up each individual view controller... some require a little more preparation than others.
- (UIViewController *)setupViewControllerForIndex:(NSInteger)index {
return [self setupViewControllerForIndex:index withPayload:nil];
}
// This is where you instantiate each child controller and setup anything you need on them, like delegates and public properties.
- (UIViewController *)setupViewControllerForIndex:(NSInteger)index withPayload:(id)payload {
UIViewController *vc = nil;
if (index == CONTROLLER_HOME){
vc = [self.storyboard instantiateViewControllerWithIdentifier:#"Home"];
} else if (index == CONTROLLER_CATEGORIES){
SAVECategoryViewController *child = [self.storyboard instantiateViewControllerWithIdentifier:#"Categories"];
if (payload) {
child.currentCategory = [(NSNumber *) [(NSDictionary *)payload objectForKey:ATTRIBUTE_CAT_ID] integerValue];
} else {
child.currentCategory = CATEGORY_ALL;
}
vc = child;
} //etc for all the other controllers...
payload = nil;
return vc;
}
I mentioned my difficulty with managing the "back" navigation. The above code ensures the navigation controllers maintain a proper "back" history, starting fresh whenever we use one of the button bar buttons to change screens. When we do use buttons inside a child controller to navigate from scene to scene, this is how we can go back:
- (IBAction)backButtonPressed:(id)sender {
[childNavigationController popViewControllerAnimated:YES];
if ([[childNavigationController viewControllers] count] <= 1) {
//Root view
[homeButton setHidden:NO];
[backButton setHidden:YES];
}
}
I think you need to implement at least one custom container view controller - the root view controller. That would be the one to host the custom button bar. Below the button bar you would add a UINavigationController the manage your other VCs. Look at this for starters:
#implementation RootVC
//...
- (void)viewDidLoad
{
self.navVC = [[UINavigationController alloc] initWithRootViewController:someOtherVC];
self.navVC.navigationBarHidden = YES;
self.navVC.view.frame = ...;
[self addChildViewController:self.navVC];
[self.view addSubview:self.navVC.view];
[self.navVC didMoveToParentViewController:self];
}
- (void)backButtonTouched:(UIButton *)button
{
[self.navVC popViewControllerAnimated:YES];
}
I'm attempting to convert our application to storyboards and have hit what I believe is a bug in the handling of unwind segues when dealing with custom container controllers. We have a view controller which displays another and uses the view controller containment api to do this, I wire up the segue in IB then select a custom class for the implementation. The perform method looks something like this:
-(void) perform {
UIViewController *container = [self sourceViewController];
UIViewController *child = [self destinationViewController];
[container addChildViewController:child];
[container.view addSubview:child.view];
child.view.center = container.view.center;
[UIView transitionWithView:container.view
duration:0.35
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
child.view.alpha = 1;
} completion:^(BOOL finished) {
[child didMoveToParentViewController:container];
}];
}
That works perfectly, however I can't make it perform the unwind segue back to the container controller. I override viewControllerForUnwindSegueAction: fromViewController: withSender: and ensure that it's returning the correct value:
-(UIViewController *) viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
id default = [super viewControllerForUnwindSegueAction:action fromViewController:fromViewController withSender:sender];
NSAssert1(default == self, #"Expected the default view controller to be self but was %#", default);
return default;
}
I can also confirm that canPerformUnwindSegueAction:fromViewController:withSender is being called and doing the right thing, but to be sure I overrode it to return YES
-(BOOL) canPerformUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender {
return YES;
}
The next step I would expect to happen is for segueForUnwindingToViewController:fromViewController:identifier: to be called, however it never is. Instead the application crashes with an NSInternalInconsistencyException.
2012-10-01 10:56:33.627 UnwindSegues[12770:c07] *** Assertion failure in -[UIStoryboardUnwindSegueTemplate _perform:], /SourceCache/UIKit_Sim/UIKit-2372/UIStoryboardUnwindSegueTemplate.m:78
2012-10-01 10:56:33.628 UnwindSegues[12770:c07] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not find a view controller to execute unwinding for <USCustomContainerViewController: 0x75949a0>'
*** First throw call stack:
(0x1c8e012 0x10cbe7e 0x1c8de78 0xb61f35 0x581711 0x45ab54 0x10df705 0x16920 0x168b8 0xd7671 0xd7bcf 0xd6d38 0x4633f 0x46552 0x243aa 0x15cf8 0x1be9df9 0x1be9ad0 0x1c03bf5 0x1c03962 0x1c34bb6 0x1c33f44 0x1c33e1b 0x1be87e3 0x1be8668 0x1365c 0x1e7d 0x1da5)
libc++abi.dylib: terminate called throwing an exception
Has anyone successfully used unwind segues combined with the view controller containment APIs? Any idea what step I'm missing? I've uploaded a demo project to github which shows the issue in the simplest demonstration project I could come up with.
The problem in your example is that there's no there there. It's too simple. First, you create your container view controller in a rather odd way (you don't use the new IB "container view" which is there to help you do this). Second, you've got nothing to unwind: nothing was pushed or presented on top of anything.
I have a working example showing that canPerformUnwindSegueAction really is consulted up the parent chain, and that viewControllerForUnwindSegueAction and segueForUnwindingToViewController are called and effective, if present in the right place. See:
https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/ch19p640presentedViewControllerStoryboard2
I have now also created a fork of your original example on github, correcting it so that it illustrates these features:
https://github.com/mattneub/UnwindSegues
It isn't really a situation where "unwind" is needed, but it does show how "unwind" can be used when a custom container view controller is involved.
This seems to be a bug – I would also expect unwind segues to work as you implemented.
The workaround that I used is explicitly dismissing the presented view controller in the IBAction method:
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController
fromViewController:(UIViewController *)fromViewController
identifier:(NSString *)identifier
{
return [[UIStoryboardSegue alloc] initWithIdentifier:identifier
source:fromViewController
destination:toViewController];
}
- (IBAction)unwind:(UIStoryboardSegue*)segue
{
UIViewController *vc = segue.sourceViewController;
[vc willMoveToParentViewController:nil];
if ([vc respondsToSelector:#selector(beginAppearanceTransition:animated:)]) {
[vc beginAppearanceTransition:NO animated:YES]; // iOS 6
}
UIView *modal = vc.view;
UIView *target = [[segue destinationViewController] view];
[UIView animateWithDuration:duration animations:^{
modal.frame = CGRectMake(0, target.bounds.size.height, modal.frame.size.width, modal.frame.size.height);
} completion:^(BOOL finished) {
[modal removeFromSuperview];
[vc removeFromParentViewController];
if ([vc respondsToSelector:#selector(endAppearanceTransition)]) {
[vc endAppearanceTransition];
}
}];
}
Brief history before the answer: I just ran into the same exact error message when trying to use multiple Container Views on one iPad screen in iOS 6 and calling unwind segues from code. At first I thought this may be a problem because my segue was created using Storyboards by CTRL-dragging from File Owner to Exit instead of from some UI control to Exit, but I got same results when I put test Close buttons on each VC and had them trigger the unwind segues. I realized that I'm trying to unwind an embed segue, not a modal/push/popup segue, so it makes sense that it fails to do it. After all, if the unwind segue succeeds and the view controller is unloaded from a Container View, iOS 6 thinks there'll just be an empty space on the screen in that spot. (In my case, I have another container view taking up screen real estate that's shown behind the container view which I'm trying to unload, but iOS doesn't know that since the two aren't connected in any way.)
Answer: this led me to realize that you can only unwind modal, push, or popover segues, be it within the main window or as part of a Navigation/Tab Controller. This is b/c iOS then knows that there was a previous VC responsible for the whole screen and it's safe to go back to it. So, in your case, I'd look into a way to tell iOS that your child container view is connected to your parent container view in a way that makes it safe to dismiss the child container view. For example, perform a modal/push/popover segue when displaying the child container view, or wrap both into a custom UINavigationController class (I assume you don't want the navigation bar, that's why custom class).
Sorry I can't give exact code, but this is the best I got to so far and I hope it's helpful.
Looks like this bug is fixed in iOS9.
I have a UIViewController subclass that contains an instance of UIImagePickerController. Let's call this controller CameraController. Among other things, the CameraController manages the UIImagePickerController instance's overlayView, and other views, buttons, labels etc. that are displayed when the UIImagePickerController, let's call this instance photoPicker, is displayed as the modal controller.
The photoPicker's camera overlay and the elemets that are part of the CameraController view hierarchy display and function as expected. The problem I'm having is that I cannot use UIViewController's default initializer to create the CameraController's view heirarchy.
I am initializing CameraController from within another UIViewController. Let's call this controller the WebViewController. When the user clicks on a button in a view managed by WebViewController, the launchCamera method is called. It currently looks like this:
- (void) launchCamera{
if (!cameraController) {
cameraController = [[CameraController alloc] init];
// cameraController = [[CameraController alloc] initWithNibName:#"CameraController"
// bundle:[NSBundle mainBundle]];
cameraController.delegate = self;
}
[self presentModalViewController:cameraController.photoPicker animated:NO];
}
I want to be able to create CameraController by calling initWithNibName:bundle: but it's not working
as I'll explain.
CameraController's init method looks like this:
- (id) init {
if (self == [super init]) {
// Create and configure the image picker here...
// Load the UI elements for the camera overlay.
nibContents = [[NSBundle mainBundle] loadNibNamed:#"CameraController" owner:self options:nil];
[nibContents retain];
photoPicker.cameraOverlayView = overlay;
// More initialization code here...
}
return self;
}
The only way I can get the elements to load from the CameraController.xib file is to call loadNibNamed:owner:options:. Otherwise the camera takes over but no overlay nor other view components are displayed. It appears that a side-effect of this problem is that none of the view management methods on CameraController are ever called, like viewDidLoad, viewDidAppear etc.
However, all outlets defined in the nib seem to be working. For example, when the camera loads a view is displayed with some instructions for the user. On this view is a button to dismiss it. The button is declared in CameraController along with the method that is called that dismisses this instructions view. It is all wired together through the nib and works great. Furthermore, the button to take a picture is on the view that servers as photoPicker's overlay. This button and the method that is called when it's pressed is managed by CameraController and all wired up in the nib. It works fine too.
So what am I missing? Why can't I use UIViewController's default initializer to create the CameraController instance. And, why are none of CameraController's view mangement methods ever called.
Thanks.
Your problem is easy but need some steps.
Well... First, if overlay is an IBOutlet, it can not be loaded at init time. So move picker and co in viewDidLoad. Place also here all other items that your say that they are not loaded. They should be loaded there (viewDIDLoad). Check that outlets are connected.
Second, call
cameraController = [[CameraController alloc] initWithNibName:#"CameraController"
bundle:nil];
and ensure that CameraController contains (just) a view, and CameraController inherits UIViewController. Check also file's owner.
And at some time, you may consider that calling :
[self presentModalViewController:cameraController.photoPicker animated:NO];
does not make the CameraController control your picker. Does that make sense to you ?
What does that do regarding your problem ?
It seems you are confusing some things. I try to explain in another way :
The one that controls the picker is the one that is its delegate. Your may consider creating in a MAIN view.
The controller of the overlay (added as subview) is the one that own its view in File's Owner. That may be created from the MAIN view, adding its view as subview of the controller. Basically, it is loaded just to get the overlay, but viewDidLoad, ... won't be called.
That's all and I belive those steps are not ok in your code.
That should give something like :
MainController
Loadcamera {
self.picker = [UIImagePicker alloc] init.....];
self.picker.delegate = self;
SecondController* scnd = [[SecondController alloc] initWithNibName:#"SecondController" bundle:nil];
[self.picker addOverlay:scnd.view];
[self presentModalViewController:self.picker animated:NO];
}
/// And here manage your picker delegate methods
SecondController
// Here manage your IBActions and whatever you want for the overlay
I have a rootController and 5 contentControllers, each as its own class.
I want to be able to call the next content controller from the current content controller. For example, if I'm currently showing contentController1, I want a way to show contentController2.
It'd be ideal if I could add a short method to every controllers' implementation file that passed the number of the controller to be called to the actual method that loads and shows the new controller.
For example:
#implementation ContentController1
- (int) loadNextController {
//take the 1 from ContentController1, add 1 to it, and pass it somewhere else
}
Then somewhere else (the root controller? the app delegate?) add the following method that then loads and shows the contentController based on the int sent from the (int) loadNextController method:
-(void) loadNextController: (int) nextController {
//init and show controller
}
If you could show me the code and, more importantly, where it goes, I would really appreciate it.
Trevor
It's not clear exactly how you want these view controllers to relate to each other. For example, you might want to push each one in turn onto a navigation controller's stack, so that the user always has the option of going back through the previous view controllers. Or, you might have a requirement that says that the user has to go through the five controllers in succession without the option to go back, in which case you'd probably set the window's rootViewController property to each controller when its time comes. Your decision will determine how you write the code that presents each controller's view. Read View Controller Programming Guide for details.
The design you describe above has each view controller deciding which view controller to present next. That's often appropriate, especially where the view controllers form a hierarchy. From your description, though, it seems that it might be helpful to concentrate all the knowledge about the sequence in which controllers are presented in one object. Make that object the delegate of each controller, and have that object (possibly the application delegate) determine the order of the controllers. As an (untested) example, you might add this to your app delegate:
-(void)application:(UIApplication*)app didFinishLaunchingWithOptions:(NSDictionary*)options
{
//...
self.controllers = [NSArray arrayWithObjects:
[[[ViewControllerA alloc] initWithNibName:nil bundle:nil] autorelease],
[[[ViewControllerB alloc] initWithNibName:nil bundle:nil] autorelease],
//...
[[[ViewControllerN alloc] initWithNibName:nil bundle:nil] autorelease]];
// Make self the delegate for each of the view controllers.
[self.controllers setValue:self forKey:#"delegate"];
[self.navigationController pushViewController:[self.controllers objectAtIndex:0] animated:NO];
}
-(void)viewControllerDidFinish:(UIViewController*)controller
{
NSUInteger index = [self.controllers indexOfObject:controller];
NSUInteger nextIndex = index + 1;
if (nextIndex < [self.controllers count]) {
[self.navigationController pushViewController:[self.controllers objectAtIndex:nextIndex animated:YES];
}
}
Then, whenever a view controller is ready to switch to the next controller, it can just call:
[self.delegate viewControllerDidFinish:self];
Again, the idea here is that the order of the view controllers in the array determines the order in which the controllers will be presented. I've used a nav controller here, but you don't have to. Also, you'll probably want to declare a protocol with your -viewControllerDidFinish:(UIViewController*)controller method, adopt that protocol in your app delegate (or whichever object manages the controllers), and have your controllers' delegate properties specify that protocol. That'll avoid any warnings about the delegate not implementing the method.