Long story short, I have a view controller where the user can tap on self.view (anywhere but the nav bar) and it will enter a full screen mode where the controls at the bottom fade out and the navigation and status bar fade out. Similar to iBooks.
I could simply fade the alpha of the navigation bar, but as to allow the user to tap in the newly gained area (where the navigation bar was now that it's faded out) and have it do something, I have to do more than change the alpha, as the nav bar is still technically there taking up area.
So I hide the navigation bar with [self.navigationController setNavigationBarHidden:YES animated:NO];. I have to do this after the animation block finishes, else it will be in the animation block and animate as part of the block. So I use a dispatch_after to make it finish after the animation completes (0.35 second delay).
However, this causes the issue where if the user taps any time during that 0.35 second time period where it's animating out and things are waiting to be finished, it causes glitchy behaviour where another block starts even though it's still waiting 0.35 seconds for the other one to finish. It causes some glitchy behaviour and causes the navigation bar to stay hidden. Gross.
Video of it happening: http://cl.ly/2i3H0k0Q1T0V
Here's my code to demonstrate what I'm doing:
- (void)hideControls:(BOOL)hidden {
self.navigationController.view.backgroundColor = self.view.backgroundColor;
int statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
[UIView animateWithDuration:0.35 animations:^{
[[UIApplication sharedApplication] setStatusBarHidden:hidden withAnimation:UIStatusBarAnimationFade];
if (hidden) {
self.navigationController.navigationBar.alpha = 0.0;
self.instructionsLabel.alpha = 0.0;
self.backFiftyWordsButton.alpha = 0.0;
self.forwardFiftyWordsButton.alpha = 0.0;
self.WPMLabel.alpha = 0.0;
self.timeRemainingLabel.alpha = 0.0;
}
else {
self.navigationController.navigationBar.alpha = 1.0;
self.instructionsLabel.alpha = 1.0;
self.backFiftyWordsButton.alpha = 1.0;
self.forwardFiftyWordsButton.alpha = 1.0;
self.WPMLabel.alpha = 1.0;
self.timeRemainingLabel.alpha = 1.0;
}
[self.view layoutIfNeeded];
}];
// Perform an "actual" hide (more than just alpha changes) after the animation finishes in order to regain that touch area
if (hidden) {
double delayInSeconds = 0.35;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
[self.navigationController setNavigationBarHidden:YES animated:NO];
self.textToReadLabelPositionFromTopConstraint.constant = TEXT_LABEL_DISTANCE + self.navigationController.navigationBar.frame.size.height + statusBarHeight;
});
}
else {
[self.navigationController setNavigationBarHidden:NO animated:NO];
self.textToReadLabelPositionFromTopConstraint.constant = TEXT_LABEL_DISTANCE;
}
}
The only other thing I'm doing is changing the constant on my Auto Layout constraint to account for the navigation bar and status bar dependent on whether or not they're there.
I'm not sure how to factor in the fact that double tapping can really glitch out the full screen process. How could I make it so if they tap during the animation process it will just cancel the animation and do their desired action as intended? Could I be doing this process better?
I think you can do this without adjusting any frames or constraints using these principles:
1) Make the window's background color the same as your view's
2) Add a tap gesture recognizer to the window. This allows tapping anywhere on the screen (except the status bar when its alpha isn't 0) whether the navigation bar is visible or not. This allows you to not have to set the navigation bar to hidden which would cause your view to resize.
3) Use hitTest: in the tapper's action method to check if the user tapped the navigation bar, and don't fade out if the tap was there.
4) Use UIViewAnimationOptionBeginFromCurrentState and UIViewAnimationOptionAllowUserInteraction in the animation block so the fade-in or fade-out can be reversed smoothly with another touch.
5) Enclose all the bottom controls in a clear UIView so you can just fade out that UIView instead of all the individual controls.
Here is the code that worked:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.view.window.backgroundColor = self.view.backgroundColor;
UITapGestureRecognizer *tapper = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(fadeInFadeOut:)];
[self.view.window addGestureRecognizer:tapper];
}
-(void)fadeInFadeOut:(UITapGestureRecognizer *)sender {
static BOOL hide = YES;
id hitView = [self.navigationController.view hitTest:[sender locationInView:self.navigationController.view] withEvent:nil];
if (! [hitView isKindOfClass:[UINavigationBar class]] && hide == YES) {
hide = ! hide;
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationFade];
[UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionAllowUserInteraction animations:^{
self.navigationController.navigationBar.alpha = 0;
self.bottomView.alpha = 0;
} completion:nil];
}else if (hide == NO){
hide = ! hide;
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade];
[UIView animateWithDuration:.35 delay:0 options:UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionAllowUserInteraction animations:^{
self.navigationController.navigationBar.alpha = 1;
self.bottomView.alpha = 1;
} completion:nil];
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With