Compare commits

..

4 Commits

Author SHA1 Message Date
Krzysztof Magiera
26d8dc21d2 Bump version -> 2.0.0-beta.7 2020-02-18 16:49:11 +01:00
Krzysztof Magiera
3193e7da8f Update iOS native modals presenting logic to be more resilient (#342)
This update changes the logic of how we present modals. Previously presentedModals array could get out of sync with the actual app state because in case when parent VC is not mounted, presenting a modal would silently fail. In order to handle that we added additional update trigger from where the screen stack is mounted to a window. Second change that we added here addresses a problem of concurrent calls to update modals. As we cannot update presentedModals array synchrounously (all presentation callbacks are async even when no animation is involved), we need to block future updates unless the previous batch of updates is done. To do that we added a flag that prevents method re-entry. The flag is reset when all the presentation animations are over.
2020-02-18 16:48:43 +01:00
Krzysztof Magiera
cce8373a20 Android fix for invisible stack views after navigating back (#341)
After fragment library upgrade we observed a regression caused by the screens that we navigate back to being invisible. This turned out to be a problem with view restore mechanism that we don't rely on. On native android the detached view state is dumped and then view's visibility is change to GONE after screen animated away. However, since we don't rely on view state restore and instead just reuse the whole view object, when navigating back we'd move to a view with visibility set to GONE. This change workarounds this problem in the method responsible for recycling views where we reset visibility flag back to VISIBLE.
2020-02-18 15:40:56 +01:00
Matt Oakes
823d11e691 feat: Add in additional modal types for iOS (#318)
Allows you to choose additional modal presentation styles for iOS. It adds the ability to force a full screen modal or choose a "form sheet" style. This only affects iOS.
2020-02-17 21:12:57 +01:00
8 changed files with 76 additions and 23 deletions

View File

@@ -23,6 +23,10 @@ public class ScreenFragment extends Fragment {
((ViewGroup) parent).endViewTransition(view);
((ViewGroup) parent).removeView(view);
}
// view detached from fragment manager get their visibility changed to GONE after their state is
// dumped. Since we don't restore the state but want to reuse the view we need to change visibility
// back to VISIBLE in order for the fragment manager to animate in the view.
view.setVisibility(View.VISIBLE);
return view;
}

View File

@@ -31,11 +31,6 @@ public class ScreenStackFragment extends ScreenFragment {
mFragment = fragment;
}
@Override
public void startAnimation(Animation animation) {
super.startAnimation(animation);
}
@Override
protected void onAnimationEnd() {
super.onAnimationEnd();

View File

@@ -35,7 +35,7 @@ public class ScreenViewManager extends ViewGroupManager<Screen> {
public void setStackPresentation(Screen view, String presentation) {
if ("push".equals(presentation)) {
view.setStackPresentation(Screen.StackPresentation.PUSH);
} else if ("modal".equals(presentation) || "containedModal".equals(presentation)) {
} else if ("modal".equals(presentation) || "containedModal".equals(presentation) || "fullScreenModal".equals(presentation) || "formSheet".equals(presentation)) {
// at the moment Android implementation does not handle contained vs regular modals
view.setStackPresentation(Screen.StackPresentation.MODAL);
} else if ("transparentModal".equals(presentation) || "containedTransparentModal".equals((presentation))) {

View File

@@ -10,7 +10,9 @@ typedef NS_ENUM(NSInteger, RNSScreenStackPresentation) {
RNSScreenStackPresentationModal,
RNSScreenStackPresentationTransparentModal,
RNSScreenStackPresentationContainedModal,
RNSScreenStackPresentationContainedTransparentModal
RNSScreenStackPresentationContainedTransparentModal,
RNSScreenStackPresentationFullScreenModal,
RNSScreenStackPresentationFormSheet
};
typedef NS_ENUM(NSInteger, RNSScreenStackAnimation) {

View File

@@ -80,6 +80,12 @@
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
#endif
break;
case RNSScreenStackPresentationFullScreenModal:
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
break;
case RNSScreenStackPresentationFormSheet:
_controller.modalPresentationStyle = UIModalPresentationFormSheet;
break;
case RNSScreenStackPresentationTransparentModal:
_controller.modalPresentationStyle = UIModalPresentationOverFullScreen;
break;
@@ -315,6 +321,8 @@ RCT_EXPORT_VIEW_PROPERTY(onDismissed, RCTDirectEventBlock);
RCT_ENUM_CONVERTER(RNSScreenStackPresentation, (@{
@"push": @(RNSScreenStackPresentationPush),
@"modal": @(RNSScreenStackPresentationModal),
@"fullScreenModal": @(RNSScreenStackPresentationFullScreenModal),
@"formSheet": @(RNSScreenStackPresentationFormSheet),
@"containedModal": @(RNSScreenStackPresentationContainedModal),
@"transparentModal": @(RNSScreenStackPresentationTransparentModal),
@"containedTransparentModal": @(RNSScreenStackPresentationContainedTransparentModal)

View File

@@ -10,6 +10,11 @@
#import <React/RCTTouchHandler.h>
@interface RNSScreenStackView () <UINavigationControllerDelegate, UIAdaptivePresentationControllerDelegate, UIGestureRecognizerDelegate>
@property (nonatomic) NSMutableArray<UIViewController *> *presentedModals;
@property (nonatomic) BOOL updatingModals;
@property (nonatomic) BOOL scheduleModalsUpdate;
@end
@interface RNSScreenStackAnimator : NSObject <UIViewControllerAnimatedTransitioning>
@@ -21,7 +26,6 @@
UINavigationController *_controller;
NSMutableArray<RNSScreenView *> *_reactSubviews;
NSMutableSet<RNSScreenView *> *_dismissedScreens;
NSMutableArray<UIViewController *> *_presentedModals;
__weak RNSScreenStackManager *_manager;
}
@@ -160,10 +164,20 @@
- (void)didUpdateReactSubviews
{
// do nothing
[self updateContainer];
}
- (void)didMoveToWindow
{
[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.
[self updateContainer];
}
}
- (void)setModalViewControllers:(NSArray<UIViewController *> *)controllers
{
// when there is no change we return immediately. This check is important because sometime we may
@@ -197,26 +211,57 @@
}
}
// prevent re-entry
if (_updatingModals) {
_scheduleModalsUpdate = YES;
return;
}
_updatingModals = YES;
__weak RNSScreenStackView *weakSelf = self;
void (^dispatchFinishTransitioning)(void) = ^{
void (^afterTransitions)(void) = ^{
if (weakSelf.onFinishTransitioning) {
weakSelf.onFinishTransitioning(nil);
}
weakSelf.updatingModals = NO;
if (weakSelf.scheduleModalsUpdate) {
// if modals update was requested during setModalViewControllers we set scheduleModalsUpdate
// flag in order to perform updates at a later point. Here we are done with all modals
// transitions and check this flag again. If it was set, we reset the flag and execute updates.
weakSelf.scheduleModalsUpdate = NO;
[weakSelf updateContainer];
}
};
void (^finish)(void) = ^{
UIViewController *previous = changeRootController;
for (NSUInteger i = changeRootIndex; i < controllers.count; i++) {
UIViewController *next = controllers[i];
BOOL animate = (i == controllers.count - 1);
[previous presentViewController:next
animated:animate
completion:animate ? dispatchFinishTransitioning : nil];
previous = next;
NSUInteger oldCount = weakSelf.presentedModals.count;
if (changeRootIndex < oldCount) {
[weakSelf.presentedModals
removeObjectsInRange:NSMakeRange(changeRootIndex, oldCount - changeRootIndex)];
}
if (changeRootIndex >= controllers.count) {
dispatchFinishTransitioning();
if (changeRootController.view.window == nil || 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
// of new controllers array. This means that no new controllers should be presented.
afterTransitions();
return;
} else {
UIViewController *previous = changeRootController;
for (NSUInteger i = changeRootIndex; i < controllers.count; i++) {
UIViewController *next = controllers[i];
BOOL lastModal = (i == controllers.count - 1);
[previous presentViewController:next
animated:lastModal
completion:^{
[weakSelf.presentedModals addObject:next];
if (lastModal) {
afterTransitions();
};
}];
previous = next;
}
}
};
@@ -227,7 +272,6 @@
} else {
finish();
}
[_presentedModals setArray:controllers];
}
- (void)setPushViewControllers:(NSArray<UIViewController *> *)controllers

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-screens",
"version": "2.0.0-beta.6",
"version": "2.0.0-beta.7",
"description": "First incomplete navigation solution for your react-native app.",
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",

2
src/screens.d.ts vendored
View File

@@ -15,7 +15,7 @@ declare module 'react-native-screens' {
export function enableScreens(shouldEnableScreens?: boolean): void;
export function screensEnabled(): boolean;
export type StackPresentationTypes = 'push' | 'modal' | 'transparentModal';
export type StackPresentationTypes = 'push' | 'modal' | 'transparentModal' | 'fullScreenModal' | 'formSheet';
export type StackAnimationTypes = 'default' | 'fade' | 'flip' | 'none';
export interface ScreenProps extends ViewProps {