In my library I have a loading view which pops to a input view. When the user is done with the input view it should go back to the loading view to do some magic again and when done it should show up a third view.
Now, from a usability view I don't want to "slide back" to the loading view, neither do I want to allocate a new loading view when I already have one in memory.
Is there some way I can popToRootViewController while sliding the view forwards?
(Yes, I remove the back button in the loading view)..
Alright here goes - perhaps try using something like this
// This goes in whatever view controller you want to pop with
- (void)popToRootWithForwardAnimation
{
NSMutableArray * viewControllers = [[[self.navigationController viewControllers] mutableCopy] autorelease]
UIViewController * rootViewController = [viewControllers objectAtIndex:0]
[viewControllers removeObjectAtIndex:0]; // try using with and without this line?
[viewControllers addObject:rootViewController];
[self.navigationController setViewControllers:viewControllers animated:YES];
}
// This goes in the root view controller
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated]
NSMutableArray * viewControllers = [[[self.navigationController viewControllers] mutableCopy] autorelease]
if ([viewControllers count] > 1)
{
[viewControllers removeAllObjects];
[viewControllers addObject:self];
[self.navigationController setViewControllers:viewControllers animated:NO];
}
…
…
}
Hmm, I'd say a better approach would be to flip the view in a modal fashion rather than push/pop in a navigation stack. So you would want to do in the input view where you are pushing next view controller:
MagicViewController *magicVC = [[MagicViewController alloc] init];
magicVC.setModalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:magicVC animated:true];
Then when the Magic View controller is done doing its magic, just do at that point (where you otherwise pop):
[self dismissModalViewControllerAnimated:true];
This would be much more cooler than doing simple navigation.
See modal view controllers guide.
Related
Possibly simple request here but I can't find the solution and it is bugging me for days.
I'm building simple options page where users could jump to desired page and I'm using UINavigationController instance to manage hierarchy. My storyboard looks like this:
Viewcontrollers are connected with push segues fired on next button, while I use [self.navigationController popViewControllerAnimated:YES] for previous button. If I connect, for instance, button labeled 2 on 5VC with 2VC through push segue, I get to the second page, but if I want to use previous button I will land to options page or 5VC which is something I don't want. Instead, I would like to be able to use previous button to go to first page, while on second page.
The way I see it, if I am on third page (3VC) and I call options page (5VC) and select button 3, system should stack 1VC-2VC and present 3VC, so I would be able to go to 2VC through [self.navigationController popViewControllerAnimated:YES] request.
I think the solution is somehow connected with setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated, but I don't know the syntax how to make things work.
You have 3 cases
Back to one of ancestors in the middle with push
case 5VC=>2VC, 5VC=>3VC:
NSArray *vcs = self.navigationController.viewControllers;
for(NSInteger i = vcs.count - 2; i > 0; i--) {
// find the target and its parent view controller
// i.e. class of 2VC is ViewController2
if([vcs[i] isKindOfClass:[ViewController2 class]]) {
UIViewController *target = vcs[i];
UIViewController *parent = vcs[i - 1];
// pop to its parent view controller with NO animation
[self.navigationController popToViewController:parent animated:NO];
// push the target from its parent
[self.navigationController pushViewController:target animated:YES];
return;
}
}
Back to the root view controller with push
case 5VC=>1VC:
UIViewController *root = self.navigationController.viewControllers.firstObject;
// reset view controllers stack with self as root.
[self.navigationController setViewControllers:#[self] animated:NO];
// push target from self
[self.navigationController pushViewController:root animated:YES];
// reset navigation stack with target as root.
[self.navigationController setViewControllers:#[root] animated:NO];
Push new VC from one of ancestors
case 5VC=>4VC
NSArray *vcs = self.navigationController.viewControllers;
for(NSInteger i = vcs.count - 1; i >= 0; i--) {
// find the parent view controller
if([vcs[i] isKindOfClass:[ViewController3 class]]) {
UIViewController *parent = vcs[i];
// pop to the parent with NO animation
[self.navigationController popToViewController:parent animated:NO];
// perform segue from the parent
[parent performSegueWithIdentifier:#"push4VC" sender:self];
return;
}
}
On the particular your case(5VC=>4VC), you know 3VC is the self's parent, you can get the parent directly:
NSArray *vcs = self.navigationController.viewControllers;
UIViewController *parent = vcs[vcs.count - 2]; // [vcs.count-1] is self.
[self.navigationController popToViewController:parent animated:NO];
[parent performSegueWithIdentifier:#"push4VC" sender:self];
I am properly pushing viewController B from A using navigationController. However, I would like to do it once uiwebview from viewController B finishes its load and not immediately. I tried firstly init B and push A when load ends but with no success, controller is not viewed. How can it be done? thank you.
from controllerA,
self.controllerB = [[BViewController alloc] initWithNibName:#"BViewController" bundle:nil anUser:self.idUser aLang:self.lang];
//[[self navigationController] pushViewController:controllerB animated:NO]; working if pushed directly here
[self.controllerB view];
then, controllerB is initialized, viewDidLoad triggered and when webviewDidFinishLoad, B must be pushed now or viewed at front.
- (void)webViewDidFinishLoad:(UIWebView*)theWebView
{
AViewController *theInstance = [[AViewController alloc] init];
[theInstance pushBcontroller]; }
on AViewController,
-(void)pushBcontroller{
[[self navigationController] pushViewController:self.controllerB animated:NO];
}
not working...
The line AViewController *theInstance = [[AViewController alloc] init]; creates a new instance of a AViewController. Since it's new it isn't part of the view controller hierarchy and is therefore not connected to the navigation controller.
Give your BViewController a reference to the previous controller and use that instead of creating a different one. Or, perhaps better, send a notification when loading is done that the original AViewController uses to know when to change the display.
Today I made some tests and I am curious of the results. I made an app (ARC) which have UINavigationController and two UIViewControllers. In the first view there is a button and when that button is pressed the second view is loaded. In the second view when shake gesture is detected the first view is loaded and so on. What I notice in instruments is that the heap grows every time when a view is loaded. Here is some code
AppDelegate.m
self.navigationController = [[UINavigationController alloc]init];
self.window setRootViewController:self.navigationController];
FirstViewController *firstview = [FirstViewController alloc]init];
[self.navigationController pushViewController:FirstViewController animated:YES];
FirstViewController.m
-(IBAction)loadSecondView
{
SecondViewController *secondview = [SecondViewController alloc]init];
[self.navigationController pushViewController:secondview animated:YES];
}
SecondViewController.m
-(IBAction)loadFirstView
{
FirstViewController *firstview = [FirstViewController alloc]init];
[self.navigationController pushViewController:first view animated:YES];
}
I can't figure out why that happens. How to avoid heap of growing in that case ?
Actually every time you are creating a new view controller object.. That should not be done.
So every time you allocate a new object and pushed that view, it will be added to the navigation stack and so, the memory grows.
Instead, when you are in first view and tapped the button, you can pop the current view controller and inform the AppDelegate class to show the second view.
Similarly while in second view, when you want to show the first view, pop the current view and inform the AppDelegate class to push the first view controller.
SecondViewController *secondview = [[[SecondViewController alloc]init] autorelease];
FirstViewController *firstview = [[[FirstViewController alloc]init] autorelease];
you should autorelease viewcontrollers (for not ARC)
if second controller opens first, you should do popViewController. If you wont return back, heap will grow
I have MainMenuViewController with button which action is
- (IBAction) goToFirstView {
FirstViewController *fvc = [[FirstViewController alloc] init];
[self.view addSubview:fvc.view];
[fvc release];
}
FirstViewController have UIButton with action
- (IBAction) rightArrow {
SecondViewController *svc = [[SecondViewController alloc] init];
[self.view addSubview:svc.view];
[svc release];
}
But when I press "rightArrow" button app crashes with "EXC_BAD_ACCESS". Can't found my problem. Help me please.
[svc release];
The problem is here. When releasing the view controller, the view's events will target a freed object, and make your program crash (probably in viewDidLoad or viewDidAppear if it's instant but it doesn't matter). Note that a view does not (normally, AFAIK) retain it's view controller, if that might have been your assumption...
When you say [self.view addSubview:svc.view] you're adding SecondViewController's view to FirstViewController's view. Similar with MainViewController and FirstViewController. What you'll end up with is a view hierarchy that looks like this:
main view
first view
second view
I doubt that's really what you want. Instead, use a navigation controller with your MainViewController as the nav controller's root controller, and then use -pushViewController:animated: to push the controllers (not the views!) onto the navigation stack.
How would I make this code animate in the SplashView NIB instead of just making it appear (e.g. the UIModalTransitionStyleFlipHorizontal style)? I am using a UITabBarController type project.
- (IBAction)showSplash:(id)sender {
// Hide toolbar
self.tabBarController.tabBar.hidden = YES;
// Splash
[[NSBundle mainBundle] loadNibNamed: #"SplashView" owner: self options: nil];
[self.view addSubview: splashView];
[window makeKeyAndVisible];
}
Bit hard to tell your context with this small bit of code. Basically, if you want to push a viewController modally, in your -(IBAction)showSplash method (you don't need to send the sender if you're not using it, BTW), I would use some code similar to this:
SplashViewController *svc = [[SplashViewController alloc] init]; (assuming nib is same name)
self.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:svc animated:YES];
[svc release];
Then in your SplashViewController you would have an IBAction that calls:
[self dismissModalViewController animated:YES];
You don't actually have to hide the tabBar when you are presenting a modalViewController. It won't be there. The idea of a modalViewController is that it blocks all user interaction with the app except for the modal view, until it is dealt with.
Hope this helps.