// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // #import "OWSNavigationController.h" #import NS_ASSUME_NONNULL_BEGIN @interface UINavigationController (OWSNavigationController) @end #pragma mark - // Expose that UINavigationController already secretly implements UIGestureRecognizerDelegate // so we can call [super navigationBar:shouldPopItem] in our own implementation to take advantage // of the important side effects of that method. @interface OWSNavigationController () @end #pragma mark - @implementation OWSNavigationController - (instancetype)initWithRootViewController:(UIViewController *)rootViewController { self = [self initWithNavigationBarClass:[OWSNavigationBar class] toolbarClass:nil]; if (!self) { return self; } [self pushViewController:rootViewController animated:NO]; if (![self.navigationBar isKindOfClass:[OWSNavigationBar class]]) { OWSFail(@"%@ navigationBar was unexpected class: %@", self.logTag, self.navigationBar); return self; } OWSNavigationBar *navbar = (OWSNavigationBar *)self.navigationBar; navbar.navBarLayoutDelegate = self; [self updateLayoutForNavbar:navbar]; return self; } - (void)viewDidLoad { [super viewDidLoad]; self.interactivePopGestureRecognizer.delegate = self; } #pragma mark - UINavigationBarDelegate // All OWSNavigationController serve as the UINavigationBarDelegate for their navbar. // We override shouldPopItem: in order to cancel some back button presses - for example, // if a view has unsaved changes. - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item { OWSAssert(self.interactivePopGestureRecognizer.delegate == self); UIViewController *topViewController = self.topViewController; // wasBackButtonClicked is YES if the back button was pressed but not // if a back gesture was performed or if the view is popped programmatically. BOOL wasBackButtonClicked = topViewController.navigationItem == item; BOOL result = YES; if (wasBackButtonClicked) { if ([topViewController conformsToProtocol:@protocol(OWSNavigationView)]) { id navigationView = (id)topViewController; result = ![navigationView shouldCancelNavigationBack]; } } // If we're not going to cancel the pop/back, we need to call the super // implementation since it has important side effects. if (result) { // NOTE: result might end up NO if the super implementation cancels the // the pop/back. [super navigationBar:navigationBar shouldPopItem:item]; result = YES; } return result; } #pragma mark - UIGestureRecognizerDelegate // We serve as the UIGestureRecognizerDelegate of the interactivePopGestureRecognizer // in order to cancel some "back" gestures - for example, // if a view has unsaved changes. - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { OWSAssert(gestureRecognizer == self.interactivePopGestureRecognizer); UIViewController *topViewController = self.topViewController; if ([topViewController conformsToProtocol:@protocol(OWSNavigationView)]) { id navigationView = (id)topViewController; return ![navigationView shouldCancelNavigationBack]; } else { UIViewController *rootViewController = self.viewControllers.firstObject; if (topViewController == rootViewController) { return NO; } else { return YES; } } } #pragma mark - NavBarLayoutDelegate - (void)navBarCallLayoutDidChangeWithNavbar:(OWSNavigationBar *)navbar { [self updateLayoutForNavbar:navbar]; } - (void)updateLayoutForNavbar:(OWSNavigationBar *)navbar { DDLogDebug(@"%@ in %s", self.logTag, __PRETTY_FUNCTION__); [UIView setAnimationsEnabled:NO]; if (@available(iOS 11.0, *)) { if (OWSWindowManager.sharedManager.hasCall) { if (UIDevice.currentDevice.isIPhoneX) { // iPhoneX computes status bar height differently. // IOS_DEVICE_CONSTANT self.additionalSafeAreaInsets = UIEdgeInsetsMake(navbar.navbarWithoutStatusHeight + 20, 0, 0, 0); } else { self.additionalSafeAreaInsets = UIEdgeInsetsMake(navbar.navbarWithoutStatusHeight + CurrentAppContext().statusBarHeight, 0, 0, 0); } } else { self.additionalSafeAreaInsets = UIEdgeInsetsZero; } // in iOS11 we have to ensure the navbar frame *in* layoutSubviews. [navbar layoutSubviews]; } else { // Pre iOS11 we size the navbar, and position it vertically once. [navbar sizeToFit]; if (OWSWindowManager.sharedManager.hasCall) { CGRect oldFrame = navbar.frame; CGRect newFrame = oldFrame; newFrame.size.height = navbar.callBannerHeight; navbar.frame = newFrame; } else { CGRect oldFrame = navbar.frame; CGRect newFrame = CGRectMake(oldFrame.origin.x, navbar.statusBarHeight, oldFrame.size.width, oldFrame.size.height); navbar.frame = newFrame; } // Since the navbar's frame was updated, we need to be sure our child VC's // container view is updated. [self.view setNeedsLayout]; [self.view layoutSubviews]; } [UIView setAnimationsEnabled:YES]; } @end NS_ASSUME_NONNULL_END