I have a very weird problem with the UINavigationController on iOS 8, maybe someone encountered this already and can shed some light. I have 2 views: let's say view A and view B
I am using it like this:
view A [self.navigationController pushViewController:vc animated:YES];
push to a new view B [self.navigationController pushViewController:vc animated:YES];
push to a new view B [self.navigationController pushViewController:vc animated:YES];
push to a new view B [self.navigationController pushViewController:vc animated:YES];
push to a new view B [self.navigationController pushViewController:vc animated:YES];
push to a new view B [self.navigationController pushViewController:vc animated:YES];
return to view A [self.navigationController popToRootViewControllerAnimated:YES];
The problem is that if I play with this for 2 min and go through this like push-push-push-push-pop and again... at some time it stops animating, for either push and pop.
I checked the 1) view controllers they get deallocated on the pop to root, 2) I don't receive any memory warnings, 3) the navigation controller is the rootviewcontroller of the window so why this problem?
I can't find any explanation maybe someone has encountered this already. Also I am mentioning I am not using custom animations, just the plain native push and pop of a normal UIViewController, not even subclassing that so everything is plain native.
#kokos8998 try using
#interface AnimatorPushGalleryToGallery : NSObject <UIViewControllerAnimatedTransitioning>
and then in view A just add this and will control everything from view B either A->B or B->A (or in case you need something more custom add the same delegate in B as well)
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.navigationController.delegate = self;
}
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
if(operation == UINavigationControllerOperationPush)
return [AnimatorPushGalleryToGallery new];
else if(operation == UINavigationControllerOperationPop)
return [AnimatorPopGalleryToGallery new];
return nil;
}
Related
I am using doing something basic like this to push to a view.
UIStoryboard *storyboard = self.navigationController.storyboard;
MenuViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"menuViewController"];
[self.navigationController pushViewController:viewController animated:YES];
Some places I can simply use:
[self.navigationController popViewControllerAnimated:YES];
But in a few places I want to pop to a specific view controller. What I have tried is:
UIStoryboard *storyboard = self.navigationController.storyboard;
RecordMenuViewController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"recordMenuViewController"];
[self.navigationController popToViewController:viewController animated:YES];
But it just goes to a black screen like the view isn't in the stack or something. What am I missing here?
If you want to pop to a specific view controller using the navigation stack, do the following:
NSArray* vcs = self.navigationController.viewControllers;
UIViewController* target = [vcs objectAtIndex:([vcs count] - 1) - numVCsToGoBack];
[self.navigationController popToViewController:target];
If you have to pop to specific view controller in the stack use following code:
(here consider you have to pop to MyViewController Class)
for (UIViewController *controller in self.navigationController.viewControllers)
{
if ([controller isKindOfClass:[MyViewController class]])
{
[navigationController popToViewController:controller animated:YES];
break;
}
}
view controller A B C D
A -> B -> C-> D
popViewController only form D to C
popViewTopController only form D to A;
Any way can I pop to any view as I wish if I have 10 view controllers?
Thanks for everyone. will the popViewController pop to a new view Controller ?
Option 1: Select by class
To tell the navigationController to pop to a specific class, you can do as follows:
NSArray *allViewControllers = [self.navigationController viewControllers];
for (UIViewController *aViewController in allViewControllers)
{
if ([aViewController isKindOfClass:[B class]])
{
[self.navigationController popToViewController:aViewController animated:YES];
}
}
Take into account that you should only use this, if you are not pushing instances of the same class several times.
Option 2: Select by level
If you want to pop to a specific level, you can just select it by index at self.navigationController.viewControllers since it correspond to the levels. The first pushed UIViewController will be at index 0, the second at index 1 and so on:
NSArray *allViewControllers = [self.navigationController viewControllers];
UIViewController *aViewController = [allViewControllers objectAtIndex:level];
[self.navigationController popToViewController:aViewController animated:YES];
if you want to pop any view you want to change objectAtIndex:1,2,3..etc
it will pop to first,second etc... from the any views.
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
This is the method you are looking for (reference)
In Obj-c
- (NSArray *)popToViewController:(UIViewController *)viewController
animated:(BOOL)animated
You should pass in the view controller that you want pop to
Use the following UINavigationController method to go to any view controller on the current stack.
- (NSArray *)popToViewController:(UIViewController *)viewController
animated:(BOOL)animated
For example, if you are in a UIViewController and you want to pop back to the third one in the stack:
UINavigationController * nc = self.navigationController;
UIViewController * popToVC = [nc.viewControllers objectAtIndex:2];
[nc popToViewController:popToVC animated:YES];
SecondViewController *sec = [SecondViewController alloc] init];
[self.navigationController popViewController:Sec animated:YES];
My storyboard looks like the following:
What I'm trying to achieve is when "Click Me" is pressed on the home page, to segue to "One" , check some logic on this controller, if successful, automatically segue to "Two".
Then when the "Back" button is pressed on "Two" it would take the user back home, essentially popping "One" off the stack.
Below is what my "One" controller looks like:
#implementation OneViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// example logic, in this case just force them to view two
if(1 == 1)
{
[self.navigationController popViewControllerAnimated:NO];
TwoViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"two"];
[self.navigationController pushViewController:vc animated:YES];
}
}
#end
I'm getting strange behavior and receiving the following error:
Finishing up a navigation transition in an unexpected state.
Navigation Bar subview tree might get corrupted.
I can't seem to figure out what I'm doing wrong. I've included the full dead simple source: http://andrewherrick.com/spike/pushpop.zip
EDIT:
I've tried moving the logic to ViewDidAppear and it simply kicks me back to the "Home" view automatically which isn't what I want.
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)viewDidAppear:(BOOL)animated {
// example logic, in this case just force them to view two
if(1 == 1)
{
[self.navigationController popViewControllerAnimated:NO];
TwoViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"two"];
[self.navigationController pushViewController:vc animated:NO];
}
}
Putting your second push logic in viewDidAppear: rather than viewDidLoad solved the problem for me.
You should also consider the UX of your app. If a viewController is being used only for a few seconds to process some data and automatically segues into another viewController, it would be better practice to show a UIActivityIndicator or a small visual indicator.
EDIT:
Your automatic push should not pop itself before pushing another.
- (void)viewDidAppear:(BOOL)animated
{
if(1 == 1)
{
TwoViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"two"];
[self.navigationController pushViewController:vc animated:YES];
}
}
Then in your TwoViewController you need to programmatically call
[self.navigationController popToRootViewControllerAnimated:YES];
in order to pop back to your Main viewController.
I have now been stuck on this problem for more then 2 weeks! In my project, I have 1 single ViewController(slide) I want to enable both landscape and portrait in. The rest of all controllers/views(slides) I want to enable portrait-mode only.
The tricky part is, the "ViewController" I am referring to is connected to both NavigationControllers and TabBarControllers. See the scheme below where the ViewController I want to enable both landscape/portrait is named: ReferredViewController.
TabBarController ----> NavigationController ----> FristViewController --(push event)--> ReferredViewController
So far I have tried to make a CATEGORY for both NavigationControllers and TabBarControllers. But since my NavigationControllers and TabBarControllers are placed at the very start of the project this will set the rules for the whole project. My ReferredViewController is placed at the end or in the middle of the projects "storyboard". I have tried to set the rules by code aswell for the single ReferredViewController without any success.
My best shot is to change the event between FirstViewController and ReferredViewController from "push" to "modal". ReferredViewController can then rotate both portrait/landscape and the rest of the project is locked in portrait. BUT, as you may know all navigations (NavigationBar) will be lost and the user will become stuck at that single slide.
So I am trying to enable the NavigationBar with the following code example in the ReferredViewController.m file:
ShowTaskViewController *detailViewController = [[ShowTaskViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
navController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.navigationController presentModalViewController:navController animated:YES completion:nil];
[navController release];
[detailViewController release];
But ofc nothing happens and I am back to square one again :O. FML!
In this line:
[self.navigationController presentModalViewController:navController
animated:YES
completion:nil];
you are conflating two UIViewController instance methods:
- (void)presentViewController:(UIViewController *)viewControllerToPresent
animated:(BOOL)flag
completion:(void (^)(void))completion
- (void)presentModalViewController:(UIViewController *)modalViewController
animated:(BOOL)animated
The first of these is now the standard, the second method was deprecated in ios6.
Also the presenting view controller should be self (the ReferredViewController), not self's navigationController.
Your presented view controller can dismiss itself thus
[[self presentingViewController] dismissViewControllerAnimated:YES
completion:(void (^)(void))completion];
But take a look at fibnochi's answer, it may be a better way for you to achieve your result.
you have to over ride UITaBarController because it is you base view controller. I have done this for my navigation controller. Tell me if this helped.
#interface UINavigationController (Autorotation)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation;
- (BOOL) shouldAutorotate;
- (NSUInteger) supportedInterfaceOrientations;
#end
#implementation UINavigationController (Autorotation)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{
if ([self.visibleViewController isKindOfClass:[MWPhotoBrowser class]] || [self.visibleViewController isKindOfClass:[ZoomPictureViewController class]]) {
return YES;
}
return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
}
-(BOOL) shouldAutorotate{
return YES;
}
-(NSUInteger) supportedInterfaceOrientations{
if ([self.visibleViewController isKindOfClass:[MWPhotoBrowser class]] || [self.visibleViewController isKindOfClass:[ZoomPictureViewController class]]) {
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationMaskPortrait;
}
I have 3 UIViewControllers in my UINavigationController.
At some points I want to go to rootViewController and from there navigate to a new UIViewController, and it doesn't aeem to work.
Any suggestions?
- (IBAction)goToRootAndNavigateToViewController
{
[self.navigationController popToRootViewControllerAnimated:YES];
MyViewController *mvc = [[MyViewController alloc] init];
[self.navigationController pushViewController:mvc animated:YES];
[mvc release];
//This takes me to the rootViewController but it doesn't navigate to MyViewController
}
Trying to use performSelector:WithDelay:
- (void)goToRootAndNavigateToViewController
{
[self.navigationController popToRootViewControllerAnimated:YES];
[self performSelector:#selector(doSomething) withObject:nil afterDelay:10];
}
- (void)doSomething
{
MyViewController *mvc = [[MyViewController alloc] init];
[self.navigationController pushViewController:mvc animated:YES];
[mvc release];
}
I believe the popToRootViewController takes up the full NSRunLoop.
You would need to push your next view controller with a separate function using something like performSelector:withObject:afterDelay.
or you could always just do a [self.navigationController setViewControllers:] call to set them manually
You need to push your mvc controller when the animation is complete. Try calling it once the first animation is done (e.g. in - (void)viewDidAppear:(BOOL)animated)
I don't know why you are facing this problem, but one solution you could try is to push the new view controller in your root view controller's -viewDidAppear: method.
I guess it has to do with your current viewcontroller using - (IBAction)goToRootAndNavigateToViewController, is somehow losing its control once being popped. Thus, making consecutive statements to be not working.
If I were you, I would make sure pushing MyViewController instance is done always at rootViewController of your choice, not from the current viewcontroller which is going to be popped from UINavigationController and possibly to be released and deallocated.
Probably, you may want to add delegate method implementation for, such as - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated in your rootViewController
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Check the right condition for pushing MyViewController...
// if it's YES...
MyViewController *mvc = [[MyViewController alloc] init];
[self.navigationController pushViewController:mvc animated:YES];
[mvc release];
}
In this implementation, you may push MyViewController instance. One thing you have to do beforehand is using some kind of conditional flag, which will make sure the situation is correct for popToRootViewController then push MyViewController