diff --git a/docs/screen-api.md b/docs/screen-api.md
index c015df0e746..52871761e7e 100644
--- a/docs/screen-api.md
+++ b/docs/screen-api.md
@@ -17,7 +17,17 @@ this.props.navigator.push({
backButtonTitle: undefined, // override the back button title (optional)
backButtonHidden: false, // hide the back button altogether (optional)
navigatorStyle: {}, // override the navigator style for the pushed screen (optional)
- navigatorButtons: {} // override the nav buttons for the pushed screen (optional)
+ navigatorButtons: {}, // override the nav buttons for the pushed screen (optional)
+ // enable peek and pop - commited screen will have `isPreview` prop set as true.
+ previewView: undefined, // react ref or node id (optional)
+ previewHeight: undefined, // set preview height, defaults to full height (optional)
+ previewCommit: true, // commit to push preview controller to the navigation stack (optional)
+ previewActions: [{ // action presses can be detected with the `PreviewActionPress` event on the commited screen.
+ id: '', // action id (required)
+ title: '', // action title (required)
+ style: undefined, // 'selected' or 'destructive' (optional)
+ actions: [], // list of sub-actions
+ }],
});
```
@@ -295,6 +305,8 @@ export default class ExampleScreen extends Component {
break;
case 'didDisappear':
break;
+ case 'willCommitPreview':
+ break;
}
}
}
@@ -348,3 +360,12 @@ export default class ExampleScreen extends Component {
}
}
```
+
+# Peek and pop (3D touch)
+
+react-native-navigation supports the [Peek and pop](
+https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/Adopting3DTouchOniPhone/#//apple_ref/doc/uid/TP40016543-CH1-SW3) feature by setting a react view reference as a `previewView` parameter when doing a push, more options are available in the `push` section.
+
+You can define actions and listen for interactions on the pushed screen with the `PreviewActionPress` event.
+
+Previewed screens will have the prop `isPreview` that can be used to render different things when the screen is in the "Peek" state and will then recieve a navigator event of `willCommitPreview` when in the "Pop" state.
\ No newline at end of file
diff --git a/example/src/components/Row.js b/example/src/components/Row.js
index 4857870eff2..97e484b9da9 100644
--- a/example/src/components/Row.js
+++ b/example/src/components/Row.js
@@ -2,27 +2,32 @@ import React from 'react';
import PropTypes from 'prop-types';
import {StyleSheet, View, Text, TouchableHighlight, Platform} from 'react-native';
-function Row({title, onPress, platform, testID}) {
- if (platform && platform !== Platform.OS) {
- return ;
- }
+class Row extends React.PureComponent {
+ render() {
+ const {title, onPress, onPressIn, platform, testID} = this.props;
+ if (platform && platform !== Platform.OS) {
+ return ;
+ }
- return (
-
-
- {title}
-
-
- );
+ return (
+
+
+ {title}
+
+
+ );
+ }
}
Row.propTypes = {
title: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
+ onPressIn: PropTypes.func
};
const styles = StyleSheet.create({
diff --git a/example/src/screens/NavigationTypes.js b/example/src/screens/NavigationTypes.js
index b9214f99459..4973829f74f 100644
--- a/example/src/screens/NavigationTypes.js
+++ b/example/src/screens/NavigationTypes.js
@@ -34,6 +34,28 @@ class NavigationTypes extends React.Component {
});
};
+ previewScreen = () => {
+ this.props.navigator.push({
+ screen: 'example.Types.Push',
+ title: 'New Screen',
+ previewCommit: true,
+ previewHeight: 250,
+ previewView: this.previewRef,
+ previewActions: [{
+ id: 'action-cancel',
+ title: 'Cancel'
+ }, {
+ id: 'action-delete',
+ title: 'Delete',
+ actions: [{
+ id: 'action-delete-sure',
+ title: 'Are you sure?',
+ style: 'destructive'
+ }]
+ }]
+ });
+ };
+
pushListScreen = () => {
console.log('RANG', 'pushListScreen');
this.props.navigator.push({
@@ -107,6 +129,13 @@ class NavigationTypes extends React.Component {
+ (this.previewRef = ref)}
+ title={'Preview Screen'}
+ testID={'previewScreen'}
+ onPress={this.pushScreen}
+ onPressIn={this.previewScreen}
+ />
{/**/}
diff --git a/example/src/screens/types/Push.js b/example/src/screens/types/Push.js
index e9c2c4cefc4..8a5bde6d2a8 100644
--- a/example/src/screens/types/Push.js
+++ b/example/src/screens/types/Push.js
@@ -1,8 +1,24 @@
import React, {Component} from 'react';
-import {StyleSheet, View, Text, Button} from 'react-native';
+import {StyleSheet, View, Text, Button, Alert} from 'react-native';
class Push extends Component {
+ constructor(props) {
+ super(props);
+ this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
+ }
+
+ onNavigatorEvent(event) {
+ if (event.type === 'PreviewActionPress') {
+ if (event.id === 'action-cancel') {
+ Alert.alert('Cancelled');
+ }
+ if (event.id === 'action-delete-sure') {
+ Alert.alert('Deleted');
+ }
+ }
+ }
+
onPushAnother = () => {
this.props.navigator.push({
screen: 'example.Types.Push',
diff --git a/ios/RCCNavigationController.m b/ios/RCCNavigationController.m
index dda66881a93..4bdbffa0664 100755
--- a/ios/RCCNavigationController.m
+++ b/ios/RCCNavigationController.m
@@ -2,6 +2,10 @@
#import "RCCViewController.h"
#import "RCCManager.h"
#import
+#import
+#if __has_include()
+#import
+#endif
#import
#import
#import
@@ -160,6 +164,33 @@ - (void)performAction:(NSString*)performAction actionParams:(NSDictionary*)actio
{
[self setButtons:rightButtons viewController:viewController side:@"right" animated:NO];
}
+
+ NSArray *previewActions = actionParams[@"previewActions"];
+ NSString *previewViewID = actionParams[@"previewViewID"];
+ if (previewViewID) {
+ if ([self.topViewController isKindOfClass:[RCCViewController class]])
+ {
+ RCCViewController *topViewController = ((RCCViewController*)self.topViewController);
+ viewController.previewActions = previewActions;
+ viewController.previewCommit = actionParams[@"previewCommit"] ? [actionParams[@"previewCommit"] boolValue] : YES;
+ NSNumber *previewHeight = actionParams[@"previewHeight"];
+ if (previewHeight) {
+ viewController.preferredContentSize = CGSizeMake(viewController.view.frame.size.width, [previewHeight floatValue]);
+ }
+ if (topViewController.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable)
+ {
+ dispatch_async(RCTGetUIManagerQueue(), ^{
+ [bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) {
+ UIView *view = viewRegistry[previewViewID];
+ topViewController.previewView = view;
+ [topViewController registerForPreviewingWithDelegate:(id)topViewController sourceView:view];
+ }];
+ });
+ topViewController.previewController = viewController;
+ }
+ return;
+ }
+ }
NSString *animationType = actionParams[@"animationType"];
if ([animationType isEqualToString:@"fade"])
diff --git a/ios/RCCViewController.h b/ios/RCCViewController.h
index 1978783725d..27ca96d415a 100755
--- a/ios/RCCViewController.h
+++ b/ios/RCCViewController.h
@@ -18,6 +18,10 @@ extern NSString* const RCCViewControllerCancelReactTouchesNotification;
@property (nonatomic, strong) NSString *controllerId;
@property (nonatomic, strong) NSString *commandType;
@property (nonatomic, strong) NSString *timestamp;
+@property (nonatomic) RCCViewController *previewController;
+@property (nonatomic) UIView *previewView;
+@property (nonatomic) NSArray *previewActions;
+@property (nonatomic) BOOL previewCommit;
+ (UIViewController*)controllerWithLayout:(NSDictionary *)layout globalProps:(NSDictionary *)globalProps bridge:(RCTBridge *)bridge;
diff --git a/ios/RCCViewController.m b/ios/RCCViewController.m
index b0222f1def4..964e8a9d0f6 100755
--- a/ios/RCCViewController.m
+++ b/ios/RCCViewController.m
@@ -19,7 +19,7 @@
const NSInteger BLUR_NAVBAR_TAG = 78264802;
const NSInteger TRANSPARENT_NAVBAR_TAG = 78264803;
-@interface RCCViewController()
+@interface RCCViewController()
@property (nonatomic) BOOL _hidesBottomBarWhenPushed;
@property (nonatomic) BOOL _statusBarHideWithNavBar;
@property (nonatomic) BOOL _statusBarHidden;
@@ -794,6 +794,37 @@ -(void)addExternalVCIfNecessary:(NSDictionary*)props
}
}
+#pragma mark - Preview Actions
+
+- (void)onActionPress:(NSString *)id {
+ if ([self.view isKindOfClass:[RCTRootView class]]) {
+ RCTRootView *rootView = (RCTRootView *)self.view;
+ if (rootView.appProperties && rootView.appProperties[@"navigatorEventID"]) {
+ [[[RCCManager sharedInstance] getBridge].eventDispatcher
+ sendAppEventWithName:rootView.appProperties[@"navigatorEventID"]
+ body:@{
+ @"type": @"PreviewActionPress",
+ @"id": id
+ }];
+ }
+ }
+}
+
+- (UIPreviewAction *) convertAction:(NSDictionary *)action {
+ NSString *actionId = action[@"id"];
+ NSString *actionTitle = action[@"title"];
+ UIPreviewActionStyle actionStyle = UIPreviewActionStyleDefault;
+ if ([action[@"style"] isEqualToString:@"selected"]) {
+ actionStyle = UIPreviewActionStyleSelected;
+ }
+ if ([action[@"style"] isEqualToString:@"destructive"]) {
+ actionStyle = UIPreviewActionStyleDestructive;
+ }
+ return [UIPreviewAction actionWithTitle:actionTitle style:actionStyle handler:^(UIPreviewAction * _Nonnull action, UIViewController * _Nonnull previewViewController) {
+ [self onActionPress:actionId];
+ }];
+}
+
#pragma mark - NewRelic
- (NSString*) customNewRelicInteractionName
@@ -830,4 +861,36 @@ -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecogniz
return !disabledSimultaneousGestureBool;
}
+#pragma mark - UIViewControllerPreviewingDelegate
+- (UIViewController *)previewingContext:(id)previewingContext viewControllerForLocation:(CGPoint)location {
+ return self.previewController;
+}
+
+- (void)previewingContext:(id)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
+ if (self.previewController.previewCommit == YES) {
+ [self.previewController sendGlobalScreenEvent:@"willCommitPreview" endTimestampString:[self.previewController getTimestampString] shouldReset:YES];
+ [self.previewController sendScreenChangedEvent:@"willCommitPreview"];
+ [self.navigationController pushViewController:self.previewController animated:false];
+ }
+}
+
+- (NSArray> *)previewActionItems {
+ NSMutableArray *actions = [[NSMutableArray alloc] init];
+ for (NSDictionary *previewAction in self.previewActions) {
+ UIPreviewAction *action = [self convertAction:previewAction];
+ NSDictionary *actionActions = previewAction[@"actions"];
+ if (actionActions.count > 0) {
+ NSMutableArray *group = [[NSMutableArray alloc] init];
+ for (NSDictionary *previewGroupAction in actionActions) {
+ [group addObject:[self convertAction:previewGroupAction]];
+ }
+ UIPreviewActionGroup *actionGroup = [UIPreviewActionGroup actionGroupWithTitle:action.title style:UIPreviewActionStyleDefault actions:group];
+ [actions addObject:actionGroup];
+ } else {
+ [actions addObject:action];
+ }
+ }
+ return actions;
+}
+
@end
diff --git a/src/deprecated/platformSpecificDeprecated.ios.js b/src/deprecated/platformSpecificDeprecated.ios.js
index 867c2b54397..6531ae52518 100644
--- a/src/deprecated/platformSpecificDeprecated.ios.js
+++ b/src/deprecated/platformSpecificDeprecated.ios.js
@@ -1,4 +1,6 @@
/*eslint-disable*/
+import { Component } from 'react';
+import { findNodeHandle } from 'react-native';
import Navigation from './../Navigation';
import Controllers, {Modal, Notification, ScreenUtils} from './controllers';
const React = Controllers.hijackReact();
@@ -234,7 +236,15 @@ function navigatorPush(navigator, params) {
console.error('Navigator.push(params): params.screen is required');
return;
}
+ let previewViewID;
const screenInstanceID = _.uniqueId('screenInstanceID');
+ if (params.previewView instanceof Component) {
+ previewViewID = findNodeHandle(params.previewView)
+ } else if (typeof params.previewView === 'number') {
+ previewViewID = params.previewView;
+ } else if (params.previewView) {
+ console.error('Navigator.push(params): params.previewView is not a valid react view');
+ }
const {
navigatorStyle,
navigatorButtons,
@@ -246,6 +256,8 @@ function navigatorPush(navigator, params) {
passProps.navigatorID = navigator.navigatorID;
passProps.screenInstanceID = screenInstanceID;
passProps.navigatorEventID = navigatorEventID;
+ passProps.previewViewID = previewViewID;
+ passProps.isPreview = !!previewViewID;
params.navigationParams = {
screenInstanceID,
@@ -270,6 +282,10 @@ function navigatorPush(navigator, params) {
backButtonHidden: params.backButtonHidden,
leftButtons: navigatorButtons.leftButtons,
rightButtons: navigatorButtons.rightButtons,
+ previewViewID: previewViewID,
+ previewActions: params.previewActions,
+ previewHeight: params.previewHeight,
+ previewCommit: params.previewCommit,
timestamp: Date.now()
});
}