mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-13 22:30:41 +08:00
For native stack we introduced a new way of layouting screen controllers. We no longer rely on flexbox to layout them so that we can use native stack to provide a correct dimensions and safe area paddings. Unfortunately this change broke the old screen container. With this commit we are adapting layout change to old screen container that will now layout its direct children on the native side by making them take up the whole space of the container.
283 lines
8.0 KiB
Objective-C
283 lines
8.0 KiB
Objective-C
#import <UIKit/UIKit.h>
|
|
|
|
#import "RNSScreen.h"
|
|
#import "RNSScreenContainer.h"
|
|
#import "RNSScreenStackHeaderConfig.h"
|
|
|
|
#import <React/RCTUIManager.h>
|
|
#import <React/RCTShadowView.h>
|
|
#import <React/RCTTouchHandler.h>
|
|
|
|
@interface RNSScreenView () <UIAdaptivePresentationControllerDelegate>
|
|
@end
|
|
|
|
@implementation RNSScreenView {
|
|
__weak RCTBridge *_bridge;
|
|
RNSScreen *_controller;
|
|
RCTTouchHandler *_touchHandler;
|
|
}
|
|
|
|
@synthesize controller = _controller;
|
|
|
|
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
|
{
|
|
if (self = [super init]) {
|
|
_bridge = bridge;
|
|
_controller = [[RNSScreen alloc] initWithView:self];
|
|
_stackPresentation = RNSScreenStackPresentationPush;
|
|
_stackAnimation = RNSScreenStackAnimationDefault;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)reactSetFrame:(CGRect)frame
|
|
{
|
|
// ignore setFrame call from react, the frame of this view
|
|
// is controlled by the UIViewController it is contained in
|
|
}
|
|
|
|
- (void)updateBounds
|
|
{
|
|
[_bridge.uiManager setSize:self.bounds.size forView:self];
|
|
}
|
|
|
|
- (void)setActive:(BOOL)active
|
|
{
|
|
if (active != _active) {
|
|
_active = active;
|
|
[_reactSuperview markChildUpdated];
|
|
}
|
|
}
|
|
|
|
- (void)setPointerEvents:(RCTPointerEvents)pointerEvents
|
|
{
|
|
// pointer events settings are managed by the parent screen container, we ignore
|
|
// any attempt of setting that via React props
|
|
}
|
|
|
|
- (void)setStackPresentation:(RNSScreenStackPresentation)stackPresentation
|
|
{
|
|
_stackPresentation = stackPresentation;
|
|
switch (stackPresentation) {
|
|
case RNSScreenStackPresentationModal:
|
|
#ifdef __IPHONE_13_0
|
|
if (@available(iOS 13.0, *)) {
|
|
_controller.modalPresentationStyle = UIModalPresentationAutomatic;
|
|
} else {
|
|
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
}
|
|
#else
|
|
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
|
|
#endif
|
|
break;
|
|
case RNSScreenStackPresentationTransparentModal:
|
|
_controller.modalPresentationStyle = UIModalPresentationOverFullScreen;
|
|
break;
|
|
case RNSScreenStackPresentationContainedModal:
|
|
_controller.modalPresentationStyle = UIModalPresentationCurrentContext;
|
|
break;
|
|
case RNSScreenStackPresentationContainedTransparentModal:
|
|
_controller.modalPresentationStyle = UIModalPresentationOverCurrentContext;
|
|
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;
|
|
}
|
|
|
|
- (void)setStackAnimation:(RNSScreenStackAnimation)stackAnimation
|
|
{
|
|
_stackAnimation = stackAnimation;
|
|
|
|
switch (stackAnimation) {
|
|
case RNSScreenStackAnimationFade:
|
|
_controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
|
|
break;
|
|
case RNSScreenStackAnimationNone:
|
|
case RNSScreenStackAnimationDefault:
|
|
// Default
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (UIView *)reactSuperview
|
|
{
|
|
return _reactSuperview;
|
|
}
|
|
|
|
- (void)addSubview:(UIView *)view
|
|
{
|
|
if (![view isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
|
|
[super addSubview:view];
|
|
}
|
|
}
|
|
|
|
- (void)notifyFinishTransitioning
|
|
{
|
|
[_controller notifyFinishTransitioning];
|
|
}
|
|
|
|
- (void)notifyDismissed
|
|
{
|
|
if (self.onDismissed) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (self.onDismissed) {
|
|
self.onDismissed(nil);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
- (BOOL)isMountedUnderScreenOrReactRoot
|
|
{
|
|
for (UIView *parent = self.superview; parent != nil; parent = parent.superview) {
|
|
if ([parent isKindOfClass:[RCTRootView class]] || [parent isKindOfClass:[RNSScreenView class]]) {
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)didMoveToWindow
|
|
{
|
|
// For RN touches to work we need to instantiate and connect RCTTouchHandler. This only applies
|
|
// for screens that aren't mounted under RCTRootView e.g., modals that are mounted directly to
|
|
// root application window.
|
|
if (self.window != nil && ![self isMountedUnderScreenOrReactRoot]) {
|
|
if (_touchHandler == nil) {
|
|
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
|
|
}
|
|
[_touchHandler attachToView:self];
|
|
} else {
|
|
[_touchHandler detachFromView:self];
|
|
}
|
|
}
|
|
|
|
- (void)presentationControllerWillDismiss:(UIPresentationController *)presentationController
|
|
{
|
|
// We need to call both "cancel" and "reset" here because RN's gesture recognizer
|
|
// does not handle the scenario when it gets cancelled by other top
|
|
// level gesture recognizer. In this case by the modal dismiss gesture.
|
|
// Because of that, at the moment when this method gets called the React's
|
|
// gesture recognizer is already in FAILED state but cancel events never gets
|
|
// send to JS. Calling "reset" forces RCTTouchHanler to dispatch cancel event.
|
|
// To test this behavior one need to open a dismissable modal and start
|
|
// pulling down starting at some touchable item. Without "reset" the touchable
|
|
// will never go back from highlighted state even when the modal start sliding
|
|
// down.
|
|
[_touchHandler cancel];
|
|
[_touchHandler reset];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RNSScreen {
|
|
__weak UIView *_view;
|
|
__weak id _previousFirstResponder;
|
|
CGRect _lastViewFrame;
|
|
}
|
|
|
|
- (instancetype)initWithView:(UIView *)view
|
|
{
|
|
if (self = [super init]) {
|
|
_view = view;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)viewDidLayoutSubviews
|
|
{
|
|
[super viewDidLayoutSubviews];
|
|
|
|
if (!CGRectEqualToRect(_lastViewFrame, self.view.frame)) {
|
|
_lastViewFrame = self.view.frame;
|
|
[((RNSScreenView *)self.view) updateBounds];
|
|
}
|
|
}
|
|
|
|
- (id)findFirstResponder:(UIView*)parent
|
|
{
|
|
if (parent.isFirstResponder) {
|
|
return parent;
|
|
}
|
|
for (UIView *subView in parent.subviews) {
|
|
id responder = [self findFirstResponder:subView];
|
|
if (responder != nil) {
|
|
return responder;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (void)willMoveToParentViewController:(UIViewController *)parent
|
|
{
|
|
if (parent == nil) {
|
|
id responder = [self findFirstResponder:self.view];
|
|
if (responder != nil) {
|
|
_previousFirstResponder = responder;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)viewDidDisappear:(BOOL)animated
|
|
{
|
|
[super viewDidDisappear:animated];
|
|
if (self.parentViewController == nil && self.presentingViewController == nil) {
|
|
// screen dismissed, send event
|
|
[((RNSScreenView *)self.view) notifyDismissed];
|
|
}
|
|
}
|
|
|
|
- (void)notifyFinishTransitioning
|
|
{
|
|
[_previousFirstResponder becomeFirstResponder];
|
|
_previousFirstResponder = nil;
|
|
}
|
|
|
|
- (void)loadView
|
|
{
|
|
if (_view != nil) {
|
|
self.view = _view;
|
|
_view = nil;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RNSScreenManager
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(active, BOOL)
|
|
RCT_EXPORT_VIEW_PROPERTY(stackPresentation, RNSScreenStackPresentation)
|
|
RCT_EXPORT_VIEW_PROPERTY(stackAnimation, RNSScreenStackAnimation)
|
|
RCT_EXPORT_VIEW_PROPERTY(onDismissed, RCTDirectEventBlock);
|
|
|
|
- (UIView *)view
|
|
{
|
|
return [[RNSScreenView alloc] initWithBridge:self.bridge];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RCTConvert (RNSScreen)
|
|
|
|
RCT_ENUM_CONVERTER(RNSScreenStackPresentation, (@{
|
|
@"push": @(RNSScreenStackPresentationPush),
|
|
@"modal": @(RNSScreenStackPresentationModal),
|
|
@"containedModal": @(RNSScreenStackPresentationContainedModal),
|
|
@"transparentModal": @(RNSScreenStackPresentationTransparentModal),
|
|
@"containedTransparentModal": @(RNSScreenStackPresentationContainedTransparentModal)
|
|
}), RNSScreenStackPresentationPush, integerValue)
|
|
|
|
RCT_ENUM_CONVERTER(RNSScreenStackAnimation, (@{
|
|
@"default": @(RNSScreenStackAnimationDefault),
|
|
@"none": @(RNSScreenStackAnimationNone),
|
|
@"fade": @(RNSScreenStackAnimationFade)
|
|
}), RNSScreenStackAnimationDefault, integerValue)
|
|
|
|
|
|
@end
|