Greetings! I have a working iPhone app (huzzah!) that uses a MainView.xib containing a fully-stocked UITabBar with several UINavigationController objects and views at-the-ready.
I've now been asked to add a one-time registration view to this mix. This view would appear before the UITabBar at app-launch, get some info from the user, register with a server - or check for an existing registration, then squirrel some data away in the keychain. (If the keychain already shows proof of registration, then we skip showing this particular view.)
The registration and keychain part I've got under control (thank you Erica Sadun for the latter!), but showing that initial one-time view is proving to be trickier than I expected.
I suspect I'm too close to the problem to see what's wrong. I really hope it's pilot error and doesn't require anything too Rube Goldberg!
Here's the scenario:
The app starts by loading MainView.xib, in which lies the aforementioned UITabBar controller, et. al. For the sake of argument, let's say we must show that registration view. Also, we'd like it to have a modal appearance, so it will fly in from the bottom up. Then, when we're done, we can dismiss it, call a delegate (most likely the App Delegate) and tell it to carry on with the original UITabBar.
// Normally, the Tab Bar Controller's view is added to the window ... still do this?
[window addSubview:tabBarController.view];
// We could now set up a VC like so. Mostly harmless. (I know, "mvc" is an unfortunate abbreviation in this case.)
RegistrationVC *mvc = [[RegistrationVC alloc] initWithNibName:#"RegistrationView" bundle:nil];
Note that RegistrationView.xib has a UIView inside, but no nav controller. We want to keep it decoupled so that it can be reused, say, as part of a tab bar item's nav controller (to review your registration info, for instance).
Moving on. We create a nav controller with the intent of presenting things modally:
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:mvc];
We then present our modal VC, using the tab bar controller as the basis, and release the alloc'ed bits.
[tabBarController presentModalViewController:nc animated:YES];
[nc release];
[mvc release];
First observation. Something tells me this is just plain sloppy. You can see the first tab bar item's nav bar and view appear just as the modal view swoops in. Yeccch! Moreover, trying to set the selected VC to nil beforehand has no effect:
tabBarController.selectedViewController = nil;
We really don't want/need to use the tab bar until after the modal VC is done (and we have the delegate to help let us know when that happens).
Why am I even bothering with the Tab Bar? Well, it looks like I need something to hang that modal VC's hat on, and I don't know what else there is to use.
Is this the only way? It just seems to tether the Registration VC and the Tab Bar unnecessarily, and it just smells ... wrong.
Clues welcome/appreciated!
It's hard to answer this without knowing what your Default.png shows. Assuming you're following the HIG and it displays an empty tabBarController, I'd suggest a somewhat complicated layering:
bottom view: tabBarController.view
middle view: UIImageView: Default.png
top view: registration view positioned below the bottom of the screen
On startup, if you need to show the registration view, manually animate it upward, and once the animation is done remove the UIImageView below it. When registration is complete, manually animate the registration view downward to reveal the tabBarController. If on startup you don't need the registration view, just animate the UIImageView to fade away (or just remove it).
OTOH hand, if you're not following the HIG and instead showing some kind of splash screen, things get a bit easier. Layer like this:
bottom view: tabBarController.view
top view: UIImageView: Default.png
If you need to show registration, do presentModalViewController with animated:NO and then fade out the UIImageView. If not, just fade out the UIImageView.
That's a lengthy explanation w/o pictures, hope it makes sense. The salient point is that I'm suggesting adding a UIImageView:Default.png to be the first thing that is seen when the app starts, and use that to guide your transition into registration or tabBarController as appropriate.
Related
Ultimately, I'd like to know how to store a UIView (globally, if necessary) from one view controller, so that it can be referenced, and called back up to the top, from within another view controller?
I've built an app that has a Home tab and a Guide tab as the two main tabs, but the Guide tab's functionality is a bit unique in that it's a dynamic tab, which will ideally show different content depending on what "guide" has been chosen from the Home tab. Thus, on the Home tab, there are several UIButtons that are meant to load a given guide. Each guide is simply a collection of images that the user can interact with. No problem there.
Now, on the Home tab, when a user selects the guide they want to view, I wanted to transition between the Home tab and the Guide tab with a CurlDown animated transition. This is the only place I'll have a UIView transition animation—meaning normal tab switching by the user will be the standard instant transition without effects.
Here's how I have implemented the Home-to-Guide transition when a user selects a guide from the Home tab. This is done in the HomeViewController, as an IBAction assigned to all the guide-loading UIButtons, each of which has a unique tag that tells the GuideViewController which guide to load:
- (IBAction)loadGuide:(id)sender
{
UIButton *button = (UIButton*) sender;
GuideViewController *guideController = [[GuideViewController alloc] initWithNibName:#"GuideViewController" bundle:nil];
self.guideViewController = guideController;
[self.guideViewController activateGuide:button.tag];
[UIView transitionFromView:self.view
toView:guideController.view
duration:1.0
options:UIViewAnimationOptionTransitionCurlDown
completion:^(BOOL finished){
UITabBarItem *tabBarGuide = [[self.tabBarController.tabBar items] objectAtIndex:1];
[tabBarGuide setEnabled:TRUE];
self.tabBarController.selectedIndex = 1;
}];
[guideController release];
}
// End (IBAction)loadGuide
This works really well, up until the point where I then have to enable the Guide tab and switch to it. What happens at that point seems to be that another instance of the GuideViewController is created and replaces the one that was initially instantiated and transitioned to from the HomeViewController as shown above.
So I thought maybe I could work around it by, instead, moving the guide-loading to a new view, on that isn't used with the UITabBar, then load and transition to this view from the Home tab, just like I did in the above code. Then, after switching tabs, I'd need to somehow recall it into my Guide tab's view controller, as a subview or the like.
I'd think the view would still exist in memory after the tab-switching process, I just don't know how to access it, so I can show it again after the Guide tab's view has hidden it. Should I assign the view to some sort of globally accessible variable from the HomeViewController so that I can then access it from the GuideViewController (Guide tab's view)?
I'm open to any alternate suggestions as well, if you think this approach is bad form! I initially tried to overcome the problem by loading the details of the active guide into the database, then recovering that data when the Guide tab's view controller kicked in. However, the Guide tab's view must be cached or something, because if the user goes back to the home page and chooses a new guide, it'll animate the transition to that guide, but then when the Home-to-Guide-tab-switch kicks in, it flips back to the same state that the Guide tab's view was in before the user went back to the home page to choose a new tab. Also, I noticed the ViewDidLoad method doesn't get called again when manually nor programmatically switching tabs, so I figured that the other view must still be around in memory too! (The "other view" being either the original instance of the GuideViewController's view initiated from the Home tab upon selecting a guide button, or a new view as previously mentioned that would essentially do the same thing, albeit in a separate class.)
So to summarize:
I have two tabs, Home and Guide
Home has several UIButtons that when tapped will load a specific guide
When a UIButton on Home is tapped, it transitions from the Home tab's view to the Guide tab's view view CurlDown, without first switching to the Guide tab because that would lose the CurlDown effect
Then I have to trigger the tab switch programmatically, which seems to load a new instance of the Guide view, which effectively covers up the other instance
I want to know if I can somehow just call back up the first instance by somehow maybe storing the other instance as a global or some such?
Hense the question about how to pass a UIView instance around several UIViewControllers
If I were you I'd ditch the transition and just the tabbar as normal.
Firstly as it's giving you problems, but also and more importantly as it would seem strange to the end user who is used to tab bars just switching when you press the tab (well it would to me).
I'd read and re-read Apple's HIG on tabbars too. I think your users will find it very confusing that something other than the tab bar buttons causes the selected tab to switch.
You would have the data in the model.
The home tab would specify to the model which guide is chosen.
The guide would view load it's data every time it appears.
If you do need to keep the transition, then ditch the guide tab and use a navigationbar inside the home tab. You mention that screen space is the one issue for not doing so, but you can have the navigation bar present but not visible. You could then have a button in the guide tab to close that viewcontroller (pop the guide vc).
Another option might be to present the guide view modally instead - but the partial page curl also restricts screen space a little.
The ideal approach to doing this is to store a retained reference to the UIView object in a persistent object such as the app delegate subclass UIApplicationDelegate. The app delegate can then instantiate the view. Then, each controller needing to show the view would just ask the app delegate for the view. Also, if a root view controller is always available, it could be retained there as well.
I am putting an iPad application together that allows a user to work their way through a virtual tour. They are able to move forward through screens on which some will have buttons to other material such as a video or more info.
If Keynote supported Hyperlinks then it would be well suited but as it doesn't I am trying to recreate the tour within Xcode.
I am a newbie but have spent time researching and have code to display the 'slides' and the capability to move forward and back through them. The slides are no more that an image view with a full screen graphic and buttons for the various options, some slides are simple and have nothing other than back and forward but others will have additional links
However doing it in this simplistic way means I am ending up with a huge number of view controllers and XIB files, currently at 75 which I know must be more than any app should have. However it does work although on occasions when running it on the device and not in the simulator it will bomb out.
My questions are is there a limit to the number of view controllers in one app and will having a large number cause the instability? I'm aware of other ways to handle the views such as having them in arrays and pushing them out a single view controller but this won't give me the flexibility to tailor slides for different content.
I'd welcome any help or advice and I hope have gone about posting this question in the right way (its my first)
Many Thanks
Kieron
The code I am using to manipulate the view is
-(IBAction)goBack {
[self dismissModalViewControllerAnimated:NO];
}
-(IBAction)goForward {
Slide5ViewController *screen = [[Slide5ViewController alloc] initWithNibName:nil bundle:nil];
screen.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:screen animated:YES];
[screen release];
}
Kieron,
Why not have one "slide" view controller and a different image only? Use some sort of data structure to keep information about the buttons, images, and pathways for each slide, and then just keep re-using the same view controller for each slide?
The view controller can then dynamically load each image as it transitions between the currently visible view and the next instantiation of itself... It should be possible using only 1 view controller.
If you're comfortable with using Interface Builder, keep using the XIB files to lay everything out. However, instead of setting each "File's Owner" to a different view controller, set them all to the same one. Then, inside your IBAction methods (when the user pressed a button), use some logic to say "I am on this view right now, and the user pressed this button, so which one should I go to next?"
Then, call a method like loadNewSlide: that might look like this:
- (void) loadNewSlide:(NSInteger)slideNumber
{
// Make a string with the new XIB name
NSString* xibName = [NSString stringWithFormat:#"slide-%d",slideNumber];
// Create the next slide view controller (it doesn't matter if you create a slide view
// controller from within another slide view controller, remember, they are all just
// objects)
SlideViewController *newSlideViewController = [[SlideViewController alloc] initWithNibName:xibName bundle:nil];
// Change the view
UIWindow *theWindow = [self.view superview];
[self.view removeFromSuperview];
[theWindow addSubview:newSlideViewController.view];
// Release, the view stack now should be retaining the view controller instead
[newSlideViewController release];
}
This will work MUCH better than running "modally" with 75 view controllers (as you had previously suggested) because this will only keep 1 slide in memory at a time - whatever you are currently looking at - and then will load the next slide just in time to move to it.
Fist of all, what error is in the log?
Did you properly implemented viewDidUnload method of view controllers? View controllers should be able to unload loaded xib. Also, release data in didReceiveMemoryWarning.
Second, it could be better to use UINavigationController to handle view controllers stack instead of modal view controllers stack. You can hide navigation bar or customize it.
I have a number of viewControllers (iPad) that manage different views presenting various screens to the user (start screen -> settings screen -> main screen -> details screen -> summary screen).
Those screens are being traversed sequentially (as arrows above indicate) based on user interaction.One exception to that rule is that I should be able to navigate to start screen from every other screen.
I dont want to allow a user to explicitly navigate through those screens (using navigationbar) - only app logic should do that.
How should I handle such viewControllers presentation logic? Should i use NavigationController with navBar hidden and pop/push viewcontrollers on it? Or is it an unnecessary overkill? Maybe simply adding viewController.view to subviews of root view will be enough?
Sorry if the question is silly but i think i still didint get MVC in iOS quite right
Yep, NavigationController is your friend in this type of scenario. The basics are:
[self.navigationController pushViewController:newViewController animated:YES];
...to add each subsequent view controller on to the stack, and ...
[self.navigationController popToRootViewControllerAnimated:YES];
...to get you back to the start screen.
And it is certainly possible to hide the navigationBar in your viewDidLoad method of each view controller, however this will prevent you from being able to go back in the stack. If that is intentional, then I think it's a perfectly valid set up.
Something is puzzling me, after going through my app with instruments, it is a UINavigationBased App, it noticed this.
Each time a tableView cell is tapped and I do this:
GenericTableViewController *someViewController = [[Generic TableViewController alloc] init];
[self.navigationController pushViewController:someViewController animated:YES];
[someViewController release];
I then tap the back button and look in Instruments to see how many living instances of the GenericTableViewController there exists. There is 1 before the back tap, and 0 afterwards.
This is great, good clean memory management by the UINavigationController.
However, other places in the app I don't use the back button to leave a view, in some viewControllers tapping one of my custom buttons will run code completely like the above, but the viewController, in which I tapped the custom button, will not be released.
I guess I understand why, the navigationController makes a judgment call, that I might be pushing something on to the stack and that I will return later, so it keeps the controller in its memory. This must mean that tapping the back button uses a [self.navigationController popViewController]. Ok.
The problem is now that each time I visit a view that isn't left using a "back" button in the navigationBar, it
will not be released. The count allocation in Instruments just goes up each time I visit a view, until I have like 20 objects living.
I tried putting a [self.navigationController popViewController] in the selector for my custom buttons. Meaning that I manually pop the visible viewController before pushing a new on to the stack.
But the allocation count does not go down? guess the "back" button does not use this technic after all...
I also tried using popToViewController, since I always know if the controller has been on the stack or not. But to use the popToViewController, I need to keep a reference around for the view.
Is there some sound way of saying to the UINavigationController: If this viewController is on your stack, please display it, if not please instantiate it, push to the stack and display it.
The challenge here is probably that not all my navigation in the NavigationController is completely linear. I will sometimes have to go from stack item no. 2 to stack item no. 5, back to 3 etc.
So is there a way to do this, while still having the UINavigationController making sure to release my viewControllers and not instantiating the same viewController several times?
You could try to access UINavigationController's viewControllers property to get a NSArray of all the view controllers currently on the navigation stack. Then use containsObject: to check if the view controller is already on the stack.
If it is, then use popToViewController:animated: to show the view controller. If it is not on the stack then just use pushViewController:animated:.
If this does not help you, maybe setViewControllers:animated: is the right method.
I posted earlier but am running into similar problems again. Basically the way that my app is setup there is a top bar that is basically just a static image that has UIButtons placed on top of it. That is the Main View Controller and is persistent no matter what view is shown beneath it. I can't use a navigation controller because it is not possible to change the height and I need the bar to be significantly larger than a navbar. However my bar is functioning in much the same way. There is a "Home" Button, a "Back" Button and several destination buttons.
I understand how to switch views from say the home screen. My confusion comes with the back button. In order to press back the app is going to need to know what view is currently being displayed so that it can be removed from view and a new subview can be added. Ideally I would use the UINavigationController so that I can push and pop views which is really what I want to do here, however that is not possible because of the visual problem.
Does anybody know of a method that returns the current displayed view so I could do something like the following
[currentview.view removeFromSuperView];
[self.view insertSubview:experienceViewController.view atIndex:0]
You can use UINavigationController with the nav bar hidden. Put the nav controller inside a view that does have your jumbo toolbar and you'll have access to the push/pop behavior you're looking for.