Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AHKNavigationController #5669

Closed
wants to merge 9 commits into from
6 changes: 6 additions & 0 deletions React/React.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; };
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; };
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; };
8AAF3CA91C74C27F0039FC59 /* AHKNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8AAF3CA81C74C27F0039FC59 /* AHKNavigationController.m */; };
E9B20B7B1B500126007A2DA7 /* RCTAccessibilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E9B20B7A1B500126007A2DA7 /* RCTAccessibilityManager.m */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -291,6 +292,8 @@
83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = "<group>"; };
83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = "<group>"; };
83F15A171B7CC46900F10295 /* UIView+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIView+Private.h"; sourceTree = "<group>"; };
8AAF3CA71C74C27F0039FC59 /* AHKNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AHKNavigationController.h; sourceTree = "<group>"; };
8AAF3CA81C74C27F0039FC59 /* AHKNavigationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AHKNavigationController.m; sourceTree = "<group>"; };
ACDD3FDA1BC7430D00E7DE33 /* RCTBorderStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBorderStyle.h; sourceTree = "<group>"; };
E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTTextDecorationLineType.h; sourceTree = "<group>"; };
E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAccessibilityManager.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -362,6 +365,8 @@
13B07FF31A6947C200A75B9A /* Views */ = {
isa = PBXGroup;
children = (
8AAF3CA71C74C27F0039FC59 /* AHKNavigationController.h */,
8AAF3CA81C74C27F0039FC59 /* AHKNavigationController.m */,
13B080181A69489C00A75B9A /* RCTActivityIndicatorViewManager.h */,
13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */,
13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */,
Expand Down Expand Up @@ -719,6 +724,7 @@
191E3EC11C29DC3800C180A6 /* RCTRefreshControl.m in Sources */,
13C156051AB1A2840079392D /* RCTWebView.m in Sources */,
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */,
8AAF3CA91C74C27F0039FC59 /* AHKNavigationController.m in Sources */,
13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */,
58114A161AAE854800E7D092 /* RCTPicker.m in Sources */,
137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */,
Expand Down
11 changes: 11 additions & 0 deletions React/Views/AHKNavigationController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Created by Arkadiusz on 01-04-14.

#import <UIKit/UIKit.h>

/// A UINavigationController subclass allowing the interactive pop gesture to be recognized when the navigation bar is hidden or a custom back button is used.
@interface AHKNavigationController : UINavigationController

- (void)pushViewController:(UIViewController *)viewController
animated:(BOOL)animated __attribute__((objc_requires_super));

@end
104 changes: 104 additions & 0 deletions React/Views/AHKNavigationController.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Created by Arkadiusz on 01-04-14.

#import "AHKNavigationController.h"

@interface AHKNavigationController () <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
/// A Boolean value indicating whether navigation controller is currently pushing a new view controller on the stack.
@property (nonatomic, getter = isDuringPushAnimation) BOOL duringPushAnimation;
/// A real delegate of the class. `delegate` property is used only for keeping an internal state during
/// animations – we need to know when the animation ended, and that info is available only
/// from `navigationController:didShowViewController:animated:`.
@property (weak, nonatomic) id<UINavigationControllerDelegate> realDelegate;
@end

@implementation AHKNavigationController

#pragma mark - NSObject

- (void)dealloc
{
self.delegate = nil;
self.interactivePopGestureRecognizer.delegate = nil;
}

#pragma mark - UIViewController

- (void)viewDidLoad
{
[super viewDidLoad];

if (!self.delegate) {
self.delegate = self;
}

self.interactivePopGestureRecognizer.delegate = self;
}

#pragma mark - UINavigationController

- (void)setDelegate:(id<UINavigationControllerDelegate>)delegate
{
self.realDelegate = delegate != self ? delegate : nil;
[super setDelegate:delegate ? self : nil];
}

- (void)pushViewController:(UIViewController *)viewController
animated:(BOOL)animated __attribute__((objc_requires_super))
{
self.duringPushAnimation = YES;
[super pushViewController:viewController animated:animated];
}

#pragma mark UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
NSCAssert(self.interactivePopGestureRecognizer.delegate == self, @"AHKNavigationController won't work correctly if you change interactivePopGestureRecognizer's delegate.");

self.duringPushAnimation = NO;

if ([self.realDelegate respondsToSelector:_cmd]) {
[self.realDelegate navigationController:navigationController didShowViewController:viewController animated:animated];
}
}

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if (gestureRecognizer == self.interactivePopGestureRecognizer) {
// Disable pop gesture in two situations:
// 1) when the pop animation is in progress
// 2) when user swipes quickly a couple of times and animations don't have time to be performed
return [self.viewControllers count] > 1 && !self.isDuringPushAnimation;
} else {
// default value
return YES;
}
}

#pragma mark - Delegate Forwarder

// Thanks for the idea goes to: https://github.com/steipete/PSPDFTextView/blob/ee9ce04ad04217efe0bc84d67f3895a34252d37c/PSPDFTextView/PSPDFTextView.m#L148-164

- (BOOL)respondsToSelector:(SEL)s
{
return [super respondsToSelector:s] || [self.realDelegate respondsToSelector:s];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)s
{
return [super methodSignatureForSelector:s] ?: [(id)self.realDelegate methodSignatureForSelector:s];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
id delegate = self.realDelegate;
if ([delegate respondsToSelector:invocation.selector]) {
[invocation invokeWithTarget:delegate];
}
}

@end
13 changes: 7 additions & 6 deletions React/Views/RCTNavigator.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#import "RCTView.h"
#import "RCTWrapperViewController.h"
#import "UIView+React.h"
#import "AHKNavigationController.h"

typedef NS_ENUM(NSUInteger, RCTNavigationLock) {
RCTNavigationLockNone,
Expand All @@ -31,7 +32,7 @@ typedef NS_ENUM(NSUInteger, RCTNavigationLock) {
NSInteger kNeverProgressed = -10000;


@interface UINavigationController ()
@interface AHKNavigationController ()

// need to declare this since `UINavigationController` doesnt publicly declare the fact that it implements
// UINavigationBarDelegate :(
Expand All @@ -41,7 +42,7 @@ - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigati

// http://stackoverflow.com/questions/5115135/uinavigationcontroller-how-to-cancel-the-back-button-event
// There's no other way to do this unfortunately :(
@interface RCTNavigationController : UINavigationController <UINavigationBarDelegate>
@interface RCTNavigationController : AHKNavigationController <UINavigationBarDelegate>
{
dispatch_block_t _scrollCallback;
}
Expand Down Expand Up @@ -348,7 +349,7 @@ - (UIViewController *)reactViewController
* swipe-back abort interaction, which leaves us *no* other way to clean up
* locks aside from the animation complete hook.
*/
- (void)navigationController:(UINavigationController *)navigationController
- (void)navigationController:(AHKNavigationController *)navigationController
willShowViewController:(__unused UIViewController *)viewController
animated:(__unused BOOL)animated
{
Expand All @@ -361,13 +362,13 @@ - (void)navigationController:(UINavigationController *)navigationController
(RCTWrapperViewController *)[context viewControllerForKey:UITransitionContextFromViewControllerKey];
RCTWrapperViewController *toController =
(RCTWrapperViewController *)[context viewControllerForKey:UITransitionContextToViewControllerKey];

// This may be triggered by a navigation controller unrelated to me: if so, ignore.
if (fromController.navigationController != _navigationController ||
toController.navigationController != _navigationController) {
return;
}

NSUInteger indexOfFrom = [_currentViews indexOfObject:fromController.navItem];
NSUInteger indexOfTo = [_currentViews indexOfObject:toController.navItem];
CGFloat destination = indexOfFrom < indexOfTo ? 1.0 : -1.0;
Expand Down Expand Up @@ -552,7 +553,7 @@ - (void)reactBridgeDidFinishTransaction
// TODO: This will likely fail when performing multiple pushes/pops. We must
// free the lock only after the *last* push/pop.
- (void)wrapperViewController:(RCTWrapperViewController *)wrapperViewController
didMoveToNavigationController:(UINavigationController *)navigationController
didMoveToNavigationController:(AHKNavigationController *)navigationController
{
if (self.superview == nil) {
// If superview is nil, then a JS reload (Cmd+R) happened
Expand Down