Skip to content

Commit

Permalink
NavigatorIOS: Expose interactivePopGestureEnabled property
Browse files Browse the repository at this point in the history
Summary:
Previously, the back swipe navigation gesture would be enabled when the navigation bar is shown and disabled when the navigation bar is hidden.

This change enables developers to control the back swipe gesture independently of the visibility of the navigation bar. An example use case would be that an app wants to render a custom navigation bar so it sets `navigationBarHidden` to true and it wants to enable the back swipe gesture so it sets `interactivePopGestureEnabled` to true.

**Test plan (required)**

- Created a test app to verify setting `interactivePopGestureEnabled` to `true` and `false` with the navigation bar both hidden and shown.
- Verified prop works in a larger app.

Adam Comella
Microsoft Corp.
Closes facebook#7369

Differential Revision: D3269304

Pulled By: javache

fb-gh-sync-id: ec4324f6517cec4b4fc4f62c4394dc9208a8af6a
fbshipit-source-id: ec4324f6517cec4b4fc4f62c4394dc9208a8af6a
  • Loading branch information
Adam Comella authored and Facebook Github Bot 3 committed May 6, 2016
1 parent 2132ad1 commit 4d2c72b
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 3 deletions.
12 changes: 11 additions & 1 deletion Libraries/Components/Navigation/NavigatorIOS.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,15 @@ var NavigatorIOS = React.createClass({
*/
translucent: PropTypes.bool,

/**
* A Boolean value that indicates whether the interactive pop gesture is enabled. Useful
* for enabling/disabling the back swipe navigation gesture. If this prop is not provided,
* the default behavior is for the back swipe gesture to be enabled when the navigation bar
* is shown and disabled when the navigation bar is hidden. Once you've provided
* the interactivePopGestureEnabled prop, you can never restore the default behavior.
*/
interactivePopGestureEnabled: PropTypes.bool,

},

navigator: (undefined: ?Object),
Expand Down Expand Up @@ -686,7 +695,8 @@ var NavigatorIOS = React.createClass({
style={styles.transitioner}
vertical={this.props.vertical}
requestedTopOfStack={this.state.requestedTopOfStack}
onNavigationComplete={this._handleNavigationComplete}>
onNavigationComplete={this._handleNavigationComplete}
interactivePopGestureEnabled={this.props.interactivePopGestureEnabled}>
{items}
</NavigatorTransitionerIOS>
</StaticContainer>
Expand Down
1 change: 1 addition & 0 deletions React/Views/RCTNavigator.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

@property (nonatomic, strong) UIView *reactNavSuperviewLink;
@property (nonatomic, assign) NSInteger requestedTopOfStack;
@property (nonatomic, assign) BOOL interactivePopGestureEnabled;

- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;

Expand Down
42 changes: 40 additions & 2 deletions React/Views/RCTNavigator.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ typedef NS_ENUM(NSUInteger, RCTNavigationLock) {
RCTNavigationLockJavaScript
};

// By default the interactive pop gesture will be enabled when the navigation bar is displayed
// and disabled when hidden
// RCTPopGestureStateDefault maps to the default behavior (mentioned above). Once popGestureState
// leaves this value, it can never be returned back to it. This is because, due to a limitation in
// the iOS APIs, once we override the default behavior of the gesture recognizer, we cannot return
// back to it.
// RCTPopGestureStateEnabled will enable the gesture independent of nav bar visibility
// RCTPopGestureStateDisabled will disable the gesture independent of nav bar visibility
typedef NS_ENUM(NSUInteger, RCTPopGestureState) {
RCTPopGestureStateDefault = 0,
RCTPopGestureStateEnabled,
RCTPopGestureStateDisabled
};

NSInteger kNeverRequested = -1;
NSInteger kNeverProgressed = -10000;

Expand Down Expand Up @@ -191,13 +205,15 @@ - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigati

@end

@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate>
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate, UIGestureRecognizerDelegate>

@property (nonatomic, copy) RCTDirectEventBlock onNavigationProgress;
@property (nonatomic, copy) RCTBubblingEventBlock onNavigationComplete;

@property (nonatomic, assign) NSInteger previousRequestedTopOfStack;

@property (nonatomic, assign) RCTPopGestureState popGestureState;

// Previous views are only mainted in order to detect incorrect
// addition/removal of views below the `requestedTopOfStack`
@property (nonatomic, copy, readwrite) NSArray<RCTNavItem *> *previousViews;
Expand Down Expand Up @@ -332,8 +348,21 @@ - (void)setPaused:(BOOL)paused
}
}

- (void)setInteractivePopGestureEnabled:(BOOL)interactivePopGestureEnabled
{
_interactivePopGestureEnabled = interactivePopGestureEnabled;

_navigationController.interactivePopGestureRecognizer.delegate = self;
_navigationController.interactivePopGestureRecognizer.enabled = interactivePopGestureEnabled;

_popGestureState = interactivePopGestureEnabled ? RCTPopGestureStateEnabled : RCTPopGestureStateDisabled;
}

- (void)dealloc
{
if (_navigationController.interactivePopGestureRecognizer.delegate == self) {
_navigationController.interactivePopGestureRecognizer.delegate = nil;
}
_navigationController.delegate = nil;
[_navigationController removeFromParentViewController];
}
Expand All @@ -343,6 +372,11 @@ - (UIViewController *)reactViewController
return _navigationController;
}

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return _navigationController.viewControllers.count > 1;
}

/**
* See documentation about lock lifecycle. This is only here to clean up
* swipe-back abort interaction, which leaves us *no* other way to clean up
Expand Down Expand Up @@ -399,7 +433,11 @@ - (BOOL)requestSchedulingJavaScriptNavigation
- (void)freeLock
{
_navigationController.navigationLock = RCTNavigationLockNone;
_navigationController.interactivePopGestureRecognizer.enabled = YES;

// Unless the pop gesture has been explicitly disabled (RCTPopGestureStateDisabled),
// Set interactivePopGestureRecognizer.enabled to YES
// If the popGestureState is RCTPopGestureStateDefault the default behavior will be maintained
_navigationController.interactivePopGestureRecognizer.enabled = self.popGestureState != RCTPopGestureStateDisabled;
}

/**
Expand Down
1 change: 1 addition & 0 deletions React/Views/RCTNavigatorManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ - (UIView *)view
RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(onNavigationProgress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onNavigationComplete, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(interactivePopGestureEnabled, BOOL)

// TODO: remove error callbacks
RCT_EXPORT_METHOD(requestSchedulingJavaScriptNavigation:(nonnull NSNumber *)reactTag
Expand Down

0 comments on commit 4d2c72b

Please sign in to comment.