Today one of our testers showed me that if he presses the back button of a navigation bar while swiping back creates a quite interesting behavior of the navigation bar:
if we are on the third view controller and perform this than go back to the top, than again go in one level and click the back button on most cases the navigation bar will not disappear if it should, or it maybe will not animate. Or not appear in deeper levels. Or deactivate the back button although not being in the top view controller.
Sometimes this messages is printed to the console:
Finishing up a navigation transition in an unexpected state. Navigation Bar subview tree might get corrupted.
It turns out that it must be in Apples classes, as I was able to reproduce it with plain classes. The code is on GitHub. You will have to run the app on the phone to perform gesture and button tap at once.
I also prepared a video.
How do I fix it?
To fix this I disable user interactions for the navigationsbar. To do so, I subclass UINavigationViewController and use Key-Value-Observing to detect the state of the gesture recognizer.
#import "NavigationViewController.h"
@interface NavigationViewController ()
@end
@implementation NavigationViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[self.interactivePopGestureRecognizer addObserver:self
forKeyPath:@"state"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:NULL];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqual:@"state"]) {
[self recognizer:object
changedState:[change[@"new"] integerValue]
oldState:[change[@"old"] integerValue]];
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
- (void)recognizer:(UIGestureRecognizer *)recognizer
changedState:(UIGestureRecognizerState)newState
oldState:(UIGestureRecognizerState)oldState
{
switch (newState) {
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
[self.navigationBar setUserInteractionEnabled:YES]; break;
case UIGestureRecognizerStateBegan:
[self.navigationBar setUserInteractionEnabled:NO]; break;
default:
break;
}
}
- (void)dealloc
{
[self.interactivePopGestureRecognizer removeObserver:self forKeyPath:@"state"];
}
@end
You will find the fixed code on GitHub as-well.
Suppose you have a navigation controller with view controllers A --> B --> C.
The problem occurs when you are on C, swipe back to B, and touch the Back button on B before lifting your swiping fingers.
To prevent this:
In B viewDidDisappear:
navigationItem.hidesBackButton = true
In B viewDidAppear:
navigationItem.hidesBackButton = false
This has the effect of preventing touches on B's Back button until the swipe is complete.