Compare commits

..

7 Commits

Author SHA1 Message Date
Krzysztof Magiera
953b3b30db Bump version -> 2.0.0-beta.8 2020-02-19 15:37:10 +01:00
Grzegorz Mandziak
e50b7eae72 check object instance of before calling detachScreen (#344)
Fixes #343 

This change filters out non ScreenFragment instances when iterating through the list of installed fragments. The reason why other fragments can be added to the manager is that some libraries (e.g. glide) use fragment manager to store screen local data and attach to the screen lifecycle.
2020-02-19 15:30:58 +01:00
Krzysztof Magiera
0e00f49e69 Fix updating push native stack to avoid mid-transition related b… (#347)
This change addresses two problem. First was related to header subviews that were not laid out properly before we installed them in the header. This was causing occasional bugs where the header subviews were misplaced. The fix was to enforce container update run after layout is done (we enqueue update on ui thread directly from didUpdateReactSubviews).

The second problem was related to UINavController nlifecycle methods not triggering correctly in case when updates are being made to the nav controller which isn't mounted. Previously we fixed similar issue for modal controllers where because of the fact container wasn't mounted we couldn't run modal screen updates. With push controllers the problem is very similar however the VC do update just stop getting proper lifecycle updates and warning "Unbalanced calls to begin/end appearance transitions" is displayed. The fix was similar as in the modal case, that is, we wait until container is installed in window.
2020-02-19 15:22:55 +01:00
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
9 changed files with 91 additions and 27 deletions

View File

@@ -252,7 +252,9 @@ public class ScreenContainer<T extends ScreenFragment> extends ViewGroup {
if (!orphaned.isEmpty()) {
Object[] orphanedAry = orphaned.toArray();
for (int i = 0; i < orphanedAry.length; i++) {
detachScreen((ScreenFragment) orphanedAry[i]);
if (orphanedAry[i] instanceof ScreenFragment) {
detachScreen((ScreenFragment) orphanedAry[i]);
}
}
}

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>
@@ -17,11 +22,9 @@
@end
@implementation RNSScreenStackView {
BOOL _needUpdate;
UINavigationController *_controller;
NSMutableArray<RNSScreenView *> *_reactSubviews;
NSMutableSet<RNSScreenView *> *_dismissedScreens;
NSMutableArray<UIViewController *> *_presentedModals;
__weak RNSScreenStackManager *_manager;
}
@@ -34,7 +37,6 @@
_dismissedScreens = [NSMutableSet new];
_controller = [[UINavigationController alloc] init];
_controller.delegate = self;
_needUpdate = NO;
[self addSubview:_controller.view];
_controller.interactivePopGestureRecognizer.delegate = self;
@@ -160,8 +162,23 @@
- (void)didUpdateReactSubviews
{
// do nothing
[self updateContainer];
// we need to wait until children have their layout set. At this point they don't have the layout
// set yet, however the layout call is already enqueued on ui thread. Enqueuing update call on the
// ui queue will guarantee that the update will run after layout.
dispatch_async(dispatch_get_main_queue(), ^{
[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
@@ -197,26 +214,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 +275,6 @@
} else {
finish();
}
[_presentedModals setArray:controllers];
}
- (void)setPushViewControllers:(NSArray<UIViewController *> *)controllers
@@ -237,6 +284,12 @@
return;
}
// if view controller is not yet attached to window we skip updates now and run them when view
// is attached
if (self.window == nil) {
return;
}
UIViewController *top = controllers.lastObject;
UIViewController *lastTop = _controller.viewControllers.lastObject;

View File

@@ -1,6 +1,6 @@
{
"name": "react-native-screens",
"version": "2.0.0-beta.6",
"version": "2.0.0-beta.8",
"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 {