I have a subclass of UINavigationController that has maximum of 4 ViewControllers in the stack. Lets call them firstVC ... fourthVC. My NavController can perform custom transitions between VCs and ios7/8 back gesture is supposed to be disabled and enabled depending on which VC is currently at the top of the stack. I've set my root VC (firstVC) as a NavController's delegate and trying to enable/disable back gesture in the delegate's method
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([viewController respondsToSelector:#selector(needsBackGestureEnabled)]) {
[self.navigationController.interactivePopGestureRecognizer setEnabled:YES];
NSLog(#"Back gesture enabled");
self.navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
} else {
if ([navigationController.interactivePopGestureRecognizer isEnabled]) {
[self.navigationController.interactivePopGestureRecognizer setEnabled:NO];
NSLog(#"Back gesture disabled");
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
}
}
It works like a charm except one glitch. I feel like a short scheme might explain situation better:
FirstVC -[CustomTran]-> SecondVC -[push]-> ThirdVC -[push]-> FourthVC
FourthVC is the only one that have -needsBackGestureEnabled selector, but after transition from second to third back gesture gets enabled by itself. Even though the back button is susbtituted with the CustomBarButtonItem. I feel like performing default -pushViewController animation makes back gesture enabled somehow. I tried to ecplicitly disable it in my NavController subclass in -pushViewController but it didn't change a thing. Any idea why this is happening and how to fix this?
Related
this is my question. I have three View Controllers (VC1, VC2 and VC3). Every View Controller inherited from UINavigationControllerDelegate and I delegate my navigation controller into the viewWillAppear method in this way:
self.navigationController.delegate = self;
And the only UINavigationControllerDelegate method that I use is for transition:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
if (operation == UINavigationControllerOperationPush || operation == UINavigationControllerOperationPop) {
myTransitionController *transitionController = [[myTransitionController alloc] init];
return transitionController;
}else{
return nil;
}
}
My problem is this: when I walk from VC1 to VC2 and into this controller trigger a NSNotification, I execute popViewControllerAnimated to go back the previous View Controller (VC1). This is the code:
- (void) backToRoot: (id) sender{
[self.navigationController popViewControllerAnimated:YES];
}
and the UINavigationBar show correctly its appearance (UINavigationBar without back button). Then, when I walk from VC1 to VC2 until VC3, and programatically go back until VC1 with popToRootViewController, the transition of the views (VC3-VC2-VC1) it works perfectly, but the UINavigationBar appearance not update and it shows with back button in the VC1 (which is the root View Controller). The method in the VC3 that I implemented is this:
- (void) goToRoot{
[self.navigationController popToRootViewControllerAnimated:YES];
}
I have tried with a double call of popViewControllerAnimated, I added in viewWillAppear and viewDidAppear method into VC2 and It not works neither. Anyone have idea what happens?
According to the documentation the func Pops all the view controllers on the stack except the root view controller and updates the display but I can't figure out the problem. You can try with unwind segue, is really simple.
What are Unwind segues for and how do you use them?
I have a very weird problem, in a typical animated transition code like below, after do the transition a few time then the view becomes irresponsive. I dug a little bit and found that the navigationController:animationControllerForOperation:fromViewController:toViewController: method is not called, even the pushViewController:animated: method is called. Any ideas?
This VC transits to another VC, then that VC can transit back with an interactive animated transition. The same problem happens in the other view as well, i.e., the transition is initiated but navigationController:animationControllerForOperation:fromViewController:toViewController: is not called. This only happens after I played with the app for like half a minute.
.h file
#interface ViewController : UIViewController <UINavigationControllerDelegate>
#end
.m file
#implementation ViewController
- (void)viewDidAppear:(BOOL)animated
{
[self.navigationController setDelegate:self];
}
- (void) transit_to_next_view
{
//...
UIViewController* newvc;
[self.navigationController pushViewController:newvc animated:YES];
}
// animated transition
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
// return animator
}
#end
it turns out that I mistakenly added an pan recognizer to the navigationController in the toViewController's viewDidLoad function. Even if the transition is canceled, the pan recognizer is still alive and will route all pan gestures to the canceled toViewController instead of the visible fromViewController. So the pan recognizer in the fromViewController is not fired and the transition never happen again. Moving the pan recognizer adding from the toViewController's viewDidLoad function to its viewDidAppear function fixed the problem.
Scenario
I have an app with a navigation controller. When the navigation controller pushes another controller onto the stack, in the upper left corner of the screen it shows the back button "<(title of the last view controller)".
What I need
I need something like (pseudo code)...
-(void)detectedBackButtonWasPushed {
NSLog(#"Back Button Pressed");
//Do what I need done
}
Question
Because this button is created by the navigation controller and I did not create this button in storyboards, how do I get the back button 'hooked up' to a method like this?
examples of what Ive tried for Oleg
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"notification"];
if (viewController == vc) {
NSLog(#"BACK BUTTON PRESSED");
}
}
Is this how I'm supposed to do it? Cause this doesn't work.
Use viewWillDisappear to detect this.
-(void) viewWillDisappear:(BOOL)animated
{
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound)
{
[self backButtonPressed];
[self.navigationController popViewControllerAnimated:NO];
}
[super viewWillDisappear:animated];
}
-(void)backButtonPressed
{
NSLog(#"YEA");
}
Previously, I have solved this by setting the navigationBar leftItem to be a back button with a custom selector that dismisses the view along with whatever else it needed to do.
I might also suggest looking at the back button item and adding a target:self that is called on touch.
I want to mark that my UINavigationController is animating (push/pop) or not.
I have a BOOL variable (_isAnimating), and the the code below seem work:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
_isAnimating = YES;
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
_isAnimating = NO;
}
But it work incorrectly in iOS7 with swipe gesture. Assume my navigation is: root-> view A -> view B . I'm currently on B.
In begin of swipe (go back from B to A), the funcion "navigationController:willShowViewController:animated:(BOOL)animated" is called, then _isAnimating = YES.
The normal case is the swipe is finished (go back to A), the function "navigationController:didShowViewController:animated:(BOOL)animated" is called, then _isAnimating = NO. This case is OK, but:
If the user may just swipe a half (half transition to A), then don't want to swipe to the previous view (view A), he go to the current view again (stay B again). Then the function "navigationController:didShowViewController:animated:(BOOL)animated" is not called, my variable has incorrect value (_isAnimating=YES).
I have no chance to update my variable in this abnormal case. Is there any way to update the state of navigation? Thank you!
The clue to solve you problem can be found in the interactivePopGestureRecognizer property of UINavigationController. This is the recognizer which responds for popping controllers with a swipe gesture. You can notice that the state of the recognizer is changed to UIGestureRecognizerStateEnded when the user rises finger up. So, additionally to Navigation Controller delegate you should add target to the Pop Recognizer:
UIGestureRecognizer *popRecognizer = self.navigationController.interactivePopGestureRecognizer;
[popRecognizer addTarget:self
action:#selector(navigationControllerPopGestureRecognizerAction:)];
This action will be called each time the Pop Recognizer changed, including the end of a gesture.
- (void)navigationControllerPopGestureRecognizerAction:(UIGestureRecognizer *)sender
{
switch (sender.state)
{
case UIGestureRecognizerStateEnded:
// Next cases are added for relaibility
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
_isAnimating = NO;
break;
default:
break;
}
}
P.S. Do not forget that the interactivePopGestureRecognizer property is available since iOS 7!
I have implemented a custom version of a search form that behaves a lot like a UISearchBar with a scope bar (but is actually pieced together programatically for UI reasons). The screen loads with a TextField, you tap in the TextField and the navigation bar animates up off the screen, the text field moves up and a segmented control appears for filtering results.
Anyway, that all works, but when I tap on one of the search results my code pushes a new ViewController. The problem is that new controller gets pushed without a navigation bar (because I used [[self navigationController] setNavigationBarHidden:YES animated:YES] when switching to the search state).
I can show the navigation bar as the new ViewController gets pushed, or even animate it in as the transition to the new ViewController appears - but all those solutions look clunky. I want it to work as if you were using a UISearchBar (actually more like the email app) in that the restored navigation bar appears to just slide in from the right as if it's part of the child view controller.
I'm hoping there'll be a simple fix... thanks
For anyone that comes to this, the solution is to make your controller the delegate of the UINavigationController, then show or hide the nav bar in your delegate methods.
Your controller needs to implement the protocol:
#interface MYSearchController() <UINavigationControllerDelegate>
Then in -(void)viewDidLoad assign your controller as the delegate:
[self navigationController].delegate = self;
Finally, implement a method like this:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if(viewController == self)
{
if(_searchState && ![self navigationController].navigationBarHidden)
{
[[self navigationController] setNavigationBarHidden:YES animated:YES];
}
}
else
{
if([self navigationController].navigationBarHidden)
{
[[self navigationController] setNavigationBarHidden:NO animated:YES];
}
}
}