Compare commits

..

18 Commits

Author SHA1 Message Date
Krzysztof Magiera
fd7fcfbc5b Bump version -> 2.0.0-beta.2 2020-02-05 16:22:32 +01:00
Krzysztof Magiera
06b928f5c1 Fix showing sw back button on nested stack on Android. (#308)
This change fixes a problem with native stack on Android where we'd display a sw back button on screens that are nested stack navigatodespite the fact those screens were the initial screens in the whole container stack. In #306 we introuced a change that would allow for displaying sw back in nested stacks, however that change did not handle the case where screen is a root screen of a container that is nested and placed as an initial screen in another container.
2020-02-05 16:22:10 +01:00
Krzysztof Magiera
acb038e213 Upgrading to beta -> 2.0.0-beta.1 2020-02-04 22:21:50 +01:00
Krzysztof Magiera
22d4400b93 Render Android back button for root screens of nested stack navi… (#306)
This change allows for root screens of nested stack navigators on Android to display back button in the navigation bar. Navigating back is still possible using hw back because of the way hw back is handled by nested fragment containers. However, despite hw back functioning properly, before this change we would not allow for the soft back button to be rendered in the header. This change adds this possibility to keep software back consistent with the hw back button functionality. This behavior can still be disabled/adjusted using hideBackButton property of the header config component.
2020-02-04 22:20:41 +01:00
Krzysztof Magiera
f78c264419 Fix updating navigation bar for modal transitions. (#305)
This change fixes the problem when navigation bar would not get properly updated for modal transitions. The problem was due to the fact that for modal transitions the animateAlongsideTransition block was not getting trriggered. This was noticeable on iOS versions before 13, as starting with iOS 13 we can use NavigationItem to specify most of the nav bar customization. On older versions however it is required that for a lot of navbar settings we need to update them durion animation. This wasn't the case for navigators presented modally. In case we show a modal we can update nav bar without animation because it actually renders a completely new navigation bar which isn't shared.

On top of that this PR fixes the problem with bridge not being set for header subviews.
2020-02-04 16:45:55 +01:00
Krzysztof Magiera
65bd436cbe Bump version -> 2.0.0-alpha.34 2020-02-03 21:12:27 +01:00
Krzysztof Magiera
a0745762ba Remove references to ScreenStackHeaderTitleView. 2020-02-03 16:30:31 +01:00
Krzysztof Magiera
89658d9361 Delete Title subview and code for handling title view scaling. (#304)
This change removes ScreenStackHeaderTitleView component and code that used to handle title view scaling. There were multiple issues related to scaling toolbar title views and it is not a priority at the moment to us to work on addressing those. The most frequent usecase is to put a fixed-dimensions view (e.g. logo or text) which can be now handled w/o this extra code.
2020-02-03 16:27:00 +01:00
osdnk
074aabc33e Bump version -> 2.0.0-alpha.33 2020-02-02 17:09:15 +01:00
Satyajit Sahoo
eb2a1ae8fd Fix type definition for gestureEnabled (#302) 2020-01-29 23:51:32 +01:00
Krzysztof Magiera
9721353e7b Bump version -> 2.0.0-alpha.32 2020-01-28 11:27:05 +01:00
Krzysztof Magiera
523b3d8f3a Fix native stack navigator wrapper after #254 (#299) 2020-01-27 19:26:51 +01:00
Krzysztof Magiera
f851c26642 Bump version -> 2.0.0-alpha.31 2020-01-27 15:46:49 +01:00
Krzysztof Magiera
f8f5d66bf9 Fix updating native header on iOS while transition is ongoing. (#298)
This change fixes a bug when header property updates happens during or at the same time of stack transition, e.g., when we both pop a screen and change parent VC header properties. When this happens we trigger header update code, but because we used to compare current VC to visibleViewController, which at that moment points to controller that we are transitioning away from, the update would've been prevented. With this change instead of using visibleViewController to compare with updated VC, we check if there is transition happening (using transitransitionCoordinator property), and in such case we use topController which points to the controller we are transitioning to.
2020-01-27 15:34:40 +01:00
osdnk
72037e20fb Bump version -> 2.0.0-alpha.30 2020-01-27 15:32:48 +01:00
Michał Osadnik
69a23f1c9f Remove blocking touch interactions if more than one active scree… (#296)
We believe that it might be better to manage the logic of blocking interactions from JS with setNativeProps and pointerEvents.

The current logic leads to some issues we're facing within React Navigation. There might be a state when more that one screen can be active. E.g. in modals we want to have two screens visible on the stack and have enabled interaction on the bottom one.

New code should not give any issues, because even though we need to handle activation of each screen from JS and this will just introduce an additional native call for enabling / disabling pointer events.

On iOS, we're not using React Native Screens in React Navigation for now and we'd like to focus on it later because right now we're not observing that crucial performance issues.
2020-01-27 11:07:07 +01:00
Janic Duplessis
d32463ee83 Move gestureEnabled config to screen instead of heade… (#254)
When you have 2 screens in a stack with the bottom one with gestureEnabled=false using the back gesture causes the screen to become unresponsive. This seems to be caused by setting interactivePopGestureRecognizer.enabled = NO as soon as the gesture starts. This causes the gesture to cancel immediately and leaves the screen in an unresponsive state.

<Stack><Screen gestureEnabled={false} /><Screen /></Stack>
To fix this instead of using interactivePopGestureRecognizer.enabled we can leverage the existing delegate that we have in RNScreenStack. In gestureRecognizerShouldBegin we can check if the top screen has gestures enabled.

To make this simpler I moved the gestureEnabled config to Screen instead of HeaderConfig. I think it also makes more sense conceptually since the gesture is tied to the screen and not the header. It also simplifies the android code a bit.

This is a breaking change.

Update

This now only moves the config to screen since a separate fix was merged for the bug.
2020-01-24 01:19:38 +01:00
Satyajit Sahoo
2da04f37e6 Fix typo in onDismissed prop name (#288)
The prop is called `onDismissed` according to the code

e00a08a3dc/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.java (L65)
e00a08a3dc/ios/RNSScreen.h (L43)
2020-01-24 01:18:26 +01:00
18 changed files with 128 additions and 208 deletions

View File

@@ -219,7 +219,7 @@ PODS:
- ReactCommon/jscallinvoker (= 0.61.2)
- RNGestureHandler (1.3.0):
- React
- RNScreens (2.0.0-alpha.3):
- RNScreens (2.0.0-alpha.23):
- React
- Yoga (1.14.0)
@@ -256,7 +256,7 @@ DEPENDENCIES:
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
https://github.com/cocoapods/specs.git:
https://github.com/CocoaPods/Specs.git:
- boost-for-react-native
EXTERNAL SOURCES:
@@ -342,9 +342,9 @@ SPEC CHECKSUMS:
React-RCTVibration: fb54c732fd20405a76598e431aa2f8c2bf527de9
ReactCommon: 5848032ed2f274fcb40f6b9ec24067787c42d479
RNGestureHandler: 5329a942fce3d41c68b84c2c2276ce06a696d8b0
RNScreens: a32a406ec4884f0ba383ef835332e8379f75e3da
RNScreens: 55c735f525774e894be67848c250c95a9c3194c0
Yoga: 14927e37bd25376d216b150ab2a561773d57911f
PODFILE CHECKSUM: 1a141b811c7076eb11c48f2e22336181f52531b5
COCOAPODS: 1.7.3
COCOAPODS: 1.8.4

View File

@@ -131,7 +131,7 @@ Screen stack component expects one or more `Screen` components as direct childre
`StackScreen` extends the capabilities of `Screen` component to allow additional customizations and to make it possible to handle events such as using hardware back or back gesture to dismiss the top screen. Below is the list of additional properties that can be used for `Screen` component:
#### `onDismiss`
#### `onDismissed`
A callback that gets called when the current screen is dismissed by hardware back (on Android) or dismiss gesture (swipe back or down). The callback takes no arguments.
@@ -155,9 +155,8 @@ Defines how the method that should be used to present the given screen. It is a
The config component is expected to be rendered as a direct children of `<Screen>`. It provides an ability to configure native navigation header that gets rendered as a part of native screen stack. The component acts as a "virtual" element that is not directly rendered under `Screen`. You can use its properties to customize platform native header for the parent screen and also render react-native components that you'd like to be displayed inside the header (e.g. in the title are or on the side).
Along with this component properties that can be used to customize header behavior one can also use one or the below component containers to render custom react-native content in different areas of the native header:
- `ScreenStackHeaderTitleView` react native views rendered as children will appear on the navigation bar in the place of title. Note that title is aligned next to back button on Android while it is centered on iOS.
- `ScreenStackHeaderCenterView` the childern will render in the center of the native navigation bar (on iOS this has the same behavior as `ScreenStackHeaderTitleView` container)
- `ScreenStackHeaderRightView` the children will render on the right hand side of the navigation bar (or on the left hand side in case LTR locales are set on the user's device)
- `ScreenStackHeaderCenterView` the childern will render in the center of the native navigation bar.
- `ScreenStackHeaderRightView` the children will render on the right hand side of the navigation bar (or on the left hand side in case LTR locales are set on the user's device).
- `ScreenStackHeaderLeftView` the children will render on the left hand side of the navigation bar (or on the right hand side in case LTR locales are set on the user's device).
Below is a list of properties that can be set with `ScreenStackHeaderConfig` component:

View File

@@ -16,7 +16,7 @@ import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactPointerEventsView;
import com.facebook.react.uimanager.UIManagerModule;
public class Screen extends ViewGroup implements ReactPointerEventsView {
public class Screen extends ViewGroup {
public enum StackPresentation {
PUSH,
@@ -52,6 +52,7 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
private boolean mTransitioning;
private StackPresentation mStackPresentation = StackPresentation.PUSH;
private StackAnimation mStackAnimation = StackAnimation.DEFAULT;
private boolean mGestureEnabled = true;
public Screen(ReactContext context) {
super(context);
@@ -122,6 +123,10 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
mStackAnimation = stackAnimation;
}
public void setGestureEnabled(boolean gestureEnabled) {
mGestureEnabled = gestureEnabled;
}
public StackAnimation getStackAnimation() {
return mStackAnimation;
}
@@ -130,11 +135,6 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
return mStackPresentation;
}
@Override
public PointerEvents getPointerEvents() {
return mTransitioning ? PointerEvents.NONE : PointerEvents.AUTO;
}
@Override
public void setLayerType(int layerType, @Nullable Paint paint) {
// ignore - layer type is controlled by `transitioning` prop
@@ -169,4 +169,8 @@ public class Screen extends ViewGroup implements ReactPointerEventsView {
public boolean isActive() {
return mActive;
}
public boolean isGestureEnabled() {
return mGestureEnabled;
}
}

View File

@@ -11,6 +11,7 @@ import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment;
import com.facebook.react.uimanager.PixelUtil;
import com.google.android.material.appbar.AppBarLayout;
@@ -97,10 +98,35 @@ public class ScreenStackFragment extends ScreenFragment {
}
public boolean isDismissable() {
View child = mScreenView.getChildAt(0);
if (child instanceof ScreenStackHeaderConfig) {
return ((ScreenStackHeaderConfig) child).isDismissable();
return mScreenView.isGestureEnabled();
}
public boolean canNavigateBack() {
ScreenContainer container = mScreenView.getContainer();
if (container instanceof ScreenStack) {
if (((ScreenStack) container).getRootScreen() == getScreen()) {
// this screen is the root of the container, if it is nested we can check parent container
// if it is also a root or not
Fragment parentFragment = getParentFragment();
if (parentFragment instanceof ScreenStackFragment) {
return ((ScreenStackFragment) parentFragment).canNavigateBack();
} else {
return false;
}
} else {
return true;
}
} else {
throw new IllegalStateException("ScreenStackFragment added into a non-stack container");
}
}
public void dismiss() {
ScreenContainer container = mScreenView.getContainer();
if (container instanceof ScreenStack) {
((ScreenStack) container).dismiss(this);
} else {
throw new IllegalStateException("ScreenStackFragment added into a non-stack container");
}
return true;
}
}

View File

@@ -30,7 +30,6 @@ public class ScreenStackHeaderConfig extends ViewGroup {
private float mTitleFontSize;
private int mBackgroundColor;
private boolean mIsHidden;
private boolean mGestureEnabled = true;
private boolean mIsBackButtonHidden;
private boolean mIsShadowHidden;
private boolean mDestroyed;
@@ -42,7 +41,16 @@ public class ScreenStackHeaderConfig extends ViewGroup {
private OnClickListener mBackClickListener = new OnClickListener() {
@Override
public void onClick(View view) {
getScreenStack().dismiss(getScreenFragment());
ScreenStack stack = getScreenStack();
ScreenStackFragment fragment = getScreenFragment();
if (stack.getRootScreen() == fragment.getScreen()) {
Fragment parentFragment = fragment.getParentFragment();
if (parentFragment instanceof ScreenStackFragment) {
((ScreenStackFragment) parentFragment).dismiss();
}
} else {
fragment.dismiss();
}
}
};
@@ -115,14 +123,9 @@ public class ScreenStackHeaderConfig extends ViewGroup {
return null;
}
public boolean isDismissable() {
return mGestureEnabled;
}
public void onUpdate() {
Screen parent = (Screen) getParent();
final ScreenStack stack = getScreenStack();
boolean isRoot = stack == null ? true : stack.getRootScreen() == parent;
boolean isTop = stack == null ? true : stack.getTopScreen() == parent;
if (!mIsAttachedToWindow || !isTop || mDestroyed) {
@@ -145,7 +148,7 @@ public class ScreenStackHeaderConfig extends ViewGroup {
ActionBar actionBar = activity.getSupportActionBar();
// hide back button
actionBar.setDisplayHomeAsUpEnabled(isRoot ? false : !mIsBackButtonHidden);
actionBar.setDisplayHomeAsUpEnabled(getScreenFragment().canNavigateBack() ? !mIsBackButtonHidden : false);
// when setSupportActionBar is called a toolbar wrapper gets initialized that overwrites
// navigation click listener. The default behavior set in the wrapper is to call into
@@ -220,11 +223,10 @@ public class ScreenStackHeaderConfig extends ViewGroup {
case RIGHT:
params.gravity = Gravity.RIGHT;
break;
case TITLE:
params.width = LayoutParams.MATCH_PARENT;
mToolbar.setTitle(null);
case CENTER:
params.width = LayoutParams.MATCH_PARENT;
params.gravity = Gravity.CENTER_HORIZONTAL;
mToolbar.setTitle(null);
break;
}
@@ -303,10 +305,6 @@ public class ScreenStackHeaderConfig extends ViewGroup {
mIsShadowHidden = hideShadow;
}
public void setGestureEnabled(boolean gestureEnabled) {
mGestureEnabled = gestureEnabled;
}
public void setHideBackButton(boolean hideBackButton) {
mIsBackButtonHidden = hideBackButton;
}

View File

@@ -99,11 +99,6 @@ public class ScreenStackHeaderConfigViewManager extends ViewGroupManager<ScreenS
config.setHideShadow(hideShadow);
}
@ReactProp(name = "gestureEnabled", defaultBoolean = true)
public void setGestureEnabled(ScreenStackHeaderConfig config, boolean gestureEnabled) {
config.setGestureEnabled(gestureEnabled);
}
@ReactProp(name = "hideBackButton")
public void setHideBackButton(ScreenStackHeaderConfig config, boolean hideBackButton) {
config.setHideBackButton(hideBackButton);

View File

@@ -9,21 +9,14 @@ import com.facebook.react.views.view.ReactViewGroup;
public class ScreenStackHeaderSubview extends ReactViewGroup {
public class Measurements {
public int width;
public int height;
}
public enum Type {
LEFT,
CENTER,
TITLE,
RIGHT,
BACK
}
private int mReactWidth, mReactHeight;
private final UIManagerModule mUIManager;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@@ -43,26 +36,13 @@ public class ScreenStackHeaderSubview extends ReactViewGroup {
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed && (mType == Type.CENTER)) {
// if we want the view to be centered we need to account for the fact that right and left
// paddings may not be equal.
Measurements measurements = new Measurements();
View parent = (View) getParent();
int parentWidth = parent.getWidth();
int rightPadding = parentWidth - right;
int leftPadding = left;
measurements.width = Math.max(0, parentWidth - 2 * Math.max(rightPadding, leftPadding));
measurements.height = bottom - top;
mUIManager.setViewLocalData(getId(), measurements);
}
super.onLayout(changed, left, top, right, bottom);
// no-op
}
private Type mType = Type.RIGHT;
public ScreenStackHeaderSubview(ReactContext context) {
super(context);
mUIManager = context.getNativeModule(UIManagerModule.class);
}
public void setType(Type type) {

View File

@@ -1,8 +1,6 @@
package com.swmansion.rnscreens;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.LayoutShadowNode;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.views.view.ReactViewGroup;
@@ -11,15 +9,6 @@ import com.facebook.react.views.view.ReactViewManager;
@ReactModule(name = ScreenStackHeaderSubviewManager.REACT_CLASS)
public class ScreenStackHeaderSubviewManager extends ReactViewManager {
private static class SubviewShadowNode extends LayoutShadowNode {
@Override
public void setLocalData(Object data) {
ScreenStackHeaderSubview.Measurements measurements = (ScreenStackHeaderSubview.Measurements) data;
setStyleWidth(measurements.width);
setStyleHeight(measurements.height);
}
}
protected static final String REACT_CLASS = "RNSScreenStackHeaderSubview";
@Override
@@ -32,19 +21,12 @@ public class ScreenStackHeaderSubviewManager extends ReactViewManager {
return new ScreenStackHeaderSubview(context);
}
@Override
public LayoutShadowNode createShadowNodeInstance(ReactApplicationContext context) {
return new SubviewShadowNode();
}
@ReactProp(name = "type")
public void setType(ScreenStackHeaderSubview view, String type) {
if ("left".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.LEFT);
} else if ("center".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.CENTER);
} else if ("title".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.TITLE);
} else if ("right".equals(type)) {
view.setType(ScreenStackHeaderSubview.Type.RIGHT);
} else if ("back".equals(type)) {

View File

@@ -57,6 +57,11 @@ public class ScreenViewManager extends ViewGroupManager<Screen> {
}
}
@ReactProp(name = "gestureEnabled", defaultBoolean = true)
public void setGestureEnabled(Screen view, boolean gestureEnabled) {
view.setGestureEnabled(gestureEnabled);
}
@Nullable
@Override
public Map getExportedCustomDirectEventTypeConstants() {

View File

@@ -15,7 +15,7 @@ import {
ScreenStackHeaderBackButtonImage,
ScreenStackHeaderLeftView,
ScreenStackHeaderRightView,
ScreenStackHeaderTitleView,
ScreenStackHeaderCenterView,
} from 'react-native-screens';
function renderComponentOrThunk(componentOrThunk, props) {
@@ -31,7 +31,9 @@ class StackView extends React.Component {
};
_onSceneFocus = (route, descriptor) => {
descriptor.options && descriptor.options.onAppear && descriptor.options.onAppear()
descriptor.options &&
descriptor.options.onAppear &&
descriptor.options.onAppear();
this.props.navigation.dispatch(
StackActions.completeTransition({ toChildKey: route.key })
);
@@ -50,7 +52,6 @@ class StackView extends React.Component {
headerBackTitle,
headerBackTitleVisible,
headerTintColor,
gestureEnabled,
largeTitle,
headerLargeTitleStyle,
translucent,
@@ -75,7 +76,6 @@ class StackView extends React.Component {
headerBackTitleStyle && headerBackTitleStyle.fontFamily,
backTitleFontSize: headerBackTitleStyle && headerBackTitleStyle.fontSize,
color: headerTintColor,
gestureEnabled: gestureEnabled === undefined ? true : gestureEnabled,
largeTitle,
largeTitleFontFamily:
headerLargeTitleStyle && headerLargeTitleStyle.fontFamily,
@@ -141,9 +141,9 @@ class StackView extends React.Component {
headerOptions.title = options.headerTitle;
} else {
children.push(
<ScreenStackHeaderTitleView key="title">
<ScreenStackHeaderCenterView key="center">
{renderComponentOrThunk(options.headerTitle, { scene })}
</ScreenStackHeaderTitleView>
</ScreenStackHeaderCenterView>
);
}
}
@@ -191,6 +191,9 @@ class StackView extends React.Component {
style={options.cardStyle}
stackAnimation={stackAnimation}
stackPresentation={stackPresentation}
gestureEnabled={
options.gestureEnabled === undefined ? true : options.gestureEnabled
}
onAppear={() => this._onSceneFocus(route, descriptor)}
onDismissed={() => this._removeScene(route)}>
{this._renderHeaderConfig(index, route, descriptor)}

View File

@@ -44,6 +44,7 @@ typedef NS_ENUM(NSInteger, RNSScreenStackAnimation) {
@property (weak, nonatomic) UIView<RNSScreenContainerDelegate> *reactSuperview;
@property (nonatomic, retain) UIViewController *controller;
@property (nonatomic) BOOL active;
@property (nonatomic) BOOL gestureEnabled;
@property (nonatomic) RNSScreenStackAnimation stackAnimation;
@property (nonatomic) RNSScreenStackPresentation stackPresentation;

View File

@@ -26,6 +26,7 @@
_controller = [[RNSScreen alloc] initWithView:self];
_stackPresentation = RNSScreenStackPresentationPush;
_stackAnimation = RNSScreenStackAnimationDefault;
_gestureEnabled = YES;
}
return self;
@@ -276,6 +277,7 @@
RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(active, BOOL)
RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(stackPresentation, RNSScreenStackPresentation)
RCT_EXPORT_VIEW_PROPERTY(stackAnimation, RNSScreenStackAnimation)
RCT_EXPORT_VIEW_PROPERTY(onAppear, RCTDirectEventBlock);

View File

@@ -102,16 +102,9 @@
RCTRootContentView *rootView = (RCTRootContentView *)parent;
[rootView.touchHandler cancel];
UIView *topView = _controller.viewControllers.lastObject.view;
RNSScreenStackHeaderConfig *config = nil;
for (UIView *subview in topView.reactSubviews) {
if ([subview isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
config = (RNSScreenStackHeaderConfig*) subview;
break;
}
}
RNSScreenView *topScreen = (RNSScreenView *)_controller.viewControllers.lastObject.view;
return _controller.viewControllers.count > 1 && (config == nil || config.gestureEnabled);
return _controller.viewControllers.count > 1 && topScreen.gestureEnabled;
}
- (void)markChildUpdated

View File

@@ -23,7 +23,6 @@
@property (nonatomic) BOOL hideBackButton;
@property (nonatomic) BOOL hideShadow;
@property (nonatomic) BOOL translucent;
@property (nonatomic) BOOL gestureEnabled;
+ (void)willShowViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig*)config;

View File

@@ -20,34 +20,26 @@
@end
@interface RNSScreenHeaderItemMeasurements : NSObject
@property (nonatomic, readonly) CGSize headerSize;
@property (nonatomic, readonly) CGFloat leftPadding;
@property (nonatomic, readonly) CGFloat rightPadding;
- (instancetype)initWithHeaderSize:(CGSize)headerSize leftPadding:(CGFloat)leftPadding rightPadding:(CGFloat)rightPadding;
@end
@implementation RNSScreenHeaderItemMeasurements
- (instancetype)initWithHeaderSize:(CGSize)headerSize leftPadding:(CGFloat)leftPadding rightPadding:(CGFloat)rightPadding
{
if (self = [super init]) {
_headerSize = headerSize;
_leftPadding = leftPadding;
_rightPadding = rightPadding;
}
return self;
}
@end
@interface RNSScreenStackHeaderSubview : UIView
@property (nonatomic, weak) RCTBridge *bridge;
@property (nonatomic, weak) UIView *reactSuperview;
@property (nonatomic) RNSScreenStackHeaderSubviewType type;
- (instancetype)initWithBridge:(RCTBridge*)bridge;
@end
@implementation RNSScreenStackHeaderSubview
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
}
return self;
}
@end
@implementation RNSScreenStackHeaderConfig {
@@ -60,7 +52,6 @@
self.hidden = YES;
_translucent = YES;
_reactSubviews = [NSMutableArray new];
_gestureEnabled = YES;
}
return self;
}
@@ -96,7 +87,16 @@
{
UIViewController *vc = _screenView.controller;
UINavigationController *nav = (UINavigationController*) vc.parentViewController;
if (vc != nil && nav.visibleViewController == vc) {
UIViewController *nextVC = nav.visibleViewController;
if (nav.transitionCoordinator != nil) {
// if navigator is performing transition instead of allowing to update of `visibleConttroller`
// we look at `topController`. This is because during transitiong the `visibleController` won't
// point to the controller that is going to be revealed after transition. This check fixes the
// problem when config gets updated while the transition is ongoing.
nextVC = nav.topViewController;
}
if (vc != nil && nextVC == vc) {
[RNSScreenStackHeaderConfig updateViewController:self.screenView.controller withConfig:self];
}
}
@@ -269,7 +269,7 @@
[navctr setNavigationBarHidden:shouldHide animated:YES];
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
vc.modalInPresentation = !config.gestureEnabled;
vc.modalInPresentation = !config.screenView.gestureEnabled;
}
#endif
if (shouldHide) {
@@ -395,17 +395,22 @@
}
case RNSScreenStackHeaderSubviewTypeCenter:
case RNSScreenStackHeaderSubviewTypeTitle: {
subview.translatesAutoresizingMaskIntoConstraints = NO;
navitem.titleView = subview;
break;
}
}
}
if (vc.transitionCoordinator != nil && !wasHidden) {
[vc.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
} completion:nil];
if (vc.transitionCoordinator != nil
&& vc.transitionCoordinator.presentationStyle == UIModalPresentationNone
&& !wasHidden) {
// when there is an ongoing transition we may need to update navbar setting in animation block
// using animateAlongsideTransition. However, we only do that given the transition is not a modal
// transition (presentationStyle == UIModalPresentationNone) and that the bar was not previously
// hidden. This is because both for modal transitions and transitions from screen with hidden bar
// the transition animation block does not get triggered. This is ok, because with both of those
// types of transitions there is no "shared" navigation bar that needs to be updated in an animated
// way.
[vc.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[self setAnimatedConfig:vc withConfig:config];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
@@ -454,7 +459,6 @@ RCT_EXPORT_VIEW_PROPERTY(hideShadow, BOOL)
// `hidden` is an UIView property, we need to use different name internally
RCT_REMAP_VIEW_PROPERTY(hidden, hide, BOOL)
RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)
RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL)
@end
@@ -470,67 +474,6 @@ RCT_ENUM_CONVERTER(RNSScreenStackHeaderSubviewType, (@{
@end
@implementation RNSScreenStackHeaderSubview
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
}
return self;
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (!self.translatesAutoresizingMaskIntoConstraints) {
CGSize size = self.superview.frame.size;
CGFloat right = size.width - self.frame.size.width - self.frame.origin.x;
CGFloat left = self.frame.origin.x;
[_bridge.uiManager
setLocalData:[[RNSScreenHeaderItemMeasurements alloc]
initWithHeaderSize:size
leftPadding:left rightPadding:right]
forView:self];
}
}
- (void)reactSetFrame:(CGRect)frame
{
if (self.translatesAutoresizingMaskIntoConstraints) {
[super reactSetFrame:frame];
}
}
- (CGSize)intrinsicContentSize
{
return UILayoutFittingExpandedSize;
}
@end
@interface RNSScreenStackHeaderSubviewShadow : RCTShadowView
@end
@implementation RNSScreenStackHeaderSubviewShadow
- (void)setLocalData:(RNSScreenHeaderItemMeasurements *)data
{
self.width = (YGValue){data.headerSize.width - data.leftPadding - data.rightPadding, YGUnitPoint};
self.height = (YGValue){data.headerSize.height, YGUnitPoint};
if (data.leftPadding > data.rightPadding) {
self.paddingLeft = (YGValue){0, YGUnitPoint};
self.paddingRight = (YGValue){data.leftPadding - data.rightPadding, YGUnitPoint};
} else {
self.paddingLeft = (YGValue){data.rightPadding - data.leftPadding, YGUnitPoint};
self.paddingRight = (YGValue){0, YGUnitPoint};
}
[self didSetProps:@[@"width", @"height", @"paddingLeft", @"paddingRight"]];
}
@end
@implementation RNSScreenStackHeaderSubviewManager
RCT_EXPORT_MODULE()
@@ -542,9 +485,4 @@ RCT_EXPORT_VIEW_PROPERTY(type, RNSScreenStackHeaderSubviewType)
return [[RNSScreenStackHeaderSubview alloc] initWithBridge:self.bridge];
}
- (RCTShadowView *)shadowView
{
return [RNSScreenStackHeaderSubviewShadow new];
}
@end

View File

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

20
src/screens.d.ts vendored
View File

@@ -3,7 +3,13 @@
declare module 'react-native-screens' {
import { ComponentClass } from 'react';
import { ViewProps, Animated, NativeSyntheticEvent, NativeTouchEvent, ImageProps } from 'react-native';
import {
ViewProps,
Animated,
NativeSyntheticEvent,
NativeTouchEvent,
ImageProps,
} from 'react-native';
export function useScreens(shouldUseScreens?: boolean): void;
export function enableScreens(shouldEnableScreens?: boolean): void;
@@ -35,11 +41,14 @@ declare module 'react-native-screens' {
* @type "none" the screen appears/dissapears without an animation
*/
stackAnimation?: StackAnimationTypes;
/**
* @description When set to false the back swipe gesture will be disabled when the parent Screen is on top of the stack. The default value is true.
*/
gestureEnabled?: boolean;
}
export type ScreenContainerProps = ViewProps;
export interface ScreenStackProps extends ViewProps {
transitioning?: number;
progress?: number;
@@ -82,10 +91,6 @@ declare module 'react-native-screens' {
* @description If set to true the back button will not be rendered as a part of navigation header.
*/
hideBackButton?: boolean;
/**
* @description When set to false the back swipe gesture will be disabled when the parent Screen is on top of the stack. The default value is true.
*/
gestureEnabled?: boolean;
/**
* @host (iOS only)
* @description When set to true, it makes native navigation bar on iOS semi transparent with blur effect. It is a common way of presenting navigation bar introduced in iOS 11. The default value is false
@@ -135,7 +140,6 @@ declare module 'react-native-screens' {
export const ScreenStackHeaderBackButtonImage: ComponentClass<ImageProps>;
export const ScreenStackHeaderLeftView: ComponentClass<ViewProps>;
export const ScreenStackHeaderRightView: ComponentClass<ViewProps>;
export const ScreenStackHeaderTitleView: ComponentClass<ViewProps>;
export const ScreenStackHeaderConfig: ComponentClass<
ScreenStackHeaderConfigProps
>;

View File

@@ -171,14 +171,6 @@ const ScreenStackHeaderLeftView = props => (
/>
);
const ScreenStackHeaderTitleView = props => (
<ScreensNativeModules.NativeScreenStackHeaderSubview
{...props}
type="title"
style={styles.headerSubview}
/>
);
const ScreenStackHeaderCenterView = props => (
<ScreensNativeModules.NativeScreenStackHeaderSubview
{...props}
@@ -210,7 +202,6 @@ module.exports = {
ScreenStackHeaderBackButtonImage,
ScreenStackHeaderRightView,
ScreenStackHeaderLeftView,
ScreenStackHeaderTitleView,
ScreenStackHeaderCenterView,
enableScreens,