-
-
Notifications
You must be signed in to change notification settings - Fork 182
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #641 from pavjacko/improvement/rn-screens-2.0.0-be…
…ta.8 improvement(RNScreens): Add overrides for 2.0.0-beta.8 version which allows for package usage on native TV platforms
- Loading branch information
Showing
3 changed files
with
1,272 additions
and
0 deletions.
There are no files selected for viewing
334 changes: 334 additions & 0 deletions
334
packages/rnv/pluginTemplates/react-native-screens/[email protected]/ios/RNSScreen.m
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,334 @@ | ||
#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; | ||
_gestureEnabled = YES; | ||
} | ||
|
||
return self; | ||
} | ||
|
||
- (void)reactSetFrame:(CGRect)frame | ||
{ | ||
if (![self.reactViewController.parentViewController | ||
isKindOfClass:[UINavigationController class]]) { | ||
[super reactSetFrame:frame]; | ||
} | ||
// when screen is mounted under UINavigationController it's size is controller | ||
// by the navigation controller itself. That is, it is set to fill space of | ||
// the controller. In that case we ignore react layout system from managing | ||
// the screen dimentions and we wait for the screen VC to update and then we | ||
// pass the dimentions to ui view manager to take into account when laying out | ||
// subviews | ||
} | ||
|
||
- (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: | ||
#if !TARGET_OS_TV | ||
_controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal; | ||
#endif | ||
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]; | ||
} | ||
|
||
- (BOOL)presentationControllerShouldDismiss:(UIPresentationController *)presentationController | ||
{ | ||
return _gestureEnabled; | ||
} | ||
|
||
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController | ||
{ | ||
if ([_reactSuperview respondsToSelector:@selector(presentationControllerDidDismiss:)]) { | ||
[_reactSuperview performSelector:@selector(presentationControllerDidDismiss:) | ||
withObject:presentationController]; | ||
} | ||
} | ||
|
||
@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]; | ||
_view = self.view; | ||
self.view = nil; | ||
} | ||
} | ||
|
||
- (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(gestureEnabled, 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 | ||
|
Oops, something went wrong.