mirror of
https://github.com/status-im/react-native.git
synced 2025-02-26 08:05:34 +00:00
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:
parent
9e63b5c5d2
commit
0df92afc1c
@ -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');
|
|
@ -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;
|
|
@ -66,9 +66,6 @@ const ReactNative = {
|
|||||||
get Modal() {
|
get Modal() {
|
||||||
return require('Modal');
|
return require('Modal');
|
||||||
},
|
},
|
||||||
get NavigatorIOS() {
|
|
||||||
return require('NavigatorIOS');
|
|
||||||
},
|
|
||||||
get Picker() {
|
get Picker() {
|
||||||
return require('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',
|
'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;
|
module.exports = ReactNative;
|
||||||
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -92,21 +92,6 @@ const ComponentExamples: Array<RNTesterExample> = [
|
|||||||
module: require('./MultiColumnExample'),
|
module: require('./MultiColumnExample'),
|
||||||
supportsTVOS: true,
|
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',
|
key: 'PickerExample',
|
||||||
module: require('./PickerExample'),
|
module: require('./PickerExample'),
|
||||||
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -7,22 +7,10 @@
|
|||||||
|
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
@class RCTNavItem;
|
|
||||||
@class RCTWrapperViewController;
|
@class RCTWrapperViewController;
|
||||||
|
|
||||||
@protocol RCTWrapperViewControllerNavigationListener <NSObject>
|
|
||||||
|
|
||||||
- (void)wrapperViewController:(RCTWrapperViewController *)wrapperViewController
|
|
||||||
didMoveToNavigationController:(UINavigationController *)navigationController;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface RCTWrapperViewController : UIViewController
|
@interface RCTWrapperViewController : UIViewController
|
||||||
|
|
||||||
- (instancetype)initWithContentView:(UIView *)contentView NS_DESIGNATED_INITIALIZER;
|
- (instancetype)initWithContentView:(UIView *)contentView NS_DESIGNATED_INITIALIZER;
|
||||||
- (instancetype)initWithNavItem:(RCTNavItem *)navItem;
|
|
||||||
|
|
||||||
@property (nonatomic, weak) id<RCTWrapperViewControllerNavigationListener> navigationListener;
|
|
||||||
@property (nonatomic, strong) RCTNavItem *navItem;
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
#import <UIKit/UIScrollView.h>
|
#import <UIKit/UIScrollView.h>
|
||||||
|
|
||||||
#import "RCTEventDispatcher.h"
|
#import "RCTEventDispatcher.h"
|
||||||
#import "RCTNavItem.h"
|
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
#import "UIView+React.h"
|
#import "UIView+React.h"
|
||||||
#import "RCTAutoInsetsProtocol.h"
|
#import "RCTAutoInsetsProtocol.h"
|
||||||
@ -38,14 +37,6 @@
|
|||||||
return self;
|
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)initWithNibName:(NSString *)nn bundle:(NSBundle *)nb)
|
||||||
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
|
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
|
- (void)loadView
|
||||||
{
|
{
|
||||||
// Add a wrapper so that the wrapper view managed by the
|
// Add a wrapper so that the wrapper view managed by the
|
||||||
@ -142,16 +84,4 @@ static UIView *RCTFindNavBarShadowViewInView(UIView *view)
|
|||||||
self.view = _wrapperView;
|
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
|
@end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user