Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion docs/screen-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
}],
});
```

Expand Down Expand Up @@ -295,6 +305,8 @@ export default class ExampleScreen extends Component {
break;
case 'didDisappear':
break;
case 'willCommitPreview':
break;
}
}
}
Expand Down Expand Up @@ -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.
35 changes: 20 additions & 15 deletions example/src/components/Row.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <View />;
}
class Row extends React.PureComponent {
render() {
const {title, onPress, onPressIn, platform, testID} = this.props;
if (platform && platform !== Platform.OS) {
return <View />;
}

return (
<TouchableHighlight
onPress={onPress}
testID={testID}
underlayColor={'rgba(0, 0, 0, 0.054)'}
>
<View style={styles.row}>
<Text style={styles.text}>{title}</Text>
</View>
</TouchableHighlight>
);
return (
<TouchableHighlight
onPress={onPress}
onPressIn={onPressIn}
testID={testID}
underlayColor={'rgba(0, 0, 0, 0.054)'}
>
<View style={styles.row}>
<Text style={styles.text}>{title}</Text>
</View>
</TouchableHighlight>
);
}
}

Row.propTypes = {
title: PropTypes.string.isRequired,
onPress: PropTypes.func.isRequired,
onPressIn: PropTypes.func
};

const styles = StyleSheet.create({
Expand Down
29 changes: 29 additions & 0 deletions example/src/screens/NavigationTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -107,6 +129,13 @@ class NavigationTypes extends React.Component {
<ScrollView style={styles.container}>
<Row title={'Toggle Drawer'} onPress={this.toggleDrawer}/>
<Row title={'Push Screen'} testID={'pushScreen'} onPress={this.pushScreen}/>
<Row
ref={(ref) => (this.previewRef = ref)}
title={'Preview Screen'}
testID={'previewScreen'}
onPress={this.pushScreen}
onPressIn={this.previewScreen}
/>
{/*<Row title={'Push List Screen'} testID={'pushListScreen'} onPress={this.pushListScreen}/>*/}
<Row title={'Custom TopBar'} onPress={this.pushCustomTopBarScreen}/>
<Row title={'Custom Button'} onPress={this.pushCustomButtonScreen}/>
Expand Down
18 changes: 17 additions & 1 deletion example/src/screens/types/Push.js
Original file line number Diff line number Diff line change
@@ -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',
Expand Down
31 changes: 31 additions & 0 deletions ios/RCCNavigationController.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
#import "RCCViewController.h"
#import "RCCManager.h"
#import <React/RCTEventDispatcher.h>
#import <React/RCTUIManager.h>
#if __has_include(<React/RCTUIManagerUtils.h>)
#import <React/RCTUIManagerUtils.h>
#endif
#import <React/RCTConvert.h>
#import <React/RCTRootView.h>
#import <objc/runtime.h>
Expand Down Expand Up @@ -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<NSNumber *, UIView *> *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"])
Expand Down
4 changes: 4 additions & 0 deletions ios/RCCViewController.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
65 changes: 64 additions & 1 deletion ios/RCCViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
const NSInteger BLUR_NAVBAR_TAG = 78264802;
const NSInteger TRANSPARENT_NAVBAR_TAG = 78264803;

@interface RCCViewController() <UIGestureRecognizerDelegate>
@interface RCCViewController() <UIGestureRecognizerDelegate, UIViewControllerPreviewingDelegate>
@property (nonatomic) BOOL _hidesBottomBarWhenPushed;
@property (nonatomic) BOOL _statusBarHideWithNavBar;
@property (nonatomic) BOOL _statusBarHidden;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -830,4 +861,36 @@ -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecogniz
return !disabledSimultaneousGestureBool;
}

#pragma mark - UIViewControllerPreviewingDelegate
- (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
return self.previewController;
}

- (void)previewingContext:(id<UIViewControllerPreviewing>)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<id<UIPreviewActionItem>> *)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
16 changes: 16 additions & 0 deletions src/deprecated/platformSpecificDeprecated.ios.js
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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()
});
}
Expand Down