UINavigationController strategy (iPhone) - iphone

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.

Related

Navigation Controller design for multiple duplicate views

I'm having a hard time understanding what approach I should take. I have a TableView controller with a list of questions, if you click a row it pushes a new QuestionViewVontroller that displays the question, answers, and next button (or done button). When a user clicks next I want to load the next question (from the tableview list) but I still want the navigation to work (back button will take you to TableView).
I'm starting to think I should keep the same QuestionViewController and just load in the question data. Is this right? Or should I use a modal view?
You have a couple of options here.
Use the same QuestionViewController and just repopulate it, like you mentioned.
Push a new QuestionViewController and use custom back button that either pops to the root view controller or pops to a given view controller.
The code for both options in #2 is below, where "self" is the current view controller (your question controller).
[self.navigationController popToRootViewControllerAnimated:YES]; //this would pop to your UITableViewController, assuming it was the root
[self.navigationController popToViewController:yourTableViewControllerHere Animated:YES]; //this would work as long as you passed a reference to your UITableViewController to your question controllers
It's up to you how you choose to solve your issue. However, if you decide to use the same QuestionViewController, include a catchy animation when importing the new question so the user knows what is happening and to make your app that much cooler :)

Managing navigation views on the stack

Being new to iPhone development I am really struggling with this concept. I have built an App which is not quite running the way that it should. I have a TabBar app. The secondViewController is essentially a form which I use to collect information about daily exercise. I have embedded this secondViewController into a Navigation controller so that I can push to a datePicker view and return with the date. I also push to a pickerView for exercise type and return with data. At least this is the intended process. I use the prepareForSegue to push the picker views and return to the secondViewController carrying data between them with each segue.
What I find is happening is that instead of pushing to the picker views and returning to the secondViewController my app seems to progress in a linear fashion. To be more precise it seems to push to the date picker, then push to a new 'instance' of the secondViewController, then push to pickerView, and then to another new instance of secondViewController. This means that when my users eventually touch the save button, they have to use the back button in the navBar to get back to the original secondViewController.
[Have an image to add but can't post it as I have reputation under 10 :-( ]
I have read Apple's documentation on managing the stack and hierarchy, but I just seem to have confused myself more than anything else. I have also searched for answers and tutorials, but I am either missing something or there is no clear explanation of how this is supposed to work that I can find. Can anyone tell me how to push to a pickerView and then return, or at least to return programmatically to the original secondViewController when the save button is finally touched?
To go Back in hierarchy in UINavigation Controller you can use
[self.navigationController popViewControllerAnimated:YES];

Can someone explain UINavigationController setViewController and popToViewController

I'm familiar with the idea of creating a new viewcontroller and pushing it onto the stack. So far I have just created an instance of my view controller and pushed it. Now I am running into a problem that my 3 different view controllers are related ways of looking at the data. My RootViewController has 3 icons to start, and when you press on an icon, you push the first view controller. I currently do not set the view controllers in an array since I'm not sure what that buys me or how that works.
Scenario 1: Click on icon 1 (push vc1), click on a table in a popover in vc1, it'll push to vc3. Then you click on something in vc3, and it'll go to vc1.
Scenario 2: Click on icon 3 (push vc3), click on something and go to vc1.
So the problem I'm having is in scenario 1, it would seem to make the most sense to pop back to vc1 so they don't have a stack of vc1/vc3/vc1 and are looking at the data twice like that. But in scenario 2, since I started at vc3 instd of vc1, I should push vc1. But in both scenarios, the user is clicking on the same thing to go to vc1 from vc3, so how do I tell which viewController they came from in order to push or pop to vc1?
So I guess that's where I thought setViewControllers or popToViewController might come in handy, but I'm not sure how those work and if there's some simple example snippet someone can provide to get me started (assuming this approach is ok). Thanks!
It sounds to me, from your description, that you should be using a navigation controller that you push and pop onto and off. Btw, the navigation bar doesn't have to be visible.
It also sounds like you should sometimes pop the current vc before pushing the next vc. You will need to check the count of the viewControllers array - if the current count is greater than one then pop before pushing, otherwise just push as you are at the root view controller.
Or, you could use popToRootViewControllerAnimated:NO each time before pushing.
UINavigationController is what allows you to manage your views.
setViewController allows you to set which controller handles your view.
popToViewController takes the current view controller off the stack to the specified view.
IMO... The last 2 promote spaghetti logic and should be used sparingly.

Managing multiple viewcontrollers with views displayed in a sequential way - should I use NavigationController?

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.

Showing a one-time UIViewController via presentModalViewController upon launch

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.