mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-02 09:18:38 +08:00
Compare commits
16 Commits
2.0.0-beta
...
2.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f8b460549 | ||
|
|
b52aea3fd2 | ||
|
|
4e72ad050b | ||
|
|
0dd03e2c53 | ||
|
|
e38c5bfdc2 | ||
|
|
cdbf4e463f | ||
|
|
4e8d13dc72 | ||
|
|
d3b6bea594 | ||
|
|
b29e634e26 | ||
|
|
f2caf02d8c | ||
|
|
c8845cbb6a | ||
|
|
9bf2edd405 | ||
|
|
748cdc6fba | ||
|
|
89cf0b7e52 | ||
|
|
db4733ad05 | ||
|
|
8952e698d2 |
@@ -67,9 +67,9 @@ yarn add react-native-screens
|
||||
2. Open your App.js file and add the following snippet somewhere near the top of the file (e.g. right after import statements):
|
||||
|
||||
```js
|
||||
import { useScreens } from 'react-native-screens';
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
|
||||
useScreens();
|
||||
enableScreens();
|
||||
```
|
||||
|
||||
3. That's all 🎉 – enjoy faster navigation in your Expo app. Keep in mind screens are in pretty early phase so please report if you discover some issues.
|
||||
|
||||
@@ -218,6 +218,12 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
// if there are pending transactions and this view is about to get detached we need to perform
|
||||
// them here as otherwise fragment manager will crash because it won't be able to find container
|
||||
// view.
|
||||
if (mFragmentManager != null && !mFragmentManager.isDestroyed()) {
|
||||
mFragmentManager.executePendingTransactions();
|
||||
}
|
||||
super.onDetachedFromWindow();
|
||||
mIsAttached = false;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#import <React/RCTShadowView.h>
|
||||
#import <React/RCTTouchHandler.h>
|
||||
|
||||
@interface RNSScreenView () <UIAdaptivePresentationControllerDelegate>
|
||||
@interface RNSScreenView () <UIAdaptivePresentationControllerDelegate, RCTInvalidating>
|
||||
@end
|
||||
|
||||
@implementation RNSScreenView {
|
||||
@@ -46,6 +46,11 @@
|
||||
// subviews
|
||||
}
|
||||
|
||||
- (UIViewController *)reactViewController
|
||||
{
|
||||
return _controller;
|
||||
}
|
||||
|
||||
- (void)updateBounds
|
||||
{
|
||||
[_bridge.uiManager setSize:self.bounds.size forView:self];
|
||||
@@ -67,7 +72,6 @@
|
||||
|
||||
- (void)setStackPresentation:(RNSScreenStackPresentation)stackPresentation
|
||||
{
|
||||
_stackPresentation = stackPresentation;
|
||||
switch (stackPresentation) {
|
||||
case RNSScreenStackPresentationModal:
|
||||
#ifdef __IPHONE_13_0
|
||||
@@ -95,11 +99,23 @@
|
||||
case RNSScreenStackPresentationContainedTransparentModal:
|
||||
_controller.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
||||
break;
|
||||
case RNSScreenStackPresentationPush:
|
||||
// ignored, we only need to keep in mind not to set presentation delegate
|
||||
break;
|
||||
}
|
||||
// `modalPresentationStyle` must be set before accessing `presentationController`
|
||||
// otherwise a default controller will be created and cannot be changed after.
|
||||
// Documented here: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621426-presentationcontroller?language=objc
|
||||
_controller.presentationController.delegate = self;
|
||||
// There is a bug in UIKit which causes retain loop when presentationController is accessed for a
|
||||
// controller that is not going to be presented modally. We therefore need to avoid setting the
|
||||
// delegate for screens presented using push. This also means that when controller is updated from
|
||||
// modal to push type, this may cause memory leak, we warn about that as well.
|
||||
if (stackPresentation != RNSScreenStackPresentationPush) {
|
||||
// `modalPresentationStyle` must be set before accessing `presentationController`
|
||||
// otherwise a default controller will be created and cannot be changed after.
|
||||
// Documented here: https://developer.apple.com/documentation/uikit/uiviewcontroller/1621426-presentationcontroller?language=objc
|
||||
_controller.presentationController.delegate = self;
|
||||
} else if (_stackPresentation != RNSScreenStackPresentationPush) {
|
||||
RCTLogError(@"Screen presentation updated from modal to push, this may likely result in a screen object leakage. If you need to change presentation style create a new screen object instead");
|
||||
}
|
||||
_stackPresentation = stackPresentation;
|
||||
}
|
||||
|
||||
- (void)setStackAnimation:(RNSScreenStackAnimation)stackAnimation
|
||||
@@ -120,6 +136,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setGestureEnabled:(BOOL)gestureEnabled
|
||||
{
|
||||
#ifdef __IPHONE_13_0
|
||||
if (@available(iOS 13.0, *)) {
|
||||
_controller.modalInPresentation = !gestureEnabled;
|
||||
}
|
||||
#endif
|
||||
|
||||
_gestureEnabled = gestureEnabled;
|
||||
}
|
||||
|
||||
- (UIView *)reactSuperview
|
||||
{
|
||||
return _reactSuperview;
|
||||
@@ -215,10 +242,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
_controller = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RNSScreen {
|
||||
__weak UIView *_view;
|
||||
__weak id _previousFirstResponder;
|
||||
CGRect _lastViewFrame;
|
||||
}
|
||||
@@ -226,7 +257,7 @@
|
||||
- (instancetype)initWithView:(UIView *)view
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_view = view;
|
||||
self.view = view;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -237,7 +268,7 @@
|
||||
|
||||
if (!CGRectEqualToRect(_lastViewFrame, self.view.frame)) {
|
||||
_lastViewFrame = self.view.frame;
|
||||
[((RNSScreenView *)self.view) updateBounds];
|
||||
[((RNSScreenView *)self.viewIfLoaded) updateBounds];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,6 +288,7 @@
|
||||
|
||||
- (void)willMoveToParentViewController:(UIViewController *)parent
|
||||
{
|
||||
[super willMoveToParentViewController:parent];
|
||||
if (parent == nil) {
|
||||
id responder = [self findFirstResponder:self.view];
|
||||
if (responder != nil) {
|
||||
@@ -271,8 +303,6 @@
|
||||
if (self.parentViewController == nil && self.presentingViewController == nil) {
|
||||
// screen dismissed, send event
|
||||
[((RNSScreenView *)self.view) notifyDismissed];
|
||||
_view = self.view;
|
||||
self.view = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,14 +318,6 @@
|
||||
_previousFirstResponder = nil;
|
||||
}
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
if (_view != nil) {
|
||||
self.view = _view;
|
||||
_view = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RNSScreenManager
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
@end
|
||||
|
||||
@interface RNSScreenContainerView ()
|
||||
@interface RNSScreenContainerView () <RCTInvalidating>
|
||||
|
||||
@property (nonatomic, retain) UIViewController *controller;
|
||||
@property (nonatomic, retain) NSMutableSet<RNSScreenView *> *activeScreens;
|
||||
@@ -68,6 +68,11 @@
|
||||
return _reactSubviews;
|
||||
}
|
||||
|
||||
- (UIViewController *)reactViewController
|
||||
{
|
||||
return _controller;
|
||||
}
|
||||
|
||||
- (void)detachScreen:(RNSScreenView *)screen
|
||||
{
|
||||
[screen.controller willMoveToParentViewController:nil];
|
||||
@@ -79,6 +84,9 @@
|
||||
- (void)attachScreen:(RNSScreenView *)screen
|
||||
{
|
||||
[_controller addChildViewController:screen.controller];
|
||||
// the frame is already set at this moment because we adjust it in insertReactSubview. We don't
|
||||
// want to update it here as react-driven animations may already run and hence changing the frame
|
||||
// would result in visual glitches
|
||||
[_controller.view addSubview:screen.controller.view];
|
||||
[screen.controller didMoveToParentViewController:_controller];
|
||||
[_activeScreens addObject:screen];
|
||||
@@ -155,10 +163,22 @@
|
||||
[self markChildUpdated];
|
||||
}
|
||||
|
||||
- (void)didMoveToWindow
|
||||
{
|
||||
if (self.window) {
|
||||
[self reactAddControllerToClosestParent:_controller];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[_controller willMoveToParentViewController:nil];
|
||||
[_controller removeFromParentViewController];
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
[self reactAddControllerToClosestParent:_controller];
|
||||
_controller.view.frame = self.bounds;
|
||||
for (RNSScreenView *subview in _reactSubviews) {
|
||||
subview.frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
_dismissedScreens = [NSMutableSet new];
|
||||
_controller = [[UINavigationController alloc] init];
|
||||
_controller.delegate = self;
|
||||
[self addSubview:_controller.view];
|
||||
_controller.interactivePopGestureRecognizer.delegate = self;
|
||||
|
||||
// we have to initialize viewControllers with a non empty array for
|
||||
@@ -49,6 +48,11 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UIViewController *)reactViewController
|
||||
{
|
||||
return _controller;
|
||||
}
|
||||
|
||||
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
|
||||
{
|
||||
UIView *view = viewController.view;
|
||||
@@ -174,10 +178,35 @@
|
||||
{
|
||||
[super didMoveToWindow];
|
||||
if (self.window) {
|
||||
// when stack is added to a window we try to update push and modal view controllers. It is
|
||||
// because modal operations are blocked by UIKit when parent VC is not mounted, so we need
|
||||
// to redo them when the stack is attached.
|
||||
// when stack is attached to a window we do two things:
|
||||
// 1) we run updateContainer – we do this because we want push view controllers to be installed
|
||||
// before the VC is mounted. If we do that after it is added to parent the push updates operations
|
||||
// are going to be blocked by UIKit.
|
||||
// 2) we add navigation VS to parent – this is needed for the VC lifecycle events to be dispatched
|
||||
// properly
|
||||
// 3) we again call updateContainer – this time we do this to open modal controllers. Modals
|
||||
// won't open in (1) because they require navigator to be added to parent. We handle that case
|
||||
// gracefully in setModalViewControllers and can retry opening at any point.
|
||||
[self updateContainer];
|
||||
[self reactAddControllerToClosestParent:_controller];
|
||||
[self updateContainer];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reactAddControllerToClosestParent:(UIViewController *)controller
|
||||
{
|
||||
if (!controller.parentViewController) {
|
||||
UIView *parentView = (UIView *)self.reactSuperview;
|
||||
while (parentView) {
|
||||
if (parentView.reactViewController) {
|
||||
[parentView.reactViewController addChildViewController:controller];
|
||||
[self addSubview:controller.view];
|
||||
[controller didMoveToParentViewController:parentView.reactViewController];
|
||||
break;
|
||||
}
|
||||
parentView = (UIView *)parentView.reactSuperview;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +272,8 @@
|
||||
[weakSelf.presentedModals
|
||||
removeObjectsInRange:NSMakeRange(changeRootIndex, oldCount - changeRootIndex)];
|
||||
}
|
||||
if (changeRootController.view.window == nil || changeRootIndex >= controllers.count) {
|
||||
BOOL isAttached = changeRootController.parentViewController != nil || changeRootController.presentingViewController != nil;
|
||||
if (!isAttached || changeRootIndex >= controllers.count) {
|
||||
// if change controller view is not attached, presenting modals will silently fail on iOS.
|
||||
// In such a case we trigger controllers update from didMoveToWindow.
|
||||
// We also don't run any present transitions if changeRootIndex is greater or equal to the size
|
||||
@@ -299,7 +329,7 @@
|
||||
// controller is still there
|
||||
BOOL firstTimePush = ![lastTop isKindOfClass:[RNSScreen class]];
|
||||
|
||||
BOOL shouldAnimate = !firstTimePush && ((RNSScreenView *) lastTop.view).stackAnimation != RNSScreenStackAnimationNone && !_controller.presentedViewController;
|
||||
BOOL shouldAnimate = !firstTimePush && ((RNSScreenView *) lastTop.view).stackAnimation != RNSScreenStackAnimationNone;
|
||||
|
||||
if (firstTimePush) {
|
||||
// nothing pushed yet
|
||||
@@ -337,7 +367,7 @@
|
||||
NSMutableArray<UIViewController *> *pushControllers = [NSMutableArray new];
|
||||
NSMutableArray<UIViewController *> *modalControllers = [NSMutableArray new];
|
||||
for (RNSScreenView *screen in _reactSubviews) {
|
||||
if (![_dismissedScreens containsObject:screen]) {
|
||||
if (![_dismissedScreens containsObject:screen] && screen.controller != nil) {
|
||||
if (pushControllers.count == 0) {
|
||||
// first screen on the list needs to be places as "push controller"
|
||||
[pushControllers addObject:screen.controller];
|
||||
@@ -358,7 +388,6 @@
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
[self reactAddControllerToClosestParent:_controller];
|
||||
_controller.view.frame = self.bounds;
|
||||
}
|
||||
|
||||
@@ -368,6 +397,8 @@
|
||||
[controller dismissViewControllerAnimated:NO completion:nil];
|
||||
}
|
||||
[_presentedModals removeAllObjects];
|
||||
[_controller willMoveToParentViewController:nil];
|
||||
[_controller removeFromParentViewController];
|
||||
}
|
||||
|
||||
- (void)dismissOnReload
|
||||
|
||||
@@ -275,11 +275,7 @@
|
||||
}
|
||||
|
||||
[navctr setNavigationBarHidden:shouldHide animated:YES];
|
||||
#ifdef __IPHONE_13_0
|
||||
if (@available(iOS 13.0, *)) {
|
||||
vc.modalInPresentation = !config.screenView.gestureEnabled;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (shouldHide) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-native-screens",
|
||||
"version": "2.0.0-beta.8",
|
||||
"description": "First incomplete navigation solution for your react-native app.",
|
||||
"version": "2.0.0-beta.13",
|
||||
"description": "Native navigation primitives for your React Native app.",
|
||||
"scripts": {
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
"test": "npm run format && npm run lint && npm run test:unit",
|
||||
|
||||
Reference in New Issue
Block a user