mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-10 22:47:02 +08:00
fixes #208 For stack overriding reactSetFrame for active does nothing because active is always NO, so there's no regression. But it fixes in other cases (like stack navigation - not native one).
311 lines
8.7 KiB
Objective-C
311 lines
8.7 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
|
|
{
|
|
if (_active) {
|
|
[super reactSetFrame: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 RNSScreenStackAnimationFlip:
|
|
_controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
|
|
break;
|
|
case RNSScreenStackAnimationNone:
|
|
case RNSScreenStackAnimationDefault:
|
|
// Default
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (UIView *)reactSuperview
|
|
{
|
|
return _reactSuperview;
|
|
}
|
|
|
|
- (void)addSubview:(UIView *)view
|
|
{
|
|
if (![view isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
|
|
[super addSubview:view];
|
|
} else {
|
|
((RNSScreenStackHeaderConfig*) view).screenView = self;
|
|
}
|
|
}
|
|
|
|
- (void)notifyFinishTransitioning
|
|
{
|
|
[_controller notifyFinishTransitioning];
|
|
}
|
|
|
|
- (void)notifyDismissed
|
|
{
|
|
if (self.onDismissed) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (self.onDismissed) {
|
|
self.onDismissed(nil);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)notifyAppear
|
|
{
|
|
if (self.onAppear) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (self.onAppear) {
|
|
self.onAppear(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)viewDidAppear:(BOOL)animated
|
|
{
|
|
[super viewDidAppear:animated];
|
|
[((RNSScreenView *)self.view) notifyAppear];
|
|
}
|
|
|
|
- (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(onAppear, RCTDirectEventBlock);
|
|
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),
|
|
@"flip": @(RNSScreenStackAnimationFlip),
|
|
}), RNSScreenStackAnimationDefault, integerValue)
|
|
|
|
|
|
@end
|
|
|