Remove NavigatorIOS

Summary:
Legacy navigator impl. There are other alternatives that should be used instead.

Part of the slimmening effort as described here: https://github.com/react-native-community/discussions-and-proposals/issues/6

Reviewed By: TheSavior

Differential Revision: D9677824

fbshipit-source-id: 24ae500751d2a8c398f246d36604a58f0b3c113b
This commit is contained in:
Kevin Gozali 2018-09-06 08:45:57 -07:00 committed by Facebook Github Bot
parent 9e63b5c5d2
commit 0df92afc1c
17 changed files with 7 additions and 2580 deletions

View File

@ -1,12 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
module.exports = require('UnimplementedView');

View File

@ -1,932 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
const EventEmitter = require('EventEmitter');
const Image = require('Image');
const RCTNavigatorManager = require('NativeModules').NavigatorManager;
const React = require('React');
const PropTypes = require('prop-types');
const ReactNative = require('ReactNative');
const StaticContainer = require('StaticContainer.react');
const StyleSheet = require('StyleSheet');
const TVEventHandler = require('TVEventHandler');
const View = require('View');
const DeprecatedViewPropTypes = require('DeprecatedViewPropTypes');
const createReactClass = require('create-react-class');
const invariant = require('fbjs/lib/invariant');
const requireNativeComponent = require('requireNativeComponent');
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
* found when Flow v0.54 was deployed. To see the error delete this comment and
* run Flow. */
const keyMirror = require('fbjs/lib/keyMirror');
const TRANSITIONER_REF = 'transitionerRef';
let __uid = 0;
function getuid() {
return __uid++;
}
class NavigatorTransitionerIOS extends React.Component<$FlowFixMeProps> {
requestSchedulingNavigation(cb) {
RCTNavigatorManager.requestSchedulingJavaScriptNavigation(
ReactNative.findNodeHandle(this),
cb,
);
}
render() {
return <RCTNavigator {...this.props} />;
}
}
const SystemIconLabels = {
done: true,
cancel: true,
edit: true,
save: true,
add: true,
compose: true,
reply: true,
action: true,
organize: true,
bookmarks: true,
search: true,
refresh: true,
stop: true,
camera: true,
trash: true,
play: true,
pause: true,
rewind: true,
'fast-forward': true,
undo: true,
redo: true,
'page-curl': true,
};
const SystemIcons = keyMirror(SystemIconLabels);
type SystemButtonType = $Enum<typeof SystemIconLabels>;
type Route = {
component: Function,
title: string,
titleImage?: Object,
passProps?: Object,
backButtonTitle?: string,
backButtonIcon?: Object,
leftButtonTitle?: string,
leftButtonIcon?: Object,
leftButtonSystemIcon?: SystemButtonType,
onLeftButtonPress?: Function,
rightButtonTitle?: string,
rightButtonIcon?: Object,
rightButtonSystemIcon?: SystemButtonType,
onRightButtonPress?: Function,
wrapperStyle?: any,
};
type State = {
idStack: Array<number>,
routeStack: Array<Route>,
requestedTopOfStack: number,
observedTopOfStack: number,
progress: number,
fromIndex: number,
toIndex: number,
makingNavigatorRequest: boolean,
updatingAllIndicesAtOrBeyond: ?number,
};
type Event = Object;
/**
* Think of `<NavigatorIOS>` as simply a component that renders an
* `RCTNavigator`, and moves the `RCTNavigator`'s `requestedTopOfStack` pointer
* forward and backward. The `RCTNavigator` interprets changes in
* `requestedTopOfStack` to be pushes and pops of children that are rendered.
* `<NavigatorIOS>` always ensures that whenever the `requestedTopOfStack`
* pointer is moved, that we've also rendered enough children so that the
* `RCTNavigator` can carry out the push/pop with those children.
* `<NavigatorIOS>` also removes children that will no longer be needed
* (after the pop of a child has been fully completed/animated out).
*/
/**
* `NavigatorIOS` is a wrapper around
* [`UINavigationController`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UINavigationController_Class/),
* enabling you to implement a navigation stack. It works exactly the same as it
* would on a native app using `UINavigationController`, providing the same
* animations and behavior from UIKit.
*
* As the name implies, it is only available on iOS. Take a look at
* [`React Navigation`](https://reactnavigation.org/) for a cross-platform
* solution in JavaScript, or check out either of these components for native
* solutions: [native-navigation](http://airbnb.io/native-navigation/),
* [react-native-navigation](https://github.com/wix/react-native-navigation).
*
* To set up the navigator, provide the `initialRoute` prop with a route
* object. A route object is used to describe each scene that your app
* navigates to. `initialRoute` represents the first route in your navigator.
*
* ```
* import PropTypes from 'prop-types';
* import React, { Component } from 'react';
* import { NavigatorIOS, Text } from 'react-native';
*
* export default class NavigatorIOSApp extends Component {
* render() {
* return (
* <NavigatorIOS
* initialRoute={{
* component: MyScene,
* title: 'My Initial Scene',
* }}
* style={{flex: 1}}
* />
* );
* }
* }
*
* class MyScene extends Component {
* static propTypes = {
* title: PropTypes.string.isRequired,
* navigator: PropTypes.object.isRequired,
* }
*
* _onForward = () => {
* this.props.navigator.push({
* title: 'Scene ' + nextIndex,
* });
* }
*
* render() {
* return (
* <View>
* <Text>Current Scene: { this.props.title }</Text>
* <TouchableHighlight onPress={this._onForward}>
* <Text>Tap me to load the next scene</Text>
* </TouchableHighlight>
* </View>
* )
* }
* }
* ```
*
* In this code, the navigator renders the component specified in initialRoute,
* which in this case is `MyScene`. This component will receive a `route` prop
* and a `navigator` prop representing the navigator. The navigator's navigation
* bar will render the title for the current scene, "My Initial Scene".
*
* You can optionally pass in a `passProps` property to your `initialRoute`.
* `NavigatorIOS` passes this in as props to the rendered component:
*
* ```
* initialRoute={{
* component: MyScene,
* title: 'My Initial Scene',
* passProps: { myProp: 'foo' }
* }}
* ```
*
* You can then access the props passed in via `{this.props.myProp}`.
*
* #### Handling Navigation
*
* To trigger navigation functionality such as pushing or popping a view, you
* have access to a `navigator` object. The object is passed in as a prop to any
* component that is rendered by `NavigatorIOS`. You can then call the
* relevant methods to perform the navigation action you need:
*
* ```
* class MyView extends Component {
* _handleBackPress() {
* this.props.navigator.pop();
* }
*
* _handleNextPress(nextRoute) {
* this.props.navigator.push(nextRoute);
* }
*
* render() {
* const nextRoute = {
* component: MyView,
* title: 'Bar That',
* passProps: { myProp: 'bar' }
* };
* return(
* <TouchableHighlight onPress={() => this._handleNextPress(nextRoute)}>
* <Text style={{marginTop: 200, alignSelf: 'center'}}>
* See you on the other nav {this.props.myProp}!
* </Text>
* </TouchableHighlight>
* );
* }
* }
* ```
*
* You can also trigger navigator functionality from the `NavigatorIOS`
* component:
*
* ```
* class NavvyIOS extends Component {
* _handleNavigationRequest() {
* this.refs.nav.push({
* component: MyView,
* title: 'Genius',
* passProps: { myProp: 'genius' },
* });
* }
*
* render() {
* return (
* <NavigatorIOS
* ref='nav'
* initialRoute={{
* component: MyView,
* title: 'Foo This',
* passProps: { myProp: 'foo' },
* rightButtonTitle: 'Add',
* onRightButtonPress: () => this._handleNavigationRequest(),
* }}
* style={{flex: 1}}
* />
* );
* }
* }
* ```
*
* The code above adds a `_handleNavigationRequest` private method that is
* invoked from the `NavigatorIOS` component when the right navigation bar item
* is pressed. To get access to the navigator functionality, a reference to it
* is saved in the `ref` prop and later referenced to push a new scene into the
* navigation stack.
*
* #### Navigation Bar Configuration
*
* Props passed to `NavigatorIOS` will set the default configuration
* for the navigation bar. Props passed as properties to a route object will set
* the configuration for that route's navigation bar, overriding any props
* passed to the `NavigatorIOS` component.
*
* ```
* _handleNavigationRequest() {
* this.refs.nav.push({
* //...
* passProps: { myProp: 'genius' },
* barTintColor: '#996699',
* });
* }
*
* render() {
* return (
* <NavigatorIOS
* //...
* style={{flex: 1}}
* barTintColor='#ffffcc'
* />
* );
* }
* ```
*
* In the example above the navigation bar color is changed when the new route
* is pushed.
*
*/
const NavigatorIOS = createReactClass({
displayName: 'NavigatorIOS',
propTypes: {
/**
* NavigatorIOS uses `route` objects to identify child views, their props,
* and navigation bar configuration. Navigation operations such as push
* operations expect routes to look like this the `initialRoute`.
*/
initialRoute: PropTypes.shape({
/**
* The React Class to render for this route
*/
component: PropTypes.func.isRequired,
/**
* The title displayed in the navigation bar and the back button for this
* route.
*/
title: PropTypes.string.isRequired,
/**
* If set, a title image will appear instead of the text title.
*/
titleImage: Image.propTypes.source,
/**
* Use this to specify additional props to pass to the rendered
* component. `NavigatorIOS` will automatically pass in `route` and
* `navigator` props to the component.
*/
passProps: PropTypes.object,
/**
* If set, the left navigation button image will be displayed using this
* source. Note that this doesn't apply to the header of the current
* view, but to those views that are subsequently pushed.
*/
backButtonIcon: Image.propTypes.source,
/**
* If set, the left navigation button text will be set to this. Note that
* this doesn't apply to the left button of the current view, but to
* those views that are subsequently pushed
*/
backButtonTitle: PropTypes.string,
/**
* If set, the left navigation button image will be displayed using
* this source.
*/
leftButtonIcon: Image.propTypes.source,
/**
* If set, the left navigation button will display this text.
*/
leftButtonTitle: PropTypes.string,
/**
* If set, the left header button will appear with this system icon
*
* Supported icons are `done`, `cancel`, `edit`, `save`, `add`,
* `compose`, `reply`, `action`, `organize`, `bookmarks`, `search`,
* `refresh`, `stop`, `camera`, `trash`, `play`, `pause`, `rewind`,
* `fast-forward`, `undo`, `redo`, and `page-curl`
*/
leftButtonSystemIcon: PropTypes.oneOf(Object.keys(SystemIcons)),
/**
* This function will be invoked when the left navigation bar item is
* pressed.
*/
onLeftButtonPress: PropTypes.func,
/**
* If set, the right navigation button image will be displayed using
* this source.
*/
rightButtonIcon: Image.propTypes.source,
/**
* If set, the right navigation button will display this text.
*/
rightButtonTitle: PropTypes.string,
/**
* If set, the right header button will appear with this system icon
*
* See leftButtonSystemIcon for supported icons
*/
rightButtonSystemIcon: PropTypes.oneOf(Object.keys(SystemIcons)),
/**
* This function will be invoked when the right navigation bar item is
* pressed.
*/
onRightButtonPress: PropTypes.func,
/**
* Styles for the navigation item containing the component.
*/
wrapperStyle: DeprecatedViewPropTypes.style,
/**
* Boolean value that indicates whether the navigation bar is hidden.
*/
navigationBarHidden: PropTypes.bool,
/**
* Boolean value that indicates whether to hide the 1px hairline
* shadow.
*/
shadowHidden: PropTypes.bool,
/**
* The color used for the buttons in the navigation bar.
*/
tintColor: PropTypes.string,
/**
* The background color of the navigation bar.
*/
barTintColor: PropTypes.string,
/**
* The style of the navigation bar. Supported values are 'default', 'black'.
* Use 'black' instead of setting `barTintColor` to black. This produces
* a navigation bar with the native iOS style with higher translucency.
*/
barStyle: PropTypes.oneOf(['default', 'black']),
/**
* The text color of the navigation bar title.
*/
titleTextColor: PropTypes.string,
/**
* Boolean value that indicates whether the navigation bar is
* translucent.
*/
translucent: PropTypes.bool,
}).isRequired,
/**
* Boolean value that indicates whether the navigation bar is hidden
* by default.
*/
navigationBarHidden: PropTypes.bool,
/**
* Boolean value that indicates whether to hide the 1px hairline shadow
* by default.
*/
shadowHidden: PropTypes.bool,
/**
* The default wrapper style for components in the navigator.
* A common use case is to set the `backgroundColor` for every scene.
*/
itemWrapperStyle: DeprecatedViewPropTypes.style,
/**
* The default color used for the buttons in the navigation bar.
*/
tintColor: PropTypes.string,
/**
* The default background color of the navigation bar.
*/
barTintColor: PropTypes.string,
/**
* The style of the navigation bar. Supported values are 'default', 'black'.
* Use 'black' instead of setting `barTintColor` to black. This produces
* a navigation bar with the native iOS style with higher translucency.
*/
barStyle: PropTypes.oneOf(['default', 'black']),
/**
* The default text color of the navigation bar title.
*/
titleTextColor: PropTypes.string,
/**
* Boolean value that indicates whether the navigation bar is
* translucent by default
*/
translucent: PropTypes.bool,
/**
* Boolean value that indicates whether the interactive pop gesture is
* enabled. This is 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),
UNSAFE_componentWillMount: function() {
// Precompute a pack of callbacks that's frequently generated and passed to
// instances.
this.navigator = {
push: this.push,
pop: this.pop,
popN: this.popN,
replace: this.replace,
replaceAtIndex: this.replaceAtIndex,
replacePrevious: this.replacePrevious,
replacePreviousAndPop: this.replacePreviousAndPop,
resetTo: this.resetTo,
popToRoute: this.popToRoute,
popToTop: this.popToTop,
};
},
componentDidMount: function() {
this._enableTVEventHandler();
},
componentWillUnmount: function() {
this._disableTVEventHandler();
},
getDefaultProps: function(): Object {
return {
translucent: true,
};
},
getInitialState: function(): State {
return {
idStack: [getuid()],
routeStack: [this.props.initialRoute],
// The navigation index that we wish to push/pop to.
requestedTopOfStack: 0,
// The last index that native has sent confirmation of completed push/pop
// for. At this point, we can discard any views that are beyond the
// `requestedTopOfStack`. A value of `null` means we have not received
// any confirmation, ever. We may receive an `observedTopOfStack` without
// ever requesting it - native can instigate pops of its own with the
// backswipe gesture.
observedTopOfStack: 0,
progress: 1,
fromIndex: 0,
toIndex: 0,
// Whether or not we are making a navigator request to push/pop. (Used
// for performance optimization).
makingNavigatorRequest: false,
// Whether or not we are updating children of navigator and if so (not
// `null`) which index marks the beginning of all updates. Used for
// performance optimization.
updatingAllIndicesAtOrBeyond: 0,
};
},
_toFocusOnNavigationComplete: (undefined: any),
_handleFocusRequest: function(item: any) {
if (this.state.makingNavigatorRequest) {
this._toFocusOnNavigationComplete = item;
} else {
this._getFocusEmitter().emit('focus', item);
}
},
_focusEmitter: (undefined: ?EventEmitter),
_getFocusEmitter: function(): EventEmitter {
// Flow not yet tracking assignments to instance fields.
let focusEmitter = this._focusEmitter;
if (!focusEmitter) {
focusEmitter = new EventEmitter();
this._focusEmitter = focusEmitter;
}
return focusEmitter;
},
getChildContext: function(): {
onFocusRequested: Function,
focusEmitter: EventEmitter,
} {
return {
onFocusRequested: this._handleFocusRequest,
focusEmitter: this._getFocusEmitter(),
};
},
childContextTypes: {
onFocusRequested: PropTypes.func,
focusEmitter: PropTypes.instanceOf(EventEmitter),
},
_tryLockNavigator: function(cb: () => void) {
this.refs[TRANSITIONER_REF].requestSchedulingNavigation(
acquiredLock => acquiredLock && cb(),
);
},
_handleNavigatorStackChanged: function(e: Event) {
const newObservedTopOfStack = e.nativeEvent.stackLength - 1;
invariant(
newObservedTopOfStack <= this.state.requestedTopOfStack,
'No navigator item should be pushed without JS knowing about it %s %s',
newObservedTopOfStack,
this.state.requestedTopOfStack,
);
const wasWaitingForConfirmation =
this.state.requestedTopOfStack !== this.state.observedTopOfStack;
if (wasWaitingForConfirmation) {
invariant(
newObservedTopOfStack === this.state.requestedTopOfStack,
'If waiting for observedTopOfStack to reach requestedTopOfStack, ' +
'the only valid observedTopOfStack should be requestedTopOfStack.',
);
}
// Mark the most recent observation regardless of if we can lock the
// navigator. `observedTopOfStack` merely represents what we've observed
// and this first `setState` is only executed to update debugging
// overlays/navigation bar.
// Also reset progress, toIndex, and fromIndex as they might not end
// in the correct states for a two possible reasons:
// Progress isn't always 0 or 1 at the end, the system rounds
// If the Navigator is offscreen these values won't be updated
// TOOD: Revisit this decision when no longer relying on native navigator.
const nextState = {
observedTopOfStack: newObservedTopOfStack,
makingNavigatorRequest: false,
updatingAllIndicesAtOrBeyond: null,
progress: 1,
toIndex: newObservedTopOfStack,
fromIndex: newObservedTopOfStack,
};
this.setState(nextState, this._eliminateUnneededChildren);
},
_eliminateUnneededChildren: function() {
// Updating the indices that we're deleting and that's all. (Truth: Nothing
// even uses the indices in this case, but let's make this describe the
// truth anyways).
const updatingAllIndicesAtOrBeyond =
this.state.routeStack.length > this.state.observedTopOfStack + 1
? this.state.observedTopOfStack + 1
: null;
this.setState({
idStack: this.state.idStack.slice(0, this.state.observedTopOfStack + 1),
routeStack: this.state.routeStack.slice(
0,
this.state.observedTopOfStack + 1,
),
// Now we rerequest the top of stack that we observed.
requestedTopOfStack: this.state.observedTopOfStack,
makingNavigatorRequest: true,
updatingAllIndicesAtOrBeyond: updatingAllIndicesAtOrBeyond,
});
},
/**
* Navigate forward to a new route.
* @param route The new route to navigate to.
*/
push: function(route: Route) {
invariant(!!route, 'Must supply route to push');
// Make sure all previous requests are caught up first. Otherwise reject.
if (this.state.requestedTopOfStack === this.state.observedTopOfStack) {
this._tryLockNavigator(() => {
const nextStack = this.state.routeStack.concat([route]);
const nextIDStack = this.state.idStack.concat([getuid()]);
this.setState({
// We have to make sure that we've also supplied enough views to
// satisfy our request to adjust the `requestedTopOfStack`.
idStack: nextIDStack,
routeStack: nextStack,
requestedTopOfStack: nextStack.length - 1,
makingNavigatorRequest: true,
updatingAllIndicesAtOrBeyond: nextStack.length - 1,
});
});
}
},
/**
* Go back N scenes at once. When N=1, behavior matches `pop()`.
* @param n The number of scenes to pop.
*/
popN: function(n: number) {
if (n === 0) {
return;
}
// Make sure all previous requests are caught up first. Otherwise reject.
if (this.state.requestedTopOfStack === this.state.observedTopOfStack) {
if (this.state.requestedTopOfStack > 0) {
this._tryLockNavigator(() => {
const newRequestedTopOfStack = this.state.requestedTopOfStack - n;
invariant(newRequestedTopOfStack >= 0, 'Cannot pop below 0');
this.setState({
requestedTopOfStack: newRequestedTopOfStack,
makingNavigatorRequest: true,
updatingAllIndicesAtOrBeyond: this.state.requestedTopOfStack - n,
});
});
}
}
},
/**
* Pop back to the previous scene.
*/
pop: function() {
this.popN(1);
},
/**
* Replace a route in the navigation stack.
*
* @param route The new route that will replace the specified one.
* @param index The route into the stack that should be replaced.
* If it is negative, it counts from the back of the stack.
*/
replaceAtIndex: function(route: Route, index: number) {
invariant(!!route, 'Must supply route to replace');
if (index < 0) {
index += this.state.routeStack.length;
}
if (this.state.routeStack.length <= index) {
return;
}
// I don't believe we need to lock for a replace since there's no
// navigation actually happening
const nextIDStack = this.state.idStack.slice();
const nextRouteStack = this.state.routeStack.slice();
nextIDStack[index] = getuid();
nextRouteStack[index] = route;
this.setState({
idStack: nextIDStack,
routeStack: nextRouteStack,
makingNavigatorRequest: false,
updatingAllIndicesAtOrBeyond: index,
});
},
/**
* Replace the route for the current scene and immediately
* load the view for the new route.
* @param route The new route to navigate to.
*/
replace: function(route: Route) {
this.replaceAtIndex(route, -1);
},
/**
* Replace the route/view for the previous scene.
* @param route The new route to will replace the previous scene.
*/
replacePrevious: function(route: Route) {
this.replaceAtIndex(route, -2);
},
/**
* Go back to the topmost item in the navigation stack.
*/
popToTop: function() {
this.popToRoute(this.state.routeStack[0]);
},
/**
* Go back to the item for a particular route object.
* @param route The new route to navigate to.
*/
popToRoute: function(route: Route) {
const indexOfRoute = this.state.routeStack.indexOf(route);
invariant(
indexOfRoute !== -1,
"Calling pop to route for a route that doesn't exist!",
);
const numToPop = this.state.routeStack.length - indexOfRoute - 1;
this.popN(numToPop);
},
/**
* Replaces the previous route/view and transitions back to it.
* @param route The new route that replaces the previous scene.
*/
replacePreviousAndPop: function(route: Route) {
// Make sure all previous requests are caught up first. Otherwise reject.
if (this.state.requestedTopOfStack !== this.state.observedTopOfStack) {
return;
}
if (this.state.routeStack.length < 2) {
return;
}
this._tryLockNavigator(() => {
this.replacePrevious(route);
this.setState({
requestedTopOfStack: this.state.requestedTopOfStack - 1,
makingNavigatorRequest: true,
});
});
},
/**
* Replaces the top item and pop to it.
* @param route The new route that will replace the topmost item.
*/
resetTo: function(route: Route) {
invariant(!!route, 'Must supply route to push');
// Make sure all previous requests are caught up first. Otherwise reject.
if (this.state.requestedTopOfStack !== this.state.observedTopOfStack) {
return;
}
this.replaceAtIndex(route, 0);
this.popToRoute(route);
},
_handleNavigationComplete: function(e: Event) {
// Don't propagate to other NavigatorIOS instances this is nested in:
e.stopPropagation();
if (this._toFocusOnNavigationComplete) {
this._getFocusEmitter().emit('focus', this._toFocusOnNavigationComplete);
this._toFocusOnNavigationComplete = null;
}
this._handleNavigatorStackChanged(e);
},
_routeToStackItem: function(routeArg: Route, i: number) {
const {component, wrapperStyle, passProps, ...route} = routeArg;
const {itemWrapperStyle, ...props} = this.props;
const shouldUpdateChild =
this.state.updatingAllIndicesAtOrBeyond != null &&
this.state.updatingAllIndicesAtOrBeyond >= i;
const Component = component;
return (
<StaticContainer key={'nav' + i} shouldUpdate={shouldUpdateChild}>
<RCTNavigatorItem
{...props}
{...route}
style={[styles.stackItem, itemWrapperStyle, wrapperStyle]}>
<Component navigator={this.navigator} route={route} {...passProps} />
</RCTNavigatorItem>
</StaticContainer>
);
},
_renderNavigationStackItems: function() {
const shouldRecurseToNavigator =
this.state.makingNavigatorRequest ||
this.state.updatingAllIndicesAtOrBeyond !== null;
// If not recursing update to navigator at all, may as well avoid
// computation of navigator children.
const items = shouldRecurseToNavigator
? this.state.routeStack.map(this._routeToStackItem)
: null;
return (
<StaticContainer shouldUpdate={shouldRecurseToNavigator}>
<NavigatorTransitionerIOS
ref={TRANSITIONER_REF}
style={styles.transitioner}
// $FlowFixMe(>=0.41.0)
vertical={this.props.vertical}
requestedTopOfStack={this.state.requestedTopOfStack}
onNavigationComplete={this._handleNavigationComplete}
interactivePopGestureEnabled={
this.props.interactivePopGestureEnabled
}>
{items}
</NavigatorTransitionerIOS>
</StaticContainer>
);
},
_tvEventHandler: (undefined: ?TVEventHandler),
_enableTVEventHandler: function() {
this._tvEventHandler = new TVEventHandler();
this._tvEventHandler.enable(this, function(cmp, evt) {
if (evt && evt.eventType === 'menu') {
cmp.pop();
}
});
},
_disableTVEventHandler: function() {
if (this._tvEventHandler) {
this._tvEventHandler.disable();
delete this._tvEventHandler;
}
},
render: function() {
return (
// $FlowFixMe(>=0.41.0)
<View style={this.props.style}>{this._renderNavigationStackItems()}</View>
);
},
});
const styles = StyleSheet.create({
stackItem: {
backgroundColor: 'white',
overflow: 'hidden',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
transitioner: {
flex: 1,
},
});
const RCTNavigator = requireNativeComponent('RCTNavigator');
const RCTNavigatorItem = requireNativeComponent('RCTNavItem');
module.exports = NavigatorIOS;

View File

@ -66,9 +66,6 @@ const ReactNative = {
get Modal() {
return require('Modal');
},
get NavigatorIOS() {
return require('NavigatorIOS');
},
get Picker() {
return require('Picker');
},
@ -325,6 +322,13 @@ const ReactNative = {
'Learn about alternative navigation solutions at http://facebook.github.io/react-native/docs/navigation.html',
);
},
get NavigatorIOS() {
invariant(
false,
'NavigatorIOS is deprecated and has been removed from this package. ' +
'Learn about alternative navigation solutions at http://facebook.github.io/react-native/docs/navigation.html',
);
},
};
module.exports = ReactNative;

View File

@ -1,84 +0,0 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @format
*/
'use strict';
var React = require('react');
var ReactNative = require('react-native');
var {NavigatorIOS, StatusBar, StyleSheet, Text, View} = ReactNative;
class EmptyPage extends React.Component<{
text: string,
}> {
render() {
return (
<View style={styles.emptyPage}>
<Text style={styles.emptyPageText}>{this.props.text}</Text>
</View>
);
}
}
class NavigatorIOSColors extends React.Component<{}> {
static title = '<NavigatorIOS> - Custom Bar Style';
static description = 'iOS navigation with custom nav bar colors';
render() {
// Set StatusBar with light contents to get better contrast
StatusBar.setBarStyle('light-content');
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
component: EmptyPage,
title: '<NavigatorIOS>',
rightButtonTitle: 'Done',
onRightButtonPress: () => {
StatusBar.setBarStyle('default');
this.props.onExampleExit();
},
passProps: {
text: 'The nav bar is black with barStyle prop.',
},
}}
barStyle="black"
/>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
},
emptyPage: {
flex: 1,
paddingTop: 64,
},
emptyPageText: {
margin: 10,
},
});
NavigatorIOSColors.external = true;
module.exports = NavigatorIOSColors;

View File

@ -1,75 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
'use strict';
var React = require('react');
var ReactNative = require('react-native');
var {NavigatorIOS, StatusBar, StyleSheet, Text, View} = ReactNative;
class EmptyPage extends React.Component {
render() {
return (
<View style={styles.emptyPage}>
<Text style={styles.emptyPageText}>{this.props.text}</Text>
</View>
);
}
}
class NavigatorIOSColors extends React.Component {
static title = '<NavigatorIOS> - Custom Colors';
static description = 'iOS navigation with custom nav bar colors';
render() {
// Set StatusBar with light contents to get better contrast
StatusBar.setBarStyle('light-content');
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
component: EmptyPage,
title: '<NavigatorIOS>',
rightButtonTitle: 'Done',
onRightButtonPress: () => {
StatusBar.setBarStyle('default');
this.props.onExampleExit();
},
passProps: {
text:
'The nav bar has custom colors with tintColor, ' +
'barTintColor and titleTextColor props.',
},
}}
tintColor="#FFFFFF"
barTintColor="#183E63"
titleTextColor="#FFFFFF"
translucent={true}
/>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
},
emptyPage: {
flex: 1,
paddingTop: 64,
},
emptyPageText: {
margin: 10,
},
});
NavigatorIOSColors.external = true;
module.exports = NavigatorIOSColors;

View File

@ -1,303 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
'use strict';
const React = require('react');
const ReactNative = require('react-native');
const ViewExample = require('./ViewExample');
const createExamplePage = require('./createExamplePage');
const nativeImageSource = require('nativeImageSource');
const {
AlertIOS,
NavigatorIOS,
ScrollView,
StyleSheet,
Text,
TouchableHighlight,
View,
} = ReactNative;
class EmptyPage extends React.Component<$FlowFixMeProps> {
render() {
return (
<View style={styles.emptyPage}>
<Text style={styles.emptyPageText}>{this.props.text}</Text>
</View>
);
}
}
class NavigatorIOSExamplePage extends React.Component<$FlowFixMeProps> {
render() {
var recurseTitle = 'Recurse Navigation';
if (!this.props.depth || this.props.depth === 1) {
recurseTitle += ' - more examples here';
}
return (
<ScrollView style={styles.list}>
<View style={styles.line} />
<View style={styles.group}>
{this._renderRow(recurseTitle, () => {
this.props.navigator.push({
title: NavigatorIOSExample.title,
component: NavigatorIOSExamplePage,
backButtonTitle: 'Custom Back',
passProps: {depth: this.props.depth ? this.props.depth + 1 : 1},
});
})}
{this._renderRow('Push View Example', () => {
this.props.navigator.push({
title: 'Very Long Custom View Example Title',
component: createExamplePage(null, ViewExample),
});
})}
{this._renderRow('Custom title image Example', () => {
this.props.navigator.push({
title: 'Custom title image Example',
titleImage: require('./relay.png'),
component: createExamplePage(null, ViewExample),
});
})}
{this._renderRow('Custom Right Button', () => {
this.props.navigator.push({
title: NavigatorIOSExample.title,
component: EmptyPage,
rightButtonTitle: 'Cancel',
onRightButtonPress: () => this.props.navigator.pop(),
passProps: {
text: 'This page has a right button in the nav bar',
},
});
})}
{this._renderRow('Custom Right System Button', () => {
this.props.navigator.push({
title: NavigatorIOSExample.title,
component: EmptyPage,
rightButtonSystemIcon: 'bookmarks',
onRightButtonPress: () => this.props.navigator.pop(),
passProps: {
text: 'This page has a right system button in the nav bar',
},
});
})}
{this._renderRow('Custom Left & Right Icons', () => {
this.props.navigator.push({
title: NavigatorIOSExample.title,
component: EmptyPage,
leftButtonTitle: 'Custom Left',
onLeftButtonPress: () => this.props.navigator.pop(),
rightButtonIcon: nativeImageSource({
ios: 'NavBarButtonPlus',
width: 17,
height: 17,
}),
onRightButtonPress: () => {
AlertIOS.alert(
'Bar Button Action',
'Recognized a tap on the bar button icon',
[
{
text: 'OK',
onPress: () => console.log('Tapped OK'),
},
],
);
},
passProps: {
text:
'This page has an icon for the right button in the nav bar',
},
});
})}
{this._renderRow('Custom Left & Right System Icons', () => {
this.props.navigator.push({
title: NavigatorIOSExample.title,
component: EmptyPage,
leftButtonSystemIcon: 'cancel',
onLeftButtonPress: () => this.props.navigator.pop(),
rightButtonSystemIcon: 'search',
onRightButtonPress: () => {
AlertIOS.alert(
'Bar Button Action',
'Recognized a tap on the bar button icon',
[
{
text: 'OK',
onPress: () => console.log('Tapped OK'),
},
],
);
},
passProps: {
text:
'This page has an icon for the right button in the nav bar',
},
});
})}
{this._renderRow('Pop', () => {
this.props.navigator.pop();
})}
{this._renderRow('Pop to top', () => {
this.props.navigator.popToTop();
})}
{this._renderReplace()}
{this._renderReplacePrevious()}
{this._renderReplacePreviousAndPop()}
{this._renderRow(
'Exit NavigatorIOS Example',
this.props.onExampleExit,
)}
</View>
<View style={styles.line} />
</ScrollView>
);
}
_renderReplace = () => {
if (!this.props.depth) {
// this is to avoid replacing the top of the stack
return null;
}
return this._renderRow('Replace here', () => {
var prevRoute = this.props.route;
this.props.navigator.replace({
title: 'New Navigation',
component: EmptyPage,
rightButtonTitle: 'Undo',
onRightButtonPress: () => this.props.navigator.replace(prevRoute),
passProps: {
text:
'The component is replaced, but there is currently no ' +
'way to change the right button or title of the current route',
},
});
});
};
_renderReplacePrevious = () => {
if (!this.props.depth || this.props.depth < 2) {
// this is to avoid replacing the top of the stack
return null;
}
return this._renderRow('Replace previous', () => {
this.props.navigator.replacePrevious({
title: 'Replaced',
component: EmptyPage,
passProps: {
text: 'This is a replaced "previous" page',
},
wrapperStyle: styles.customWrapperStyle,
});
});
};
_renderReplacePreviousAndPop = () => {
if (!this.props.depth || this.props.depth < 2) {
// this is to avoid replacing the top of the stack
return null;
}
return this._renderRow('Replace previous and pop', () => {
this.props.navigator.replacePreviousAndPop({
title: 'Replaced and Popped',
component: EmptyPage,
passProps: {
text: 'This is a replaced "previous" page',
},
wrapperStyle: styles.customWrapperStyle,
});
});
};
_renderRow = (title: string, onPress: Function) => {
return (
<View>
<TouchableHighlight onPress={onPress}>
<View style={styles.row}>
<Text style={styles.rowText}>{title}</Text>
</View>
</TouchableHighlight>
<View style={styles.separator} />
</View>
);
};
}
class NavigatorIOSExample extends React.Component<$FlowFixMeProps> {
static title = '<NavigatorIOS>';
static description = 'iOS navigation capabilities';
static external = true;
render() {
const {onExampleExit} = this.props;
return (
<NavigatorIOS
style={styles.container}
initialRoute={{
title: NavigatorIOSExample.title,
component: NavigatorIOSExamplePage,
passProps: {onExampleExit},
}}
tintColor="#008888"
/>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
customWrapperStyle: {
backgroundColor: '#bbdddd',
},
emptyPage: {
flex: 1,
paddingTop: 64,
},
emptyPageText: {
margin: 10,
},
list: {
backgroundColor: '#eeeeee',
marginTop: 10,
},
group: {
backgroundColor: 'white',
},
groupSpace: {
height: 15,
},
line: {
backgroundColor: '#bbbbbb',
height: StyleSheet.hairlineWidth,
},
row: {
backgroundColor: 'white',
justifyContent: 'center',
paddingHorizontal: 15,
paddingVertical: 15,
},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: '#bbbbbb',
marginLeft: 15,
},
rowNote: {
fontSize: 17,
},
rowText: {
fontSize: 17,
fontWeight: '500',
},
});
module.exports = NavigatorIOSExample;

View File

@ -92,21 +92,6 @@ const ComponentExamples: Array<RNTesterExample> = [
module: require('./MultiColumnExample'),
supportsTVOS: true,
},
{
key: 'NavigatorIOSColorsExample',
module: require('./NavigatorIOSColorsExample'),
supportsTVOS: false,
},
{
key: 'NavigatorIOSBarStyleExample',
module: require('./NavigatorIOSBarStyleExample'),
supportsTVOS: false,
},
{
key: 'NavigatorIOSExample',
module: require('./NavigatorIOSExample'),
supportsTVOS: true,
},
{
key: 'PickerExample',
module: require('./PickerExample'),

View File

@ -1,42 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTComponent.h>
@interface RCTNavItem : UIView
@property (nonatomic, copy) NSString *title;
@property (nonatomic, strong) UIImage *titleImage;
@property (nonatomic, strong) UIImage *leftButtonIcon;
@property (nonatomic, copy) NSString *leftButtonTitle;
@property (nonatomic, assign) UIBarButtonSystemItem leftButtonSystemIcon;
@property (nonatomic, strong) UIImage *rightButtonIcon;
@property (nonatomic, copy) NSString *rightButtonTitle;
@property (nonatomic, assign) UIBarButtonSystemItem rightButtonSystemIcon;
@property (nonatomic, strong) UIImage *backButtonIcon;
@property (nonatomic, copy) NSString *backButtonTitle;
@property (nonatomic, assign) BOOL navigationBarHidden;
@property (nonatomic, assign) BOOL shadowHidden;
@property (nonatomic, strong) UIColor *tintColor;
@property (nonatomic, strong) UIColor *barTintColor;
@property (nonatomic, strong) UIColor *titleTextColor;
@property (nonatomic, assign) BOOL translucent;
#if !TARGET_OS_TV
@property (nonatomic, assign) UIBarStyle barStyle;
#endif
@property (nonatomic, readonly) UIImageView *titleImageView;
@property (nonatomic, readonly) UIBarButtonItem *backButtonItem;
@property (nonatomic, readonly) UIBarButtonItem *leftButtonItem;
@property (nonatomic, readonly) UIBarButtonItem *rightButtonItem;
@property (nonatomic, copy) RCTBubblingEventBlock onLeftButtonPress;
@property (nonatomic, copy) RCTBubblingEventBlock onRightButtonPress;
@end

View File

@ -1,174 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTNavItem.h"
@implementation RCTNavItem
@synthesize backButtonItem = _backButtonItem;
@synthesize leftButtonItem = _leftButtonItem;
@synthesize rightButtonItem = _rightButtonItem;
- (UIImageView *)titleImageView
{
if (_titleImage) {
return [[UIImageView alloc] initWithImage:_titleImage];
} else {
return nil;
}
}
-(instancetype)init
{
if (self = [super init]) {
_leftButtonSystemIcon = NSNotFound;
_rightButtonSystemIcon = NSNotFound;
}
return self;
}
- (void)setBackButtonTitle:(NSString *)backButtonTitle
{
_backButtonTitle = backButtonTitle;
_backButtonItem = nil;
}
- (void)setBackButtonIcon:(UIImage *)backButtonIcon
{
_backButtonIcon = backButtonIcon;
_backButtonItem = nil;
}
- (UIBarButtonItem *)backButtonItem
{
if (!_backButtonItem) {
if (_backButtonIcon) {
_backButtonItem = [[UIBarButtonItem alloc] initWithImage:_backButtonIcon
style:UIBarButtonItemStylePlain
target:nil
action:nil];
} else if (_backButtonTitle.length) {
_backButtonItem = [[UIBarButtonItem alloc] initWithTitle:_backButtonTitle
style:UIBarButtonItemStylePlain
target:nil
action:nil];
} else {
_backButtonItem = nil;
}
}
return _backButtonItem;
}
- (void)setLeftButtonTitle:(NSString *)leftButtonTitle
{
_leftButtonTitle = leftButtonTitle;
_leftButtonItem = nil;
}
- (void)setLeftButtonIcon:(UIImage *)leftButtonIcon
{
_leftButtonIcon = leftButtonIcon;
_leftButtonItem = nil;
}
- (void)setLeftButtonSystemIcon:(UIBarButtonSystemItem)leftButtonSystemIcon
{
_leftButtonSystemIcon = leftButtonSystemIcon;
_leftButtonItem = nil;
}
- (UIBarButtonItem *)leftButtonItem
{
if (!_leftButtonItem) {
if (_leftButtonIcon) {
_leftButtonItem =
[[UIBarButtonItem alloc] initWithImage:_leftButtonIcon
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleLeftButtonPress)];
} else if (_leftButtonTitle.length) {
_leftButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_leftButtonTitle
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleLeftButtonPress)];
} else if (_leftButtonSystemIcon != NSNotFound) {
_leftButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:_leftButtonSystemIcon
target:self
action:@selector(handleLeftButtonPress)];
} else {
_leftButtonItem = nil;
}
}
return _leftButtonItem;
}
- (void)handleLeftButtonPress
{
if (_onLeftButtonPress) {
_onLeftButtonPress(nil);
}
}
- (void)setRightButtonTitle:(NSString *)rightButtonTitle
{
_rightButtonTitle = rightButtonTitle;
_rightButtonItem = nil;
}
- (void)setRightButtonIcon:(UIImage *)rightButtonIcon
{
_rightButtonIcon = rightButtonIcon;
_rightButtonItem = nil;
}
- (void)setRightButtonSystemIcon:(UIBarButtonSystemItem)rightButtonSystemIcon
{
_rightButtonSystemIcon = rightButtonSystemIcon;
_rightButtonItem = nil;
}
- (UIBarButtonItem *)rightButtonItem
{
if (!_rightButtonItem) {
if (_rightButtonIcon) {
_rightButtonItem =
[[UIBarButtonItem alloc] initWithImage:_rightButtonIcon
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleRightButtonPress)];
} else if (_rightButtonTitle.length) {
_rightButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_rightButtonTitle
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleRightButtonPress)];
} else if (_rightButtonSystemIcon != NSNotFound) {
_rightButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:_rightButtonSystemIcon
target:self
action:@selector(handleRightButtonPress)];
} else {
_rightButtonItem = nil;
}
}
return _rightButtonItem;
}
- (void)handleRightButtonPress
{
if (_onRightButtonPress) {
_onRightButtonPress(nil);
}
}
@end

View File

@ -1,19 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTConvert.h>
#import <React/RCTViewManager.h>
@interface RCTConvert (BarButtonSystemItem)
+ (UIBarButtonSystemItem)UIBarButtonSystemItem:(id)json;
@end
@interface RCTNavItemManager : RCTViewManager
@end

View File

@ -1,80 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTNavItemManager.h"
#import "RCTConvert.h"
#import "RCTNavItem.h"
@implementation RCTConvert (BarButtonSystemItem)
RCT_ENUM_CONVERTER(UIBarButtonSystemItem, (@{
@"done": @(UIBarButtonSystemItemDone),
@"cancel": @(UIBarButtonSystemItemCancel),
@"edit": @(UIBarButtonSystemItemEdit),
@"save": @(UIBarButtonSystemItemSave),
@"add": @(UIBarButtonSystemItemAdd),
@"flexible-space": @(UIBarButtonSystemItemFlexibleSpace),
@"fixed-space": @(UIBarButtonSystemItemFixedSpace),
@"compose": @(UIBarButtonSystemItemCompose),
@"reply": @(UIBarButtonSystemItemReply),
@"action": @(UIBarButtonSystemItemAction),
@"organize": @(UIBarButtonSystemItemOrganize),
@"bookmarks": @(UIBarButtonSystemItemBookmarks),
@"search": @(UIBarButtonSystemItemSearch),
@"refresh": @(UIBarButtonSystemItemRefresh),
@"stop": @(UIBarButtonSystemItemStop),
@"camera": @(UIBarButtonSystemItemCamera),
@"trash": @(UIBarButtonSystemItemTrash),
@"play": @(UIBarButtonSystemItemPlay),
@"pause": @(UIBarButtonSystemItemPause),
@"rewind": @(UIBarButtonSystemItemRewind),
@"fast-forward": @(UIBarButtonSystemItemFastForward),
@"undo": @(UIBarButtonSystemItemUndo),
@"redo": @(UIBarButtonSystemItemRedo),
@"page-curl": @(UIBarButtonSystemItemPageCurl)
}), NSNotFound, integerValue);
@end
@implementation RCTNavItemManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [RCTNavItem new];
}
RCT_EXPORT_VIEW_PROPERTY(navigationBarHidden, BOOL)
RCT_EXPORT_VIEW_PROPERTY(shadowHidden, BOOL)
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor)
#if !TARGET_OS_TV
RCT_EXPORT_VIEW_PROPERTY(barStyle, UIBarStyle)
#endif
RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
RCT_EXPORT_VIEW_PROPERTY(titleTextColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(titleImage, UIImage)
RCT_EXPORT_VIEW_PROPERTY(backButtonIcon, UIImage)
RCT_EXPORT_VIEW_PROPERTY(backButtonTitle, NSString)
RCT_EXPORT_VIEW_PROPERTY(leftButtonTitle, NSString)
RCT_EXPORT_VIEW_PROPERTY(leftButtonIcon, UIImage)
RCT_EXPORT_VIEW_PROPERTY(leftButtonSystemIcon, UIBarButtonSystemItem)
RCT_EXPORT_VIEW_PROPERTY(rightButtonIcon, UIImage)
RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString)
RCT_EXPORT_VIEW_PROPERTY(rightButtonSystemIcon, UIBarButtonSystemItem)
RCT_EXPORT_VIEW_PROPERTY(onLeftButtonPress, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onRightButtonPress, RCTBubblingEventBlock)
@end

View File

@ -1,34 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <React/RCTFrameUpdate.h>
@class RCTBridge;
@interface RCTNavigator : UIView <RCTFrameUpdateObserver>
@property (nonatomic, strong) UIView *reactNavSuperviewLink;
@property (nonatomic, assign) NSInteger requestedTopOfStack;
@property (nonatomic, assign) BOOL interactivePopGestureEnabled;
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
/**
* Schedules a JavaScript navigation and prevents `UIKit` from navigating until
* JavaScript has sent its scheduled navigation.
*
* @returns Whether or not a JavaScript driven navigation could be
* scheduled/reserved. If returning `NO`, JavaScript should usually just do
* nothing at all.
*/
- (BOOL)requestSchedulingJavaScriptNavigation;
- (void)uiManagerDidPerformMounting;
@end

View File

@ -1,630 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTNavigator.h"
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTNavItem.h"
#import "RCTScrollView.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "RCTWrapperViewController.h"
#import "UIView+React.h"
typedef NS_ENUM(NSUInteger, RCTNavigationLock) {
RCTNavigationLockNone,
RCTNavigationLockNative,
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;
@interface UINavigationController ()
// need to declare this since `UINavigationController` doesn't publicly declare the fact that it implements
// UINavigationBarDelegate :(
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
@end
// 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>
{
dispatch_block_t _scrollCallback;
}
@property (nonatomic, assign) RCTNavigationLock navigationLock;
@end
/**
* In general, `RCTNavigator` examines `_currentViews` (which are React child
* views), and compares them to `_navigationController.viewControllers` (which
* are controlled by UIKit).
*
* It is possible for JavaScript (`_currentViews`) to "get ahead" of native
* (`navigationController.viewControllers`) and vice versa. JavaScript gets
* ahead by adding/removing React subviews. Native gets ahead by swiping back,
* or tapping the back button. In both cases, the other system is initially
* unaware. And in both cases, `RCTNavigator` helps the other side "catch up".
*
* If `RCTNavigator` sees the number of React children have changed, it
* pushes/pops accordingly. If `RCTNavigator` sees a `UIKit` driven push/pop, it
* notifies JavaScript that this has happened, and expects that JavaScript will
* eventually render more children to match `UIKit`. There's no rush for
* JavaScript to catch up. But if it does render anything, it must catch up to
* UIKit. It cannot deviate.
*
* To implement this, we need a lock, which we store on the native thread. This
* lock allows one of the systems to push/pop views. Whoever wishes to
* "get ahead" must obtain the lock. Whoever wishes to "catch up" must obtain
* the lock. One thread may not "get ahead" or "catch up" when the other has
* the lock. Once a thread has the lock, it can only do the following:
*
* 1. If it is behind, it may only catch up.
* 2. If it is caught up or ahead, it may push or pop.
*
*
* ========= Acquiring The Lock ==========
*
* JavaScript asynchronously acquires the lock using a native hook. It might be
* rejected and receive the return value `false`.
*
* We acquire the native lock in `shouldPopItem`, which is called right before
* native tries to push/pop, but only if JavaScript doesn't already have the
* lock.
*
* ======== While JavaScript Has Lock ====
*
* When JavaScript has the lock, we have to block all `UIKit` driven pops:
*
* 1. Block back button navigation:
* - Back button will invoke `shouldPopItem`, from which we return `NO` if
* JavaScript has the lock.
* - Back button will respect the return value `NO` and not permit
* navigation.
*
* 2. Block swipe-to-go-back navigation:
* - Swipe will trigger `shouldPopItem`, but swipe won't respect our `NO`
* return value so we must disable the gesture recognizer while JavaScript
* has the lock.
*
* ======== While Native Has Lock =======
*
* We simply deny JavaScript the right to acquire the lock.
*
*
* ======== Releasing The Lock ===========
*
* Recall that the lock represents who has the right to either push/pop (or
* catch up). As soon as we recognize that the side that has locked has carried
* out what it scheduled to do, we can release the lock, but only after any
* possible animations are completed.
*
* *IF* a scheduled operation results in a push/pop (not all do), then we can
* only release the lock after the push/pop animation is complete because
* UIKit. `didMoveToNavigationController` is invoked when the view is done
* pushing/popping/animating. Native swipe-to-go-back interactions can be
* aborted, however, and you'll never see that method invoked. So just to cover
* that case, we also put an animation complete hook in
* `animateAlongsideTransition` to make sure we free the lock, in case the
* scheduled native push/pop never actually happened.
*
* For JavaScript:
* - When we see that JavaScript has "caught up" to `UIKit`, and no pushes/pops
* were needed, we can release the lock.
* - When we see that JavaScript requires *some* push/pop, it's not yet done
* carrying out what it scheduled to do. Just like with `UIKit` push/pops, we
* still have to wait for it to be done animating
* (`didMoveToNavigationController` is a suitable hook).
*
*/
@implementation RCTNavigationController
/**
* @param callback Callback that is invoked when a "scroll" interaction begins
* so that `RCTNavigator` can notify `JavaScript`.
*/
- (instancetype)initWithScrollCallback:(dispatch_block_t)callback
{
if ((self = [super initWithNibName:nil bundle:nil])) {
_scrollCallback = callback;
}
return self;
}
/**
* Invoked when either a navigation item has been popped off, or when a
* swipe-back gesture has began. The swipe-back gesture doesn't respect the
* return value of this method. The back button does. That's why we have to
* completely disable the gesture recognizer for swipe-back while JS has the
* lock.
*/
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
#if !TARGET_OS_TV
if (self.interactivePopGestureRecognizer.state == UIGestureRecognizerStateBegan) {
if (self.navigationLock == RCTNavigationLockNone) {
self.navigationLock = RCTNavigationLockNative;
if (_scrollCallback) {
_scrollCallback();
}
} else if (self.navigationLock == RCTNavigationLockJavaScript) {
// This should never happen because we disable/enable the gesture
// recognizer when we lock the navigation.
RCTAssert(NO, @"Should never receive gesture start while JS locks navigator");
}
} else
#endif //TARGET_OS_TV
{
if (self.navigationLock == RCTNavigationLockNone) {
// Must be coming from native interaction, lock it - it will be unlocked
// in `didMoveToNavigationController`
self.navigationLock = RCTNavigationLockNative;
if (_scrollCallback) {
_scrollCallback();
}
} else if (self.navigationLock == RCTNavigationLockJavaScript) {
// This should only occur when JS has the lock, and
// - JS is driving the pop
// - Or the back button was pressed
// TODO: We actually want to disable the backbutton while JS has the
// lock, but it's not so easy. Even returning `NO` wont' work because it
// will also block JS driven pops. We simply need to disallow a standard
// back button, and instead use a custom one that tells JS to pop to
// length (`currentReactCount` - 1).
return [super navigationBar:navigationBar shouldPopItem:item];
}
}
return [super navigationBar:navigationBar shouldPopItem:item];
}
@end
@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;
@property (nonatomic, readwrite, strong) RCTNavigationController *navigationController;
/**
* Display link is used to get high frequency sample rate during
* interaction/animation of view controller push/pop.
*
* - The run loop retains the displayLink.
* - `displayLink` retains its target.
* - We use `invalidate` to remove the `RCTNavigator`'s reference to the
* `displayLink` and remove the `displayLink` from the run loop.
*
*
* `displayLink`:
* --------------
*
* - Even though we could implement the `displayLink` cleanup without the
* `invalidate` hook by adding and removing it from the run loop at the
* right times (begin/end animation), we need to account for the possibility
* that the view itself is destroyed mid-interaction. So we always keep it
* added to the run loop, but start/stop it with interactions/animations. We
* remove it from the run loop when the view will be destroyed by React.
*
* +----------+ +--------------+
* | run loop o----strong--->| displayLink |
* +----------+ +--o-----------+
* | ^
* | |
* strong strong
* | |
* v |
* +---------o---+
* | RCTNavigator |
* +-------------+
*
* `dummyView`:
* ------------
* There's no easy way to get a callback that fires when the position of a
* navigation item changes. The actual layers that are moved around during the
* navigation transition are private. Our only hope is to use
* `animateAlongsideTransition`, to set a dummy view's position to transition
* anywhere from -1.0 to 1.0. We later set up a `CADisplayLink` to poll the
* `presentationLayer` of that dummy view and report the value as a "progress"
* percentage.
*
* It was critical that we added the dummy view as a subview of the
* transitionCoordinator's `containerView`, otherwise the animations would not
* work correctly when reversing the gesture direction etc. This seems to be
* undocumented behavior/requirement.
*
*/
@property (nonatomic, readonly, assign) CGFloat mostRecentProgress;
@property (nonatomic, readonly, strong) NSTimer *runTimer;
@property (nonatomic, readonly, assign) NSInteger currentlyTransitioningFrom;
@property (nonatomic, readonly, assign) NSInteger currentlyTransitioningTo;
// Dummy view that we make animate with the same curve/interaction as the
// navigation animation/interaction.
@property (nonatomic, readonly, strong) UIView *dummyView;
@end
@implementation RCTNavigator
{
__weak RCTBridge *_bridge;
NSInteger _numberOfViewControllerMovesToIgnore;
}
@synthesize paused = _paused;
@synthesize pauseCallback = _pauseCallback;
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
RCTAssertParam(bridge);
if ((self = [super initWithFrame:CGRectZero])) {
_paused = YES;
_bridge = bridge;
_mostRecentProgress = kNeverProgressed;
_dummyView = [[UIView alloc] initWithFrame:CGRectZero];
_previousRequestedTopOfStack = kNeverRequested; // So that we initialize with a push.
_previousViews = @[];
__weak RCTNavigator *weakSelf = self;
_navigationController = [[RCTNavigationController alloc] initWithScrollCallback:^{
[weakSelf dispatchFakeScrollEvent];
}];
_navigationController.delegate = self;
RCTAssert([self requestSchedulingJavaScriptNavigation], @"Could not acquire JS navigation lock on init");
[self addSubview:_navigationController.view];
[_navigationController.view addSubview:_dummyView];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)didUpdateFrame:(__unused RCTFrameUpdate *)update
{
if (_currentlyTransitioningFrom != _currentlyTransitioningTo) {
UIView *topView = _dummyView;
id presentationLayer = [topView.layer presentationLayer];
CGRect frame = [presentationLayer frame];
CGFloat nextProgress = ABS(frame.origin.x);
// Don't want to spam the bridge, when the user holds their finger still mid-navigation.
if (nextProgress == _mostRecentProgress) {
return;
}
_mostRecentProgress = nextProgress;
if (_onNavigationProgress) {
_onNavigationProgress(@{
@"fromIndex": @(_currentlyTransitioningFrom),
@"toIndex": @(_currentlyTransitioningTo),
@"progress": @(nextProgress),
});
}
}
}
- (void)setPaused:(BOOL)paused
{
if (_paused != paused) {
_paused = paused;
if (_pauseCallback) {
_pauseCallback();
}
}
}
- (void)setInteractivePopGestureEnabled:(BOOL)interactivePopGestureEnabled
{
#if !TARGET_OS_TV
_interactivePopGestureEnabled = interactivePopGestureEnabled;
_navigationController.interactivePopGestureRecognizer.delegate = self;
_navigationController.interactivePopGestureRecognizer.enabled = interactivePopGestureEnabled;
_popGestureState = interactivePopGestureEnabled ? RCTPopGestureStateEnabled : RCTPopGestureStateDisabled;
#endif
}
- (void)dealloc
{
#if !TARGET_OS_TV
if (_navigationController.interactivePopGestureRecognizer.delegate == self) {
_navigationController.interactivePopGestureRecognizer.delegate = nil;
}
#endif
_navigationController.delegate = nil;
[_navigationController removeFromParentViewController];
}
- (UIViewController *)reactViewController
{
return _navigationController;
}
- (BOOL)gestureRecognizerShouldBegin:(__unused 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
* locks aside from the animation complete hook.
*/
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(__unused UIViewController *)viewController
animated:(__unused BOOL)animated
{
id<UIViewControllerTransitionCoordinator> tc =
navigationController.topViewController.transitionCoordinator;
__weak RCTNavigator *weakSelf = self;
[tc.containerView addSubview: _dummyView];
[tc animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
RCTWrapperViewController *fromController =
(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 != self->_navigationController ||
toController.navigationController != self->_navigationController) {
return;
}
NSUInteger indexOfFrom = [self.reactSubviews indexOfObject:fromController.navItem];
NSUInteger indexOfTo = [self.reactSubviews indexOfObject:toController.navItem];
CGFloat destination = indexOfFrom < indexOfTo ? 1.0 : -1.0;
self->_dummyView.frame = (CGRect){{destination, 0}, CGSizeZero};
self->_currentlyTransitioningFrom = indexOfFrom;
self->_currentlyTransitioningTo = indexOfTo;
self.paused = NO;
}
completion:^(__unused id<UIViewControllerTransitionCoordinatorContext> context) {
[weakSelf freeLock];
self->_currentlyTransitioningFrom = 0;
self->_currentlyTransitioningTo = 0;
self->_dummyView.frame = CGRectZero;
self.paused = YES;
// Reset the parallel position tracker
}];
}
- (BOOL)requestSchedulingJavaScriptNavigation
{
if (_navigationController.navigationLock == RCTNavigationLockNone) {
_navigationController.navigationLock = RCTNavigationLockJavaScript;
#if !TARGET_OS_TV
_navigationController.interactivePopGestureRecognizer.enabled = NO;
#endif
return YES;
}
return NO;
}
- (void)freeLock
{
_navigationController.navigationLock = RCTNavigationLockNone;
// 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
#if !TARGET_OS_TV
_navigationController.interactivePopGestureRecognizer.enabled = self.popGestureState != RCTPopGestureStateDisabled;
#endif
}
/**
* A React subview can be inserted/removed at any time, however if the
* `requestedTopOfStack` changes, there had better be enough subviews present
* to satisfy the push/pop.
*/
- (void)insertReactSubview:(RCTNavItem *)view atIndex:(NSInteger)atIndex
{
RCTAssert([view isKindOfClass:[RCTNavItem class]], @"RCTNavigator only accepts RCTNavItem subviews");
RCTAssert(
_navigationController.navigationLock == RCTNavigationLockJavaScript,
@"Cannot change subviews from JS without first locking."
);
[super insertReactSubview:view atIndex:atIndex];
}
- (void)didUpdateReactSubviews
{
// Do nothing, as subviews are managed by `uiManagerDidPerformMounting`
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self reactAddControllerToClosestParent:_navigationController];
_navigationController.view.frame = self.bounds;
}
- (void)removeReactSubview:(RCTNavItem *)subview
{
if (self.reactSubviews.count <= 0 || subview == self.reactSubviews[0]) {
RCTLogError(@"Attempting to remove invalid RCT subview of RCTNavigator");
return;
}
[super removeReactSubview:subview];
}
- (void)handleTopOfStackChanged
{
if (_onNavigationComplete) {
_onNavigationComplete(@{
@"stackLength":@(_navigationController.viewControllers.count)
});
}
}
- (void)dispatchFakeScrollEvent
{
[_bridge.eventDispatcher sendFakeScrollEvent:self.reactTag];
}
/**
* Must be overridden because UIKit removes the view's superview when used
* as a navigator - it's considered outside the view hierarchy.
*/
- (UIView *)reactSuperview
{
RCTAssert(!_bridge.isValid || self.superview != nil, @"put reactNavSuperviewLink back");
UIView *superview = [super reactSuperview];
return superview ?: self.reactNavSuperviewLink;
}
- (void)uiManagerDidPerformMounting
{
// we can't hook up the VC hierarchy in 'init' because the subviews aren't
// hooked up yet, so we do it on demand here
[self reactAddControllerToClosestParent:_navigationController];
NSUInteger viewControllerCount = _navigationController.viewControllers.count;
// The "react count" is the count of views that are visible on the navigation
// stack. There may be more beyond this - that aren't visible, and may be
// deleted/purged soon.
NSUInteger previousReactCount =
_previousRequestedTopOfStack == kNeverRequested ? 0 : _previousRequestedTopOfStack + 1;
NSUInteger currentReactCount = _requestedTopOfStack + 1;
BOOL jsGettingAhead =
// ----- previously caught up ------ ------ no longer caught up -------
viewControllerCount == previousReactCount && currentReactCount != viewControllerCount;
BOOL jsCatchingUp =
// --- previously not caught up ---- --------- now caught up ----------
viewControllerCount != previousReactCount && currentReactCount == viewControllerCount;
BOOL jsMakingNoProgressButNeedsToCatchUp =
// --- previously not caught up ---- ------- still the same -----------
viewControllerCount != previousReactCount && currentReactCount == previousReactCount;
BOOL jsMakingNoProgressAndDoesntNeedTo =
// --- previously caught up -------- ------- still caught up ----------
viewControllerCount == previousReactCount && currentReactCount == previousReactCount;
BOOL jsGettingtooSlow =
// --- previously not caught up -------- ------- no longer caught up ----------
viewControllerCount < previousReactCount && currentReactCount < previousReactCount;
BOOL reactPushOne = jsGettingAhead && currentReactCount == previousReactCount + 1;
BOOL reactPopN = jsGettingAhead && currentReactCount < previousReactCount;
// We can actually recover from this situation, but it would be nice to know
// when this error happens. This simply means that JS hasn't caught up to a
// back navigation before progressing. It's likely a bug in the JS code that
// catches up/schedules navigations.
if (!(jsGettingAhead ||
jsCatchingUp ||
jsMakingNoProgressButNeedsToCatchUp ||
jsMakingNoProgressAndDoesntNeedTo ||
jsGettingtooSlow)) {
RCTLogError(@"JS has only made partial progress to catch up to UIKit");
}
if (currentReactCount > self.reactSubviews.count) {
RCTLogError(@"Cannot adjust current top of stack beyond available views");
}
// Views before the previous React count must not have changed. Views greater than previousReactCount
// up to currentReactCount may have changed.
for (NSUInteger i = 0; i < MIN(self.reactSubviews.count, MIN(_previousViews.count, previousReactCount)); i++) {
if (self.reactSubviews[i] != _previousViews[i]) {
RCTLogError(@"current view should equal previous view");
}
}
if (currentReactCount < 1) {
RCTLogError(@"should be at least one current view");
}
if (jsGettingAhead) {
if (reactPushOne) {
UIView *lastView = self.reactSubviews.lastObject;
RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView];
vc.navigationListener = self;
_numberOfViewControllerMovesToIgnore = 1;
[_navigationController pushViewController:vc animated:(currentReactCount > 1)];
} else if (reactPopN) {
UIViewController *viewControllerToPopTo = _navigationController.viewControllers[(currentReactCount - 1)];
_numberOfViewControllerMovesToIgnore = viewControllerCount - currentReactCount;
[_navigationController popToViewController:viewControllerToPopTo animated:YES];
} else {
RCTLogError(@"Pushing or popping more than one view at a time from JS");
}
} else if (jsCatchingUp) {
[self freeLock]; // Nothing to push/pop
} else {
// Else, JS making no progress, could have been unrelated to anything nav.
return;
}
// Only make a copy of the subviews whose validity we expect to be able to check (in the loop, above),
// otherwise we would unnecessarily retain a reference to view(s) no longer on the React navigation stack:
NSUInteger expectedCount = MIN(currentReactCount, self.reactSubviews.count);
_previousViews = [[self.reactSubviews subarrayWithRange: NSMakeRange(0, expectedCount)] copy];
_previousRequestedTopOfStack = _requestedTopOfStack;
}
// 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
{
if (self.superview == nil) {
// If superview is nil, then a JS reload (Cmd+R) happened
// while a push/pop is in progress.
return;
}
RCTAssert(
(navigationController == nil || [_navigationController.viewControllers containsObject:wrapperViewController]),
@"if navigation controller is not nil, it should contain the wrapper view controller"
);
RCTAssert(_navigationController.navigationLock == RCTNavigationLockJavaScript ||
_numberOfViewControllerMovesToIgnore == 0,
@"If JS doesn't have the lock there should never be any pending transitions");
/**
* When JS has the lock we want to keep track of when the request completes
* the pending transition count hitting 0 signifies this, and should always
* remain at 0 when JS does not have the lock
*/
if (_numberOfViewControllerMovesToIgnore > 0) {
_numberOfViewControllerMovesToIgnore -= 1;
}
if (_numberOfViewControllerMovesToIgnore == 0) {
[self handleTopOfStackChanged];
[self freeLock];
}
}
@end

View File

@ -1,12 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <React/RCTViewManager.h>
@interface RCTNavigatorManager : RCTViewManager
@end

View File

@ -1,83 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RCTNavigatorManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTNavigator.h"
#import "RCTUIManager.h"
#import "RCTUIManagerObserverCoordinator.h"
#import "UIView+React.h"
@interface RCTNavigatorManager () <RCTUIManagerObserver>
@end
@implementation RCTNavigatorManager
{
// The main thread only.
NSHashTable<RCTNavigator *> *_viewRegistry;
}
- (void)setBridge:(RCTBridge *)bridge
{
[super setBridge:bridge];
[self.bridge.uiManager.observerCoordinator addObserver:self];
}
- (void)invalidate
{
[self.bridge.uiManager.observerCoordinator removeObserver:self];
}
RCT_EXPORT_MODULE()
- (UIView *)view
{
if (!_viewRegistry) {
_viewRegistry = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
}
RCTNavigator *view = [[RCTNavigator alloc] initWithBridge:self.bridge];
[_viewRegistry addObject:view];
return 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)
RCT_EXPORT_METHOD(requestSchedulingJavaScriptNavigation:(nonnull NSNumber *)reactTag
callback:(RCTResponseSenderBlock)callback)
{
[self.bridge.uiManager addUIBlock:
^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, RCTNavigator *> *viewRegistry){
RCTNavigator *navigator = viewRegistry[reactTag];
if ([navigator isKindOfClass:[RCTNavigator class]]) {
BOOL wasAcquired = [navigator requestSchedulingJavaScriptNavigation];
callback(@[@(wasAcquired)]);
} else {
RCTLogError(@"Cannot set lock: %@ (tag #%@) is not an RCTNavigator", navigator, reactTag);
}
}];
}
#pragma mark - RCTUIManagerObserver
- (void)uiManagerDidPerformMounting:(__unused RCTUIManager *)manager
{
RCTExecuteOnMainQueue(^{
for (RCTNavigator *view in self->_viewRegistry) {
[view uiManagerDidPerformMounting];
}
});
}
@end

View File

@ -7,22 +7,10 @@
#import <UIKit/UIKit.h>
@class RCTNavItem;
@class RCTWrapperViewController;
@protocol RCTWrapperViewControllerNavigationListener <NSObject>
- (void)wrapperViewController:(RCTWrapperViewController *)wrapperViewController
didMoveToNavigationController:(UINavigationController *)navigationController;
@end
@interface RCTWrapperViewController : UIViewController
- (instancetype)initWithContentView:(UIView *)contentView NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNavItem:(RCTNavItem *)navItem;
@property (nonatomic, weak) id<RCTWrapperViewControllerNavigationListener> navigationListener;
@property (nonatomic, strong) RCTNavItem *navItem;
@end

View File

@ -10,7 +10,6 @@
#import <UIKit/UIScrollView.h>
#import "RCTEventDispatcher.h"
#import "RCTNavItem.h"
#import "RCTUtils.h"
#import "UIView+React.h"
#import "RCTAutoInsetsProtocol.h"
@ -38,14 +37,6 @@
return self;
}
- (instancetype)initWithNavItem:(RCTNavItem *)navItem
{
if ((self = [self initWithContentView:navItem])) {
_navItem = navItem;
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithNibName:(NSString *)nn bundle:(NSBundle *)nb)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
@ -83,55 +74,6 @@ static BOOL RCTFindScrollViewAndRefreshContentInsetInView(UIView *view)
}
}
static UIView *RCTFindNavBarShadowViewInView(UIView *view)
{
if ([view isKindOfClass:[UIImageView class]] && view.bounds.size.height <= 1) {
return view;
}
for (UIView *subview in view.subviews) {
UIView *shadowView = RCTFindNavBarShadowViewInView(subview);
if (shadowView) {
return shadowView;
}
}
return nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// TODO: find a way to make this less-tightly coupled to navigation controller
if ([self.parentViewController isKindOfClass:[UINavigationController class]])
{
[self.navigationController
setNavigationBarHidden:_navItem.navigationBarHidden
animated:animated];
UINavigationBar *bar = self.navigationController.navigationBar;
bar.barTintColor = _navItem.barTintColor;
bar.tintColor = _navItem.tintColor;
bar.translucent = _navItem.translucent;
#if !TARGET_OS_TV
bar.barStyle = _navItem.barStyle;
#endif
bar.titleTextAttributes = _navItem.titleTextColor ? @{
NSForegroundColorAttributeName: _navItem.titleTextColor
} : nil;
RCTFindNavBarShadowViewInView(bar).hidden = _navItem.shadowHidden;
UINavigationItem *item = self.navigationItem;
item.title = _navItem.title;
item.titleView = _navItem.titleImageView;
#if !TARGET_OS_TV
item.backBarButtonItem = _navItem.backButtonItem;
#endif //TARGET_OS_TV
item.leftBarButtonItem = _navItem.leftButtonItem;
item.rightBarButtonItem = _navItem.rightButtonItem;
}
}
- (void)loadView
{
// Add a wrapper so that the wrapper view managed by the
@ -142,16 +84,4 @@ static UIView *RCTFindNavBarShadowViewInView(UIView *view)
self.view = _wrapperView;
}
- (void)didMoveToParentViewController:(UIViewController *)parent
{
// There's no clear setter for navigation controllers, but did move to parent
// view controller provides the desired effect. This is called after a pop
// finishes, be it a swipe to go back or a standard tap on the back button
[super didMoveToParentViewController:parent];
if (parent == nil || [parent isKindOfClass:[UINavigationController class]]) {
[self.navigationListener wrapperViewController:self
didMoveToNavigationController:(UINavigationController *)parent];
}
}
@end