This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

77
node_modules/react-native-screens/ios/RNSScreen.h generated vendored Normal file
View File

@ -0,0 +1,77 @@
#import <React/RCTViewManager.h>
#import <React/RCTView.h>
#import <React/RCTComponent.h>
#import "RNSScreenContainer.h"
@class RNSScreenContainerView;
typedef NS_ENUM(NSInteger, RNSScreenStackPresentation) {
RNSScreenStackPresentationPush,
RNSScreenStackPresentationModal,
RNSScreenStackPresentationTransparentModal,
RNSScreenStackPresentationContainedModal,
RNSScreenStackPresentationContainedTransparentModal,
RNSScreenStackPresentationFullScreenModal,
RNSScreenStackPresentationFormSheet
};
typedef NS_ENUM(NSInteger, RNSScreenStackAnimation) {
RNSScreenStackAnimationDefault,
RNSScreenStackAnimationNone,
RNSScreenStackAnimationFade,
RNSScreenStackAnimationFlip,
};
typedef NS_ENUM(NSInteger, RNSScreenReplaceAnimation) {
RNSScreenReplaceAnimationPop,
RNSScreenReplaceAnimationPush,
};
typedef NS_ENUM(NSInteger, RNSActivityState) {
RNSActivityStateInactive = 0,
RNSActivityStateTransitioningOrBelowTop = 1,
RNSActivityStateOnTop = 2
};
@interface RCTConvert (RNSScreen)
+ (RNSScreenStackPresentation)RNSScreenStackPresentation:(id)json;
+ (RNSScreenStackAnimation)RNSScreenStackAnimation:(id)json;
@end
@interface RNSScreen : UIViewController <RNScreensViewControllerDelegate>
- (instancetype)initWithView:(UIView *)view;
- (void)notifyFinishTransitioning;
@end
@interface RNSScreenManager : RCTViewManager
@end
@interface RNSScreenView : RCTView
@property (nonatomic, copy) RCTDirectEventBlock onAppear;
@property (nonatomic, copy) RCTDirectEventBlock onDisappear;
@property (nonatomic, copy) RCTDirectEventBlock onDismissed;
@property (nonatomic, copy) RCTDirectEventBlock onWillAppear;
@property (nonatomic, copy) RCTDirectEventBlock onWillDisappear;
@property (weak, nonatomic) UIView<RNSScreenContainerDelegate> *reactSuperview;
@property (nonatomic, retain) UIViewController *controller;
@property (nonatomic, readonly) BOOL dismissed;
@property (nonatomic) int activityState;
@property (nonatomic) BOOL gestureEnabled;
@property (nonatomic) RNSScreenStackAnimation stackAnimation;
@property (nonatomic) RNSScreenStackPresentation stackPresentation;
@property (nonatomic) RNSScreenReplaceAnimation replaceAnimation;
- (void)notifyFinishTransitioning;
@end
@interface UIView (RNSScreen)
- (UIViewController *)parentViewController;
@end

532
node_modules/react-native-screens/ios/RNSScreen.m generated vendored Normal file
View File

@ -0,0 +1,532 @@
#import <UIKit/UIKit.h>
#import "RNSScreen.h"
#import "RNSScreenStackHeaderConfig.h"
#import "RNSScreenContainer.h"
#import <React/RCTUIManager.h>
#import <React/RCTShadowView.h>
#import <React/RCTTouchHandler.h>
@interface RNSScreenView () <UIAdaptivePresentationControllerDelegate, RCTInvalidating>
@end
@implementation RNSScreenView {
__weak RCTBridge *_bridge;
RNSScreen *_controller;
RCTTouchHandler *_touchHandler;
CGRect _reactFrame;
}
@synthesize controller = _controller;
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
_controller = [[RNSScreen alloc] initWithView:self];
_stackPresentation = RNSScreenStackPresentationPush;
_stackAnimation = RNSScreenStackAnimationDefault;
_gestureEnabled = YES;
_replaceAnimation = RNSScreenReplaceAnimationPop;
_dismissed = NO;
}
return self;
}
- (void)reactSetFrame:(CGRect)frame
{
_reactFrame = frame;
UIViewController *parentVC = self.reactViewController.parentViewController;
if (parentVC != nil && ![parentVC 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 dimensions and we wait for the screen VC to update and then we
// pass the dimensions to ui view manager to take into account when laying out
// subviews
}
- (UIViewController *)reactViewController
{
return _controller;
}
- (void)updateBounds
{
[_bridge.uiManager setSize:self.bounds.size forView:self];
}
// Nil will be provided when activityState is set as an animated value and we change
// it from JS to be a plain value (non animated).
// In case when nil is received, we want to ignore such value and not make
// any updates as the actual non-nil value will follow immediately.
- (void)setActivityStateOrNil:(NSNumber *)activityStateOrNil
{
int activityState = [activityStateOrNil intValue];
if (activityStateOrNil != nil && activityState != _activityState) {
_activityState = activityState;
[_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
{
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 RNSScreenStackPresentationFullScreenModal:
_controller.modalPresentationStyle = UIModalPresentationFullScreen;
break;
#if (TARGET_OS_IOS)
case RNSScreenStackPresentationFormSheet:
_controller.modalPresentationStyle = UIModalPresentationFormSheet;
break;
#endif
case RNSScreenStackPresentationTransparentModal:
_controller.modalPresentationStyle = UIModalPresentationOverFullScreen;
break;
case RNSScreenStackPresentationContainedModal:
_controller.modalPresentationStyle = UIModalPresentationCurrentContext;
break;
case RNSScreenStackPresentationContainedTransparentModal:
_controller.modalPresentationStyle = UIModalPresentationOverCurrentContext;
break;
case RNSScreenStackPresentationPush:
// ignored, we only need to keep in mind not to set presentation delegate
break;
}
// There is a bug in UIKit which causes retain loop when presentationController is accessed for a
// controller that is not going to be presented modally. We therefore need to avoid setting the
// delegate for screens presented using push. This also means that when controller is updated from
// modal to push type, this may cause memory leak, we warn about that as well.
if (stackPresentation != RNSScreenStackPresentationPush) {
// `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;
} else if (_stackPresentation != RNSScreenStackPresentationPush) {
RCTLogError(@"Screen presentation updated from modal to push, this may likely result in a screen object leakage. If you need to change presentation style create a new screen object instead");
}
_stackPresentation = stackPresentation;
}
- (void)setStackAnimation:(RNSScreenStackAnimation)stackAnimation
{
_stackAnimation = stackAnimation;
switch (stackAnimation) {
case RNSScreenStackAnimationFade:
_controller.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
break;
#if (TARGET_OS_IOS)
case RNSScreenStackAnimationFlip:
_controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
break;
#endif
case RNSScreenStackAnimationNone:
case RNSScreenStackAnimationDefault:
// Default
break;
}
}
- (void)setGestureEnabled:(BOOL)gestureEnabled
{
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
_controller.modalInPresentation = !gestureEnabled;
}
#endif
_gestureEnabled = gestureEnabled;
}
- (void)setReplaceAnimation:(RNSScreenReplaceAnimation)replaceAnimation
{
_replaceAnimation = replaceAnimation;
}
- (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
{
_dismissed = YES;
if (self.onDismissed) {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.onDismissed) {
self.onDismissed(nil);
}
});
}
}
- (void)notifyWillAppear
{
if (self.onWillAppear) {
self.onWillAppear(nil);
}
// we do it here too because at this moment the `parentViewController` is already not nil,
// so if the parent is not UINavCtr, the frame will be updated to the correct one.
[self reactSetFrame:_reactFrame];
}
- (void)notifyWillDisappear
{
if (self.onWillDisappear) {
self.onWillDisappear(nil);
}
}
- (void)notifyAppear
{
if (self.onAppear) {
dispatch_async(dispatch_get_main_queue(), ^{
if (self.onAppear) {
self.onAppear(nil);
}
});
}
}
- (void)notifyDisappear
{
if (self.onDisappear) {
self.onDisappear(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];
}
}
- (void)invalidate
{
_controller = nil;
}
@end
@implementation RNSScreen {
__weak id _previousFirstResponder;
CGRect _lastViewFrame;
}
- (instancetype)initWithView:(UIView *)view
{
if (self = [super init]) {
self.view = view;
}
return self;
}
- (UIViewController *)childViewControllerForStatusBarStyle
{
UIViewController *vc = [self findChildVCForConfig];
return vc == self ? nil : vc;
}
- (UIStatusBarStyle)preferredStatusBarStyle
{
RNSScreenStackHeaderConfig *config = [self findScreenConfig];
return [RNSScreenStackHeaderConfig statusBarStyleForRNSStatusBarStyle:config && config.statusBarStyle ? config.statusBarStyle : RNSStatusBarStyleAuto];
}
- (UIViewController *)childViewControllerForStatusBarHidden
{
UIViewController *vc = [self findChildVCForConfig];
return vc == self ? nil : vc;
}
- (BOOL)prefersStatusBarHidden
{
RNSScreenStackHeaderConfig *config = [self findScreenConfig];
return config && config.statusBarHidden ? config.statusBarHidden : NO;
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
UIViewController *vc = [self findChildVCForConfig];
if (vc != self && vc != nil) {
return vc.preferredStatusBarUpdateAnimation;
}
RNSScreenStackHeaderConfig *config = [self findScreenConfig];
return config && config.statusBarAnimation ? config.statusBarAnimation : UIStatusBarAnimationFade;
}
// if the returned vc is a child, it means that it can provide config;
// if the returned vc is self, it means that there is no child for config and self has config to provide,
// so we return self which results in asking self for preferredStatusBarStyle;
// if the returned vc is nil, it means none of children could provide config and self does not have config either,
// so if it was asked by parent, it will fallback to parent's option, or use default option if it is the top Screen
- (UIViewController *)findChildVCForConfig
{
UIViewController *lastViewController = [[self childViewControllers] lastObject];
if (self.presentedViewController != nil) {
lastViewController = self.presentedViewController;
// setting this makes the modal vc being asked for appearance,
// so it doesn't matter what we return here since the modal's root screen will be asked
lastViewController.modalPresentationCapturesStatusBarAppearance = YES;
return nil;
}
UIViewController *selfOrNil = [self findScreenConfig] != nil ? self : nil;
if (lastViewController == nil) {
return selfOrNil;
} else {
if ([lastViewController conformsToProtocol:@protocol(RNScreensViewControllerDelegate)]) {
// If there is a child (should be VC of ScreenContainer or ScreenStack), that has a child that could provide config,
// we recursively go into its findChildVCForConfig, and if one of the children has the config, we return it,
// otherwise we return self if this VC has config, and nil if it doesn't
// we use `childViewControllerForStatusBarStyle` for all options since the behavior is the same for all of them
UIViewController *childScreen = [lastViewController childViewControllerForStatusBarStyle];
if ([childScreen isKindOfClass:[RNSScreen class]]) {
return [(RNSScreen *)childScreen findChildVCForConfig] ?: selfOrNil;
} else {
return selfOrNil;
}
} else {
// child vc is not from this library, so we don't ask it
return selfOrNil;
}
}
}
- (RNSScreenStackHeaderConfig *)findScreenConfig
{
for (UIView *subview in self.view.reactSubviews) {
if ([subview isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
return (RNSScreenStackHeaderConfig *)subview;
}
}
return nil;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
// The below code makes the screen view adapt dimensions provided by the system. We take these
// into account only when the view is mounted under UINavigationController in which case system
// provides additional padding to account for possible header, and in the case when screen is
// shown as a native modal, as the final dimensions of the modal on iOS 12+ are shorter than the
// screen size
BOOL isDisplayedWithinUINavController = [self.parentViewController isKindOfClass:[UINavigationController class]];
BOOL isPresentedAsNativeModal = self.parentViewController == nil && self.presentingViewController != nil;
if ((isDisplayedWithinUINavController || isPresentedAsNativeModal) && !CGRectEqualToRect(_lastViewFrame, self.view.frame)) {
_lastViewFrame = self.view.frame;
[((RNSScreenView *)self.viewIfLoaded) 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
{
[super willMoveToParentViewController:parent];
if (parent == nil) {
id responder = [self findFirstResponder:self.view];
if (responder != nil) {
_previousFirstResponder = responder;
}
}
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[RNSScreenStackHeaderConfig updateStatusBarAppearance];
[((RNSScreenView *)self.view) notifyWillAppear];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[((RNSScreenView *)self.view) notifyWillDisappear];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[((RNSScreenView *)self.view) notifyAppear];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
[((RNSScreenView *)self.view) notifyDisappear];
if (self.parentViewController == nil && self.presentingViewController == nil) {
// screen dismissed, send event
[((RNSScreenView *)self.view) notifyDismissed];
}
[self traverseForScrollView:self.view];
}
- (void)traverseForScrollView:(UIView*)view
{
if([view isKindOfClass:[UIScrollView class]] && ([[(UIScrollView*)view delegate] respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) ) {
[[(UIScrollView*)view delegate] scrollViewDidEndDecelerating:(id)view];
}
[view.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[self traverseForScrollView:obj];
}];
}
- (void)notifyFinishTransitioning
{
[_previousFirstResponder becomeFirstResponder];
_previousFirstResponder = nil;
// the correct Screen for appearance is set after the transition
[RNSScreenStackHeaderConfig updateStatusBarAppearance];
}
@end
@implementation RNSScreenManager
RCT_EXPORT_MODULE()
// we want to handle the case when activityState is nil
RCT_REMAP_VIEW_PROPERTY(activityState, activityStateOrNil, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(gestureEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(replaceAnimation, RNSScreenReplaceAnimation)
RCT_EXPORT_VIEW_PROPERTY(stackPresentation, RNSScreenStackPresentation)
RCT_EXPORT_VIEW_PROPERTY(stackAnimation, RNSScreenStackAnimation)
RCT_EXPORT_VIEW_PROPERTY(onWillAppear, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onWillDisappear, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onAppear, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onDisappear, 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),
@"fullScreenModal": @(RNSScreenStackPresentationFullScreenModal),
@"formSheet": @(RNSScreenStackPresentationFormSheet),
@"containedModal": @(RNSScreenStackPresentationContainedModal),
@"transparentModal": @(RNSScreenStackPresentationTransparentModal),
@"containedTransparentModal": @(RNSScreenStackPresentationContainedTransparentModal)
}), RNSScreenStackPresentationPush, integerValue)
RCT_ENUM_CONVERTER(RNSScreenStackAnimation, (@{
@"default": @(RNSScreenStackAnimationDefault),
@"none": @(RNSScreenStackAnimationNone),
@"fade": @(RNSScreenStackAnimationFade),
@"flip": @(RNSScreenStackAnimationFlip),
}), RNSScreenStackAnimationDefault, integerValue)
RCT_ENUM_CONVERTER(RNSScreenReplaceAnimation, (@{
@"push": @(RNSScreenReplaceAnimationPush),
@"pop": @(RNSScreenReplaceAnimationPop),
}), RNSScreenReplaceAnimationPop, integerValue)
@end

View File

@ -0,0 +1,19 @@
#import <React/RCTViewManager.h>
@protocol RNSScreenContainerDelegate
- (void)markChildUpdated;
@end
@protocol RNScreensViewControllerDelegate
@end
@interface RNScreensViewController: UIViewController <RNScreensViewControllerDelegate>
@end
@interface RNSScreenContainerView : UIView <RNSScreenContainerDelegate>
@end

View File

@ -0,0 +1,279 @@
#import "RNSScreenContainer.h"
#import "RNSScreen.h"
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import <React/RCTUIManagerUtils.h>
@interface RNSScreenContainerManager : RCTViewManager
- (void)markUpdated:(RNSScreenContainerView *)screen;
@end
@interface RNSScreenContainerView () <RCTInvalidating>
@property (nonatomic, retain) UIViewController *controller;
@property (nonatomic, retain) NSMutableSet<RNSScreenView *> *activeScreens;
@property (nonatomic, retain) NSMutableArray<RNSScreenView *> *reactSubviews;
- (void)updateContainer;
@end
@implementation RNScreensViewController
- (UIViewController *)childViewControllerForStatusBarStyle
{
return [self findActiveChildVC];
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
return [self findActiveChildVC].preferredStatusBarUpdateAnimation;
}
- (UIViewController *)childViewControllerForStatusBarHidden
{
return [self findActiveChildVC];
}
- (UIViewController *)findActiveChildVC
{
for (UIViewController *childVC in self.childViewControllers) {
if ([childVC isKindOfClass:[RNSScreen class]] && ((RNSScreenView *)((RNSScreen *)childVC.view)).activityState == RNSActivityStateOnTop) {
return childVC;
}
}
return [[self childViewControllers] lastObject];
}
@end
@implementation RNSScreenContainerView {
BOOL _needUpdate;
BOOL _invalidated;
__weak RNSScreenContainerManager *_manager;
}
- (instancetype)initWithManager:(RNSScreenContainerManager *)manager
{
if (self = [super init]) {
_activeScreens = [NSMutableSet new];
_reactSubviews = [NSMutableArray new];
_controller = [[RNScreensViewController alloc] init];
_needUpdate = NO;
_invalidated = NO;
_manager = manager;
[self addSubview:_controller.view];
}
return self;
}
- (void)markChildUpdated
{
// We want 'updateContainer' to be executed on main thread after all enqueued operations in
// uimanager are complete. For that we collect all marked containers in manager class and enqueue
// operation on ui thread that should run once all the updates are completed.
if (!_needUpdate) {
_needUpdate = YES;
[_manager markUpdated:self];
}
}
- (void)insertReactSubview:(RNSScreenView *)subview atIndex:(NSInteger)atIndex
{
subview.reactSuperview = self;
[_reactSubviews insertObject:subview atIndex:atIndex];
[subview reactSetFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
}
- (void)removeReactSubview:(RNSScreenView *)subview
{
subview.reactSuperview = nil;
[_reactSubviews removeObject:subview];
}
- (NSArray<UIView *> *)reactSubviews
{
return _reactSubviews;
}
- (UIViewController *)reactViewController
{
return _controller;
}
- (UIViewController*)findChildControllerForScreen:(RNSScreenView*)screen
{
for (UIViewController *vc in _controller.childViewControllers) {
if (vc.view == screen) {
return vc;
}
}
return nil;
}
- (void)prepareDetach:(RNSScreenView *)screen
{
[[self findChildControllerForScreen:screen] willMoveToParentViewController:nil];
}
- (void)detachScreen:(RNSScreenView *)screen
{
// We use findChildControllerForScreen method instead of directly accesing
// screen.controller because screen.controller may be reset to nil when the
// original screen view gets detached from the view hierarchy (we reset controller
// reference to avoid reference loops)
UIViewController *detachController = [self findChildControllerForScreen:screen];
[detachController willMoveToParentViewController:nil];
[screen removeFromSuperview];
[detachController removeFromParentViewController];
[_activeScreens removeObject:screen];
}
- (void)attachScreen:(RNSScreenView *)screen atIndex:(NSInteger)index
{
[_controller addChildViewController:screen.controller];
// the frame is already set at this moment because we adjust it in insertReactSubview. We don't
// want to update it here as react-driven animations may already run and hence changing the frame
// would result in visual glitches
[_controller.view insertSubview:screen.controller.view atIndex:index];
[screen.controller didMoveToParentViewController:_controller];
[_activeScreens addObject:screen];
}
- (void)updateContainer
{
_needUpdate = NO;
BOOL screenRemoved = NO;
// remove screens that are no longer active
NSMutableSet *orphaned = [NSMutableSet setWithSet:_activeScreens];
for (RNSScreenView *screen in _reactSubviews) {
if (screen.activityState == RNSActivityStateInactive && [_activeScreens containsObject:screen]) {
screenRemoved = YES;
[self detachScreen:screen];
}
[orphaned removeObject:screen];
}
for (RNSScreenView *screen in orphaned) {
screenRemoved = YES;
[self detachScreen:screen];
}
// detect if new screen is going to be activated
BOOL screenAdded = NO;
for (RNSScreenView *screen in _reactSubviews) {
if (screen.activityState != RNSActivityStateInactive && ![_activeScreens containsObject:screen]) {
screenAdded = YES;
}
}
if (screenAdded) {
// add new screens in order they are placed in subviews array
NSInteger index = 0;
for (RNSScreenView *screen in _reactSubviews) {
if (screen.activityState != RNSActivityStateInactive) {
if ([_activeScreens containsObject:screen] && screen.activityState == RNSActivityStateTransitioningOrBelowTop) {
// for screens that were already active we want to mimick the effect UINavigationController
// has when willMoveToWindow:nil is triggered before the animation starts
[self prepareDetach:screen];
} else if (![_activeScreens containsObject:screen]) {
[self attachScreen:screen atIndex:index];
}
index += 1;
}
}
}
if (screenRemoved || screenAdded) {
// we disable interaction for the duration of the transition until one of the screens changes its state to "onTop"
self.userInteractionEnabled = NO;
}
for (RNSScreenView *screen in _reactSubviews) {
if (screen.activityState == RNSActivityStateOnTop) {
// if there is an "onTop" screen it means the transition has ended so we restore interactions
self.userInteractionEnabled = YES;
[screen notifyFinishTransitioning];
}
}
if ((screenRemoved || screenAdded) && _controller.presentedViewController == nil && _controller.presentingViewController == nil) {
// if user has reachability enabled (one hand use) and the window is slided down the below
// method will force it to slide back up as it is expected to happen with UINavController when
// we push or pop views.
// We only do that if `presentedViewController` is nil, as otherwise it'd mean that modal has
// been presented on top of recently changed controller in which case the below method would
// dismiss such a modal (e.g., permission modal or alert)
[_controller dismissViewControllerAnimated:NO completion:nil];
}
}
- (void)didUpdateReactSubviews
{
[self markChildUpdated];
}
- (void)didMoveToWindow
{
if (self.window && !_invalidated) {
// We check whether the view has been invalidated before running side-effects in didMoveToWindow
// This is needed because when LayoutAnimations are used it is possible for view to be re-attached
// to a window despite the fact it has been removed from the React Native view hierarchy.
[self reactAddControllerToClosestParent:_controller];
}
}
- (void)invalidate
{
_invalidated = YES;
[_controller willMoveToParentViewController:nil];
[_controller removeFromParentViewController];
}
- (void)layoutSubviews
{
[super layoutSubviews];
_controller.view.frame = self.bounds;
for (RNSScreenView *subview in _reactSubviews) {
[subview reactSetFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
[subview setNeedsLayout];
}
}
@end
@implementation RNSScreenContainerManager {
NSMutableArray<RNSScreenContainerView *> *_markedContainers;
}
RCT_EXPORT_MODULE()
- (UIView *)view
{
if (!_markedContainers) {
_markedContainers = [NSMutableArray new];
}
return [[RNSScreenContainerView alloc] initWithManager:self];
}
- (void)markUpdated:(RNSScreenContainerView *)screen
{
RCTAssertMainQueue();
[_markedContainers addObject:screen];
if ([_markedContainers count] == 1) {
// we enqueue updates to be run on the main queue in order to make sure that
// all this updates (new screens attached etc) are executed in one batch
RCTExecuteOnMainQueue(^{
for (RNSScreenContainerView *container in self->_markedContainers) {
[container updateContainer];
}
[self->_markedContainers removeAllObjects];
});
}
}
@end

21
node_modules/react-native-screens/ios/RNSScreenStack.h generated vendored Normal file
View File

@ -0,0 +1,21 @@
#import <React/RCTViewManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import "RNSScreenContainer.h"
@interface RNScreensNavigationController: UINavigationController <RNScreensViewControllerDelegate>
@end
@interface RNSScreenStackView : UIView <RNSScreenContainerDelegate, RCTInvalidating>
@property (nonatomic, copy) RCTDirectEventBlock onFinishTransitioning;
- (void)markChildUpdated;
- (void)didUpdateChildren;
@end
@interface RNSScreenStackManager : RCTViewManager <RCTInvalidating>
@end

583
node_modules/react-native-screens/ios/RNSScreenStack.m generated vendored Normal file
View File

@ -0,0 +1,583 @@
#import "RNSScreenStack.h"
#import "RNSScreen.h"
#import "RNSScreenStackHeaderConfig.h"
#import <React/RCTBridge.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerUtils.h>
#import <React/RCTShadowView.h>
#import <React/RCTRootContentView.h>
#import <React/RCTTouchHandler.h>
@interface RNSScreenStackView () <UINavigationControllerDelegate, UIAdaptivePresentationControllerDelegate, UIGestureRecognizerDelegate>
@property (nonatomic) NSMutableArray<UIViewController *> *presentedModals;
@property (nonatomic) BOOL updatingModals;
@property (nonatomic) BOOL scheduleModalsUpdate;
@end
@implementation RNScreensNavigationController
- (UIViewController *)childViewControllerForStatusBarStyle
{
return [self topViewController];
}
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
{
return [self topViewController].preferredStatusBarUpdateAnimation;
}
- (UIViewController *)childViewControllerForStatusBarHidden
{
return [self topViewController];
}
@end
@interface RNSScreenStackAnimator : NSObject <UIViewControllerAnimatedTransitioning>
- (instancetype)initWithOperation:(UINavigationControllerOperation)operation;
@end
@implementation RNSScreenStackView {
UINavigationController *_controller;
NSMutableArray<RNSScreenView *> *_reactSubviews;
__weak RNSScreenStackManager *_manager;
BOOL _hasLayout;
BOOL _invalidated;
}
- (instancetype)initWithManager:(RNSScreenStackManager*)manager
{
if (self = [super init]) {
_hasLayout = NO;
_invalidated = NO;
_manager = manager;
_reactSubviews = [NSMutableArray new];
_presentedModals = [NSMutableArray new];
_controller = [[RNScreensNavigationController alloc] init];
_controller.delegate = self;
// we have to initialize viewControllers with a non empty array for
// largeTitle header to render in the opened state. If it is empty
// the header will render in collapsed state which is perhaps a bug
// in UIKit but ¯\_()_/¯
[_controller setViewControllers:@[[UIViewController new]]];
}
return self;
}
- (UIViewController *)reactViewController
{
return _controller;
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
UIView *view = viewController.view;
RNSScreenStackHeaderConfig *config = nil;
for (UIView *subview in view.reactSubviews) {
if ([subview isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
config = (RNSScreenStackHeaderConfig*) subview;
break;
}
}
[RNSScreenStackHeaderConfig willShowViewController:viewController animated:animated withConfig:config];
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (self.onFinishTransitioning) {
self.onFinishTransitioning(nil);
}
}
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
{
// We don't directly set presentation delegate but instead rely on the ScreenView's delegate to
// forward certain calls to the container (Stack).
UIView *screenView = presentationController.presentedViewController.view;
if ([screenView isKindOfClass:[RNSScreenView class]]) {
// we trigger the update of status bar's appearance here because there is no other lifecycle method
// that can handle it when dismissing a modal
[RNSScreenStackHeaderConfig updateStatusBarAppearance];
[_presentedModals removeObject:presentationController.presentedViewController];
if (self.onFinishTransitioning) {
// instead of directly triggering onFinishTransitioning this time we enqueue the event on the
// main queue. We do that because onDismiss event is also enqueued and we want for the transition
// finish event to arrive later than onDismiss (see RNSScreen#notifyDismiss)
dispatch_async(dispatch_get_main_queue(), ^{
if (self.onFinishTransitioning) {
self.onFinishTransitioning(nil);
}
});
}
}
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
RNSScreenView *screen;
if (operation == UINavigationControllerOperationPush) {
screen = (RNSScreenView *) toVC.view;
} else if (operation == UINavigationControllerOperationPop) {
screen = (RNSScreenView *) fromVC.view;
}
if (screen != nil && (screen.stackAnimation == RNSScreenStackAnimationFade || screen.stackAnimation == RNSScreenStackAnimationNone)) {
return [[RNSScreenStackAnimator alloc] initWithOperation:operation];
}
return nil;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// cancel touches in parent, this is needed to cancel RN touch events. For example when Touchable
// item is close to an edge and we start pulling from edge we want the Touchable to be cancelled.
// Without the below code the Touchable will remain active (highlighted) for the duration of back
// gesture and onPress may fire when we release the finger.
UIView *parent = _controller.view;
while (parent != nil && ![parent isKindOfClass:[RCTRootContentView class]]) parent = parent.superview;
RCTRootContentView *rootView = (RCTRootContentView *)parent;
[rootView.touchHandler cancel];
RNSScreenView *topScreen = (RNSScreenView *)_controller.viewControllers.lastObject.view;
return _controller.viewControllers.count > 1 && topScreen.gestureEnabled;
}
- (void)markChildUpdated
{
// do nothing
}
- (void)didUpdateChildren
{
// do nothing
}
- (void)insertReactSubview:(RNSScreenView *)subview atIndex:(NSInteger)atIndex
{
if (![subview isKindOfClass:[RNSScreenView class]]) {
RCTLogError(@"ScreenStack only accepts children of type Screen");
return;
}
subview.reactSuperview = self;
[_reactSubviews insertObject:subview atIndex:atIndex];
}
- (void)removeReactSubview:(RNSScreenView *)subview
{
subview.reactSuperview = nil;
[_reactSubviews removeObject:subview];
}
- (NSArray<UIView *> *)reactSubviews
{
return _reactSubviews;
}
- (void)didUpdateReactSubviews
{
// 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->_hasLayout = YES;
[self maybeAddToParentAndUpdateContainer];
});
}
- (void)didMoveToWindow
{
[super didMoveToWindow];
if (!_invalidated) {
// We check whether the view has been invalidated before running side-effects in didMoveToWindow
// This is needed because when LayoutAnimations are used it is possible for view to be re-attached
// to a window despite the fact it has been removed from the React Native view hierarchy.
[self maybeAddToParentAndUpdateContainer];
}
}
- (void)maybeAddToParentAndUpdateContainer
{
BOOL wasScreenMounted = _controller.parentViewController != nil;
BOOL isScreenReadyForShowing = self.window && _hasLayout;
if (!isScreenReadyForShowing && !wasScreenMounted) {
// We wait with adding to parent controller until the stack is mounted and has its initial
// layout done.
// If we add it before layout, some of the items (specifically items from the navigation bar),
// won't be able to position properly. Also the position and size of such items, even if it
// happens to change, won't be properly updated (this is perhaps some internal issue of UIKit).
// If we add it when window is not attached, some of the view transitions will be bloced (i.e.
// modal transitions) and the internal view controler's state will get out of sync with what's
// on screen without us knowing.
return;
}
[self updateContainer];
if (!wasScreenMounted) {
// when stack hasn't been added to parent VC yet we do two things:
// 1) we run updateContainer (the one above) we do this because we want push view controllers to
// be installed before the VC is mounted. If we do that after it is added to parent the push
// updates operations are going to be blocked by UIKit.
// 2) we add navigation VS to parent this is needed for the VC lifecycle events to be dispatched
// properly
// 3) we again call updateContainer this time we do this to open modal controllers. Modals
// won't open in (1) because they require navigator to be added to parent. We handle that case
// gracefully in setModalViewControllers and can retry opening at any point.
[self reactAddControllerToClosestParent:_controller];
[self updateContainer];
}
}
- (void)reactAddControllerToClosestParent:(UIViewController *)controller
{
if (!controller.parentViewController) {
UIView *parentView = (UIView *)self.reactSuperview;
while (parentView) {
if (parentView.reactViewController) {
[parentView.reactViewController addChildViewController:controller];
[self addSubview:controller.view];
#if (TARGET_OS_IOS)
_controller.interactivePopGestureRecognizer.delegate = self;
#endif
[controller didMoveToParentViewController:parentView.reactViewController];
// On iOS pre 12 we observed that `willShowViewController` delegate method does not always
// get triggered when the navigation controller is instantiated. As the only thing we do in
// that delegate method is ask nav header to update to the current state it does not hurt to
// trigger that logic from here too such that we can be sure the header is properly updated.
[self navigationController:_controller willShowViewController:_controller.topViewController animated:NO];
break;
}
parentView = (UIView *)parentView.reactSuperview;
}
return;
}
}
- (void)setModalViewControllers:(NSArray<UIViewController *> *)controllers
{
// prevent re-entry
if (_updatingModals) {
_scheduleModalsUpdate = YES;
return;
}
// when there is no change we return immediately. This check is important because sometime we may
// accidently trigger modal dismiss if we don't verify to run the below code only when an actual
// change in the list of presented modal was made.
if ([_presentedModals isEqualToArray:controllers]) {
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 && _presentedModals.lastObject.view.window == nil) {
return;
}
_updatingModals = YES;
NSMutableArray<UIViewController *> *newControllers = [NSMutableArray arrayWithArray:controllers];
[newControllers removeObjectsInArray:_presentedModals];
// find bottom-most controller that should stay on the stack for the duration of transition
NSUInteger changeRootIndex = 0;
UIViewController *changeRootController = _controller;
for (NSUInteger i = 0; i < MIN(_presentedModals.count, controllers.count); i++) {
if (_presentedModals[i] == controllers[i]) {
changeRootController = controllers[i];
changeRootIndex = i + 1;
} else {
break;
}
}
// we verify that controllers added on top of changeRootIndex are all new. Unfortunately modal
// VCs cannot be reshuffled (there are some visual glitches when we try to dismiss then show as
// even non-animated dismissal has delay and updates the screen several times)
for (NSUInteger i = changeRootIndex; i < controllers.count; i++) {
if ([_presentedModals containsObject:controllers[i]]) {
RCTAssert(false, @"Modally presented controllers are being reshuffled, this is not allowed");
}
}
__weak RNSScreenStackView *weakSelf = self;
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) = ^{
NSUInteger oldCount = weakSelf.presentedModals.count;
if (changeRootIndex < oldCount) {
[weakSelf.presentedModals
removeObjectsInRange:NSMakeRange(changeRootIndex, oldCount - changeRootIndex)];
}
BOOL isAttached = changeRootController.parentViewController != nil || changeRootController.presentingViewController != nil;
if (!isAttached || 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);
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
// Inherit UI style from its parent - solves an issue with incorrect style being applied to some UIKit views like date picker or segmented control.
next.overrideUserInterfaceStyle = _controller.overrideUserInterfaceStyle;
}
#endif
BOOL shouldAnimate = lastModal && [next isKindOfClass:[RNSScreen class]] && ((RNSScreenView *) next.view).stackAnimation != RNSScreenStackAnimationNone;
[previous presentViewController:next
animated:shouldAnimate
completion:^{
[weakSelf.presentedModals addObject:next];
if (lastModal) {
afterTransitions();
};
}];
previous = next;
}
}
};
if (changeRootController.presentedViewController != nil
&& [_presentedModals containsObject:changeRootController.presentedViewController]) {
BOOL shouldAnimate = changeRootIndex == controllers.count && [changeRootController.presentedViewController isKindOfClass:[RNSScreen class]] && ((RNSScreenView *) changeRootController.presentedViewController.view).stackAnimation != RNSScreenStackAnimationNone;
[changeRootController
dismissViewControllerAnimated:shouldAnimate
completion:finish];
} else {
finish();
}
}
- (void)setPushViewControllers:(NSArray<UIViewController *> *)controllers
{
// when there is no change we return immediately
if ([_controller.viewControllers isEqualToArray:controllers]) {
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;
}
// when transition is ongoing, any updates made to the controller will not be reflected until the
// transition is complete. In particular, when we push/pop view controllers we expect viewControllers
// property to be updated immediately. Based on that property we then calculate future updates.
// When the transition is ongoing the property won't be updated immediatly. We therefore avoid
// making any updated when transition is ongoing and schedule updates for when the transition
// is complete.
if (_controller.transitionCoordinator != nil) {
__weak RNSScreenStackView *weakSelf = self;
[_controller.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// do nothing here, we only want to be notified when transition is complete
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[weakSelf updateContainer];
}];
return;
}
UIViewController *top = controllers.lastObject;
UIViewController *lastTop = _controller.viewControllers.lastObject;
// at the start we set viewControllers to contain a single UIVIewController
// instance. This is a workaround for header height adjustment bug (see comment
// in the init function). Here, we need to detect if the initial empty
// controller is still there
BOOL firstTimePush = ![lastTop isKindOfClass:[RNSScreen class]];
BOOL shouldAnimate = !firstTimePush && ((RNSScreenView *) lastTop.view).stackAnimation != RNSScreenStackAnimationNone;
if (firstTimePush) {
// nothing pushed yet
[_controller setViewControllers:controllers animated:NO];
} else if (top != lastTop) {
if (![controllers containsObject:lastTop]) {
// if the previous top screen does not exist anymore and the new top was not on the stack before, probably replace was called, so we check the animation
if ( ![_controller.viewControllers containsObject:top] && ((RNSScreenView *) top.view).replaceAnimation == RNSScreenReplaceAnimationPush) {
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:controllers];
[_controller pushViewController:top animated:shouldAnimate];
[_controller setViewControllers:newControllers animated:NO];
} else {
// last top controller is no longer on stack
// in this case we set the controllers stack to the new list with
// added the last top element to it and perform (animated) pop
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:controllers];
[newControllers addObject:lastTop];
[_controller setViewControllers:newControllers animated:NO];
[_controller popViewControllerAnimated:shouldAnimate];
}
} else if (![_controller.viewControllers containsObject:top]) {
// new top controller is not on the stack
// in such case we update the stack except from the last element with
// no animation and do animated push of the last item
NSMutableArray *newControllers = [NSMutableArray arrayWithArray:controllers];
[newControllers removeLastObject];
[_controller setViewControllers:newControllers animated:NO];
[_controller pushViewController:top animated:shouldAnimate];
} else {
// don't really know what this case could be, but may need to handle it
// somehow
[_controller setViewControllers:controllers animated:shouldAnimate];
}
} else {
// change wasn't on the top of the stack. We don't need animation.
[_controller setViewControllers:controllers animated:NO];
}
}
- (void)updateContainer
{
NSMutableArray<UIViewController *> *pushControllers = [NSMutableArray new];
NSMutableArray<UIViewController *> *modalControllers = [NSMutableArray new];
for (RNSScreenView *screen in _reactSubviews) {
if (!screen.dismissed && screen.controller != nil) {
if (pushControllers.count == 0) {
// first screen on the list needs to be places as "push controller"
[pushControllers addObject:screen.controller];
} else {
if (screen.stackPresentation == RNSScreenStackPresentationPush) {
[pushControllers addObject:screen.controller];
} else {
[modalControllers addObject:screen.controller];
}
}
}
}
[self setPushViewControllers:pushControllers];
[self setModalViewControllers:modalControllers];
}
- (void)layoutSubviews
{
[super layoutSubviews];
_controller.view.frame = self.bounds;
}
- (void)invalidate
{
_invalidated = YES;
for (UIViewController *controller in _presentedModals) {
[controller dismissViewControllerAnimated:NO completion:nil];
}
[_presentedModals removeAllObjects];
[_controller willMoveToParentViewController:nil];
[_controller removeFromParentViewController];
}
- (void)dismissOnReload
{
dispatch_async(dispatch_get_main_queue(), ^{
[self invalidate];
});
}
@end
@implementation RNSScreenStackManager {
NSPointerArray *_stacks;
}
RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(onFinishTransitioning, RCTDirectEventBlock);
- (UIView *)view
{
RNSScreenStackView *view = [[RNSScreenStackView alloc] initWithManager:self];
if (!_stacks) {
_stacks = [NSPointerArray weakObjectsPointerArray];
}
[_stacks addPointer:(__bridge void *)view];
return view;
}
- (void)invalidate
{
for (RNSScreenStackView *stack in _stacks) {
[stack dismissOnReload];
}
_stacks = nil;
}
@end
@implementation RNSScreenStackAnimator {
UINavigationControllerOperation _operation;
}
- (instancetype)initWithOperation:(UINavigationControllerOperation)operation
{
if (self = [super init]) {
_operation = operation;
}
return self;
}
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
RNSScreenView *screen;
if (_operation == UINavigationControllerOperationPush) {
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
screen = (RNSScreenView *)toViewController.view;
} else if (_operation == UINavigationControllerOperationPop) {
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
screen = (RNSScreenView *)fromViewController.view;
}
if (screen != nil && screen.stackAnimation == RNSScreenStackAnimationNone) {
return 0;
}
return 0.35; // default duration
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
if (_operation == UINavigationControllerOperationPush) {
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 0.0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
} else if (_operation == UINavigationControllerOperationPop) {
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.alpha = 0.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
}
@end

View File

@ -0,0 +1,74 @@
#import <React/RCTViewManager.h>
#import <React/RCTConvert.h>
#import "RNSScreen.h"
typedef NS_ENUM(NSInteger, RNSStatusBarStyle) {
RNSStatusBarStyleAuto,
RNSStatusBarStyleInverted,
RNSStatusBarStyleLight,
RNSStatusBarStyleDark,
};
@interface RNSScreenStackHeaderConfig : UIView
@property (nonatomic, weak) RNSScreenView *screenView;
@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *titleFontFamily;
@property (nonatomic, retain) NSNumber *titleFontSize;
@property (nonatomic, retain) UIColor *titleColor;
@property (nonatomic, retain) NSString *backTitle;
@property (nonatomic, retain) NSString *backTitleFontFamily;
@property (nonatomic, retain) NSNumber *backTitleFontSize;
@property (nonatomic, retain) UIColor *backgroundColor;
@property (nonatomic) UIBlurEffectStyle blurEffect;
@property (nonatomic, retain) UIColor *color;
@property (nonatomic) BOOL hide;
@property (nonatomic) BOOL largeTitle;
@property (nonatomic, retain) NSString *largeTitleFontFamily;
@property (nonatomic, retain) NSNumber *largeTitleFontSize;
@property (nonatomic, retain) UIColor *largeTitleBackgroundColor;
@property (nonatomic) BOOL largeTitleHideShadow;
@property (nonatomic, retain) UIColor *largeTitleColor;
@property (nonatomic) BOOL hideBackButton;
@property (nonatomic) BOOL backButtonInCustomView;
@property (nonatomic) BOOL hideShadow;
@property (nonatomic) BOOL translucent;
@property (nonatomic) UISemanticContentAttribute direction;
@property (nonatomic) RNSStatusBarStyle statusBarStyle;
@property (nonatomic) UIStatusBarAnimation statusBarAnimation;
@property (nonatomic) BOOL statusBarHidden;
+ (void)willShowViewController:(UIViewController *)vc animated:(BOOL)animated withConfig:(RNSScreenStackHeaderConfig*)config;
+ (void)updateStatusBarAppearance;
+ (UIStatusBarStyle)statusBarStyleForRNSStatusBarStyle:(RNSStatusBarStyle)statusBarStyle;
@end
@interface RNSScreenStackHeaderConfigManager : RCTViewManager
@end
typedef NS_ENUM(NSInteger, RNSScreenStackHeaderSubviewType) {
RNSScreenStackHeaderSubviewTypeBackButton,
RNSScreenStackHeaderSubviewTypeLeft,
RNSScreenStackHeaderSubviewTypeRight,
RNSScreenStackHeaderSubviewTypeTitle,
RNSScreenStackHeaderSubviewTypeCenter,
};
@interface RCTConvert (RNSScreenStackHeader)
+ (RNSScreenStackHeaderSubviewType)RNSScreenStackHeaderSubviewType:(id)json;
+ (UIBlurEffectStyle)UIBlurEffectStyle:(id)json;
+ (UISemanticContentAttribute)UISemanticContentAttribute:(id)json;
+ (RNSStatusBarStyle)RNSStatusBarStyle:(id)json;
@end
@interface RNSScreenStackHeaderSubviewManager : RCTViewManager
@property (nonatomic) RNSScreenStackHeaderSubviewType type;
@end

View File

@ -0,0 +1,664 @@
#import "RNSScreenStackHeaderConfig.h"
#import "RNSScreen.h"
#import <React/RCTBridge.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerUtils.h>
#import <React/RCTShadowView.h>
#import <React/RCTImageLoader.h>
#import <React/RCTImageView.h>
#import <React/RCTImageSource.h>
#import <React/RCTFont.h>
// Some RN private method hacking below. Couldn't figure out better way to access image data
// of a given RCTImageView. See more comments in the code section processing SubviewTypeBackButton
@interface RCTImageView (Private)
- (UIImage*)image;
@end
@interface RCTImageLoader (Private)
- (id<RCTImageCache>)imageCache;
@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 {
NSMutableArray<RNSScreenStackHeaderSubview *> *_reactSubviews;
}
- (instancetype)init
{
if (self = [super init]) {
self.hidden = YES;
_translucent = YES;
_reactSubviews = [NSMutableArray new];
}
return self;
}
- (void)insertReactSubview:(RNSScreenStackHeaderSubview *)subview atIndex:(NSInteger)atIndex
{
[_reactSubviews insertObject:subview atIndex:atIndex];
subview.reactSuperview = self;
}
- (void)removeReactSubview:(RNSScreenStackHeaderSubview *)subview
{
[_reactSubviews removeObject:subview];
}
- (NSArray<UIView *> *)reactSubviews
{
return _reactSubviews;
}
- (UIView *)reactSuperview
{
return _screenView;
}
- (void)removeFromSuperview
{
[super removeFromSuperview];
_screenView = nil;
}
- (void)updateViewControllerIfNeeded
{
UIViewController *vc = _screenView.controller;
UINavigationController *nav = (UINavigationController*) vc.parentViewController;
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
animated:YES];
}
}
- (void)didSetProps:(NSArray<NSString *> *)changedProps
{
[super didSetProps:changedProps];
[self updateViewControllerIfNeeded];
}
- (void)didUpdateReactSubviews
{
[super didUpdateReactSubviews];
[self updateViewControllerIfNeeded];
}
+ (void)setAnimatedConfig:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
{
UINavigationBar *navbar = ((UINavigationController *)vc.parentViewController).navigationBar;
// It is workaround for loading custom back icon when transitioning from a screen without header to the screen which has one.
// This action fails when navigating to the screen with header for the second time and loads default back button.
// It looks like changing the tint color of navbar triggers an update of the items belonging to it and it seems to load the custom back image
// so we change the tint color's alpha by a very small amount and then set it to the one it should have.
[navbar setTintColor:[config.color colorWithAlphaComponent:CGColorGetAlpha(config.color.CGColor) - 0.01]];
[navbar setTintColor:config.color];
#if defined(__IPHONE_13_0) && TARGET_OS_IOS
if (@available(iOS 13.0, *)) {
// font customized on the navigation item level, so nothing to do here
} else
#endif
{
BOOL hideShadow = config.hideShadow;
if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
[navbar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
[navbar setBarTintColor:[UIColor clearColor]];
hideShadow = YES;
} else {
[navbar setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
[navbar setBarTintColor:config.backgroundColor];
}
[navbar setTranslucent:config.translucent];
[navbar setValue:@(hideShadow ? YES : NO) forKey:@"hidesShadow"];
if (config.titleFontFamily || config.titleFontSize || config.titleColor) {
NSMutableDictionary *attrs = [NSMutableDictionary new];
if (config.titleColor) {
attrs[NSForegroundColorAttributeName] = config.titleColor;
}
NSNumber *size = config.titleFontSize ?: @17;
if (config.titleFontFamily) {
attrs[NSFontAttributeName] = [RCTFont updateFont:nil withFamily:config.titleFontFamily size:size weight:nil style:nil variant:nil scaleMultiplier:1.0];
} else {
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
}
[navbar setTitleTextAttributes:attrs];
}
#if (TARGET_OS_IOS)
if (@available(iOS 11.0, *)) {
if (config.largeTitle && (config.largeTitleFontFamily || config.largeTitleFontSize || config.largeTitleColor || config.titleColor)) {
NSMutableDictionary *largeAttrs = [NSMutableDictionary new];
if (config.largeTitleColor || config.titleColor) {
largeAttrs[NSForegroundColorAttributeName] = config.largeTitleColor ? config.largeTitleColor : config.titleColor;
}
NSNumber *largeSize = config.largeTitleFontSize ?: @34;
if (config.largeTitleFontFamily) {
largeAttrs[NSFontAttributeName] = [RCTFont updateFont:nil withFamily:config.largeTitleFontFamily size:largeSize weight:nil style:nil variant:nil scaleMultiplier:1.0];
} else {
largeAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:[largeSize floatValue] weight:UIFontWeightBold];
}
[navbar setLargeTitleTextAttributes:largeAttrs];
}
}
#endif
}
}
+ (void)setTitleAttibutes:(NSDictionary *)attrs forButton:(UIBarButtonItem *)button
{
[button setTitleTextAttributes:attrs forState:UIControlStateNormal];
[button setTitleTextAttributes:attrs forState:UIControlStateHighlighted];
[button setTitleTextAttributes:attrs forState:UIControlStateDisabled];
[button setTitleTextAttributes:attrs forState:UIControlStateSelected];
if (@available(iOS 9.0, *)) {
[button setTitleTextAttributes:attrs forState:UIControlStateFocused];
}
}
+ (UIImage*)loadBackButtonImageInViewController:(UIViewController *)vc
withConfig:(RNSScreenStackHeaderConfig *)config
{
BOOL hasBackButtonImage = NO;
for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
if (subview.type == RNSScreenStackHeaderSubviewTypeBackButton && subview.subviews.count > 0) {
hasBackButtonImage = YES;
RCTImageView *imageView = subview.subviews[0];
if (imageView.image == nil) {
// This is yet another workaround for loading custom back icon. It turns out that under
// certain circumstances image attribute can be null despite the app running in production
// mode (when images are loaded from the filesystem). This can happen because image attribute
// is reset when image view is detached from window, and also in some cases initialization
// does not populate the frame of the image view before the loading start. The latter result
// in the image attribute not being updated. We manually set frame to the size of an image
// in order to trigger proper reload that'd update the image attribute.
RCTImageSource *source = imageView.imageSources[0];
[imageView reactSetFrame:CGRectMake(imageView.frame.origin.x,
imageView.frame.origin.y,
source.size.width,
source.size.height)];
}
UIImage *image = imageView.image;
// IMPORTANT!!!
// image can be nil in DEV MODE ONLY
//
// It is so, because in dev mode images are loaded over HTTP from the packager. In that case
// we first check if image is already loaded in cache and if it is, we take it from cache and
// display immediately. Otherwise we wait for the transition to finish and retry updating
// header config.
// Unfortunately due to some problems in UIKit we cannot update the image while the screen
// transition is ongoing. This results in the settings being reset after the transition is done
// to the state from before the transition.
if (image == nil) {
// in DEV MODE we try to load from cache (we use private API for that as it is not exposed
// publically in headers).
RCTImageSource *source = imageView.imageSources[0];
image = [subview.bridge.imageLoader.imageCache
imageForUrl:source.request.URL.absoluteString
size:source.size
scale:source.scale
resizeMode:imageView.resizeMode];
}
if (image == nil) {
// This will be triggered if the image is not in the cache yet. What we do is we wait until
// the end of transition and run header config updates again. We could potentially wait for
// image on load to trigger, but that would require even more private method hacking.
if (vc.transitionCoordinator) {
[vc.transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// nothing, we just want completion
} completion:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
// in order for new back button image to be loaded we need to trigger another change
// in back button props that'd make UIKit redraw the button. Otherwise the changes are
// not reflected. Here we change back button visibility which is then immediately restored
#if (TARGET_OS_IOS)
vc.navigationItem.hidesBackButton = YES;
#endif
[config updateViewControllerIfNeeded];
}];
}
return [UIImage new];
} else {
return image;
}
}
}
return nil;
}
+ (void)willShowViewController:(UIViewController *)vc animated:(BOOL)animated withConfig:(RNSScreenStackHeaderConfig *)config
{
[self updateViewController:vc withConfig:config animated:animated];
}
#if defined(__IPHONE_13_0) && TARGET_OS_IOS
+ (UINavigationBarAppearance*)buildAppearance:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config
{
UINavigationBarAppearance *appearance = [UINavigationBarAppearance new];
if (config.backgroundColor && CGColorGetAlpha(config.backgroundColor.CGColor) == 0.) {
// transparent background color
[appearance configureWithTransparentBackground];
} else {
[appearance configureWithOpaqueBackground];
}
// set background color if specified
if (config.backgroundColor) {
appearance.backgroundColor = config.backgroundColor;
}
if (config.blurEffect) {
appearance.backgroundEffect = [UIBlurEffect effectWithStyle:config.blurEffect];
}
if (config.hideShadow) {
appearance.shadowColor = nil;
}
if (config.titleFontFamily || config.titleFontSize || config.titleColor) {
NSMutableDictionary *attrs = [NSMutableDictionary new];
if (config.titleColor) {
attrs[NSForegroundColorAttributeName] = config.titleColor;
}
NSNumber *size = config.titleFontSize ?: @17;
if (config.titleFontFamily) {
attrs[NSFontAttributeName] = [RCTFont updateFont:nil withFamily:config.titleFontFamily size:size weight:nil style:nil variant:nil scaleMultiplier:1.0];
} else {
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
}
appearance.titleTextAttributes = attrs;
}
if (config.largeTitleFontFamily || config.largeTitleFontSize || config.largeTitleColor || config.titleColor) {
NSMutableDictionary *largeAttrs = [NSMutableDictionary new];
if (config.largeTitleColor || config.titleColor) {
largeAttrs[NSForegroundColorAttributeName] = config.largeTitleColor ? config.largeTitleColor : config.titleColor;
}
NSNumber *largeSize = config.largeTitleFontSize ?: @34;
if (config.largeTitleFontFamily) {
largeAttrs[NSFontAttributeName] = [RCTFont updateFont:nil withFamily:config.largeTitleFontFamily size:largeSize weight:nil style:nil variant:nil scaleMultiplier:1.0];
} else {
largeAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:[largeSize floatValue] weight:UIFontWeightBold];
}
appearance.largeTitleTextAttributes = largeAttrs;
}
UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
if (backButtonImage) {
[appearance setBackIndicatorImage:backButtonImage transitionMaskImage:backButtonImage];
} else if (appearance.backIndicatorImage) {
[appearance setBackIndicatorImage:nil transitionMaskImage:nil];
}
return appearance;
}
#endif
+ (void)updateViewController:(UIViewController *)vc withConfig:(RNSScreenStackHeaderConfig *)config animated:(BOOL)animated
{
UINavigationItem *navitem = vc.navigationItem;
UINavigationController *navctr = (UINavigationController *)vc.parentViewController;
NSUInteger currentIndex = [navctr.viewControllers indexOfObject:vc];
UINavigationItem *prevItem = currentIndex > 0 ? [navctr.viewControllers objectAtIndex:currentIndex - 1].navigationItem : nil;
BOOL wasHidden = navctr.navigationBarHidden;
BOOL shouldHide = config == nil || config.hide;
if (!shouldHide && !config.translucent) {
// when nav bar is not translucent we chage edgesForExtendedLayout to avoid system laying out
// the screen underneath navigation controllers
vc.edgesForExtendedLayout = UIRectEdgeNone;
} else {
// system default is UIRectEdgeAll
vc.edgesForExtendedLayout = UIRectEdgeAll;
}
[navctr setNavigationBarHidden:shouldHide animated:animated];
#if (TARGET_OS_IOS)
// we put it before check with return because we want to apply changes to status bar even if the header is hidden
if (config != nil) {
if (config.statusBarStyle || config.statusBarAnimation || config.statusBarHidden) {
[RNSScreenStackHeaderConfig assertViewControllerBasedStatusBarAppearenceSet];
if ([vc isKindOfClass:[RNSScreen class]]) {
[RNSScreenStackHeaderConfig updateStatusBarAppearance];
}
}
}
#endif
if (shouldHide) {
return;
}
if (config.direction == UISemanticContentAttributeForceLeftToRight || config.direction == UISemanticContentAttributeForceRightToLeft) {
navctr.view.semanticContentAttribute = config.direction;
navctr.navigationBar.semanticContentAttribute = config.direction;
}
navitem.title = config.title;
#if (TARGET_OS_IOS)
if (config.backTitle != nil || config.backTitleFontFamily || config.backTitleFontSize) {
prevItem.backBarButtonItem = [[UIBarButtonItem alloc]
initWithTitle:config.backTitle ?: prevItem.title
style:UIBarButtonItemStylePlain
target:nil
action:nil];
if (config.backTitleFontFamily || config.backTitleFontSize) {
NSMutableDictionary *attrs = [NSMutableDictionary new];
NSNumber *size = config.backTitleFontSize ?: @17;
if (config.backTitleFontFamily) {
attrs[NSFontAttributeName] = [RCTFont updateFont:nil withFamily:config.backTitleFontFamily size:size weight:nil style:nil variant:nil scaleMultiplier:1.0];
} else {
attrs[NSFontAttributeName] = [UIFont boldSystemFontOfSize:[size floatValue]];
}
[self setTitleAttibutes:attrs forButton:prevItem.backBarButtonItem];
}
} else {
prevItem.backBarButtonItem = nil;
}
if (@available(iOS 11.0, *)) {
if (config.largeTitle) {
navctr.navigationBar.prefersLargeTitles = YES;
}
navitem.largeTitleDisplayMode = config.largeTitle ? UINavigationItemLargeTitleDisplayModeAlways : UINavigationItemLargeTitleDisplayModeNever;
}
#endif
#if defined(__IPHONE_13_0) && TARGET_OS_IOS
if (@available(iOS 13.0, *)) {
UINavigationBarAppearance *appearance = [self buildAppearance:vc withConfig:config];
navitem.standardAppearance = appearance;
navitem.compactAppearance = appearance;
UINavigationBarAppearance *scrollEdgeAppearance = [[UINavigationBarAppearance alloc] initWithBarAppearance:appearance];
if (config.largeTitleBackgroundColor != nil) {
scrollEdgeAppearance.backgroundColor = config.largeTitleBackgroundColor;
}
if (config.largeTitleHideShadow) {
scrollEdgeAppearance.shadowColor = nil;
}
navitem.scrollEdgeAppearance = scrollEdgeAppearance;
} else
#endif
{
#if (TARGET_OS_IOS)
// updating backIndicatotImage does not work when called during transition. On iOS pre 13 we need
// to update it before the navigation starts.
UIImage *backButtonImage = [self loadBackButtonImageInViewController:vc withConfig:config];
if (backButtonImage) {
navctr.navigationBar.backIndicatorImage = backButtonImage;
navctr.navigationBar.backIndicatorTransitionMaskImage = backButtonImage;
} else if (navctr.navigationBar.backIndicatorImage) {
navctr.navigationBar.backIndicatorImage = nil;
navctr.navigationBar.backIndicatorTransitionMaskImage = nil;
}
#endif
}
#if (TARGET_OS_IOS)
navitem.hidesBackButton = config.hideBackButton;
#endif
navitem.leftBarButtonItem = nil;
navitem.rightBarButtonItem = nil;
navitem.titleView = nil;
for (RNSScreenStackHeaderSubview *subview in config.reactSubviews) {
switch (subview.type) {
case RNSScreenStackHeaderSubviewTypeLeft: {
#if (TARGET_OS_IOS)
navitem.leftItemsSupplementBackButton = config.backButtonInCustomView;
#endif
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithCustomView:subview];
navitem.leftBarButtonItem = buttonItem;
break;
}
case RNSScreenStackHeaderSubviewTypeRight: {
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithCustomView:subview];
navitem.rightBarButtonItem = buttonItem;
break;
}
case RNSScreenStackHeaderSubviewTypeCenter:
case RNSScreenStackHeaderSubviewTypeTitle: {
navitem.titleView = subview;
break;
}
}
}
if (animated
&& 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) {
if ([context isCancelled]) {
UIViewController* fromVC = [context viewControllerForKey:UITransitionContextFromViewControllerKey];
RNSScreenStackHeaderConfig* config = nil;
for (UIView *subview in fromVC.view.reactSubviews) {
if ([subview isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
config = (RNSScreenStackHeaderConfig*) subview;
break;
}
}
[self setAnimatedConfig:fromVC withConfig:config];
}
}];
} else {
[self setAnimatedConfig:vc withConfig:config];
}
}
+ (void)assertViewControllerBasedStatusBarAppearenceSet
{
static dispatch_once_t once;
static bool viewControllerBasedAppearence;
dispatch_once(&once, ^{
viewControllerBasedAppearence = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"] boolValue];
});
if (!viewControllerBasedAppearence) {
RCTLogError(@"If you want to change the appearance of status bar, you have to change \
UIViewControllerBasedStatusBarAppearance key in the Info.plist to YES");
}
}
+ (void)updateStatusBarAppearance
{
[UIView animateWithDuration:0.4 animations:^{ // duration based on "Programming iOS 13" p. 311 implementation
if (@available(iOS 13, *)) {
UIWindow *firstWindow = [[[UIApplication sharedApplication] windows] firstObject];
if (firstWindow != nil) {
[[firstWindow rootViewController] setNeedsStatusBarAppearanceUpdate];
}
} else {
[UIApplication.sharedApplication.keyWindow.rootViewController setNeedsStatusBarAppearanceUpdate];
}
}];
}
+ (UIStatusBarStyle)statusBarStyleForRNSStatusBarStyle:(RNSStatusBarStyle)statusBarStyle
{
#ifdef __IPHONE_13_0
if (@available(iOS 13.0, *)) {
switch (statusBarStyle) {
case RNSStatusBarStyleAuto:
return [UITraitCollection.currentTraitCollection userInterfaceStyle] == UIUserInterfaceStyleDark ? UIStatusBarStyleLightContent : UIStatusBarStyleDarkContent;
case RNSStatusBarStyleInverted:
return [UITraitCollection.currentTraitCollection userInterfaceStyle] == UIUserInterfaceStyleDark ? UIStatusBarStyleDarkContent : UIStatusBarStyleLightContent;
case RNSStatusBarStyleLight:
return UIStatusBarStyleLightContent;
case RNSStatusBarStyleDark:
return UIStatusBarStyleDarkContent;
default:
return UIStatusBarStyleLightContent;
}
}
#endif
return UIStatusBarStyleLightContent;
}
@end
@implementation RNSScreenStackHeaderConfigManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [RNSScreenStackHeaderConfig new];
}
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
RCT_EXPORT_VIEW_PROPERTY(titleFontFamily, NSString)
RCT_EXPORT_VIEW_PROPERTY(titleFontSize, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(titleColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(backTitle, NSString)
RCT_EXPORT_VIEW_PROPERTY(backTitleFontFamily, NSString)
RCT_EXPORT_VIEW_PROPERTY(backTitleFontSize, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(blurEffect, UIBlurEffectStyle)
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
RCT_EXPORT_VIEW_PROPERTY(direction, UISemanticContentAttribute)
RCT_EXPORT_VIEW_PROPERTY(largeTitle, BOOL)
RCT_EXPORT_VIEW_PROPERTY(largeTitleFontFamily, NSString)
RCT_EXPORT_VIEW_PROPERTY(largeTitleFontSize, NSNumber)
RCT_EXPORT_VIEW_PROPERTY(largeTitleColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(largeTitleBackgroundColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(largeTitleHideShadow, BOOL)
RCT_EXPORT_VIEW_PROPERTY(hideBackButton, BOOL)
RCT_EXPORT_VIEW_PROPERTY(hideShadow, BOOL)
RCT_EXPORT_VIEW_PROPERTY(backButtonInCustomView, 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(statusBarStyle, RNSStatusBarStyle)
RCT_EXPORT_VIEW_PROPERTY(statusBarAnimation, UIStatusBarAnimation)
RCT_EXPORT_VIEW_PROPERTY(statusBarHidden, BOOL)
@end
@implementation RCTConvert (RNSScreenStackHeader)
+ (NSMutableDictionary *)blurEffectsForIOSVersion
{
NSMutableDictionary *blurEffects = [NSMutableDictionary new];
[blurEffects addEntriesFromDictionary:@{
@"extraLight": @(UIBlurEffectStyleExtraLight),
@"light": @(UIBlurEffectStyleLight),
@"dark": @(UIBlurEffectStyleDark),
}];
if (@available(iOS 10.0, *)) {
[blurEffects addEntriesFromDictionary:@{
@"regular": @(UIBlurEffectStyleRegular),
@"prominent": @(UIBlurEffectStyleProminent),
}];
}
#if defined(__IPHONE_13_0) && TARGET_OS_IOS
if (@available(iOS 13.0, *)) {
[blurEffects addEntriesFromDictionary:@{
@"systemUltraThinMaterial": @(UIBlurEffectStyleSystemUltraThinMaterial),
@"systemThinMaterial": @(UIBlurEffectStyleSystemThinMaterial),
@"systemMaterial": @(UIBlurEffectStyleSystemMaterial),
@"systemThickMaterial": @(UIBlurEffectStyleSystemThickMaterial),
@"systemChromeMaterial": @(UIBlurEffectStyleSystemChromeMaterial),
@"systemUltraThinMaterialLight": @(UIBlurEffectStyleSystemUltraThinMaterialLight),
@"systemThinMaterialLight": @(UIBlurEffectStyleSystemThinMaterialLight),
@"systemMaterialLight": @(UIBlurEffectStyleSystemMaterialLight),
@"systemThickMaterialLight": @(UIBlurEffectStyleSystemThickMaterialLight),
@"systemChromeMaterialLight": @(UIBlurEffectStyleSystemChromeMaterialLight),
@"systemUltraThinMaterialDark": @(UIBlurEffectStyleSystemUltraThinMaterialDark),
@"systemThinMaterialDark": @(UIBlurEffectStyleSystemThinMaterialDark),
@"systemMaterialDark": @(UIBlurEffectStyleSystemMaterialDark),
@"systemThickMaterialDark": @(UIBlurEffectStyleSystemThickMaterialDark),
@"systemChromeMaterialDark": @(UIBlurEffectStyleSystemChromeMaterialDark),
}];
}
#endif
return blurEffects;
}
RCT_ENUM_CONVERTER(RNSScreenStackHeaderSubviewType, (@{
@"back": @(RNSScreenStackHeaderSubviewTypeBackButton),
@"left": @(RNSScreenStackHeaderSubviewTypeLeft),
@"right": @(RNSScreenStackHeaderSubviewTypeRight),
@"title": @(RNSScreenStackHeaderSubviewTypeTitle),
@"center": @(RNSScreenStackHeaderSubviewTypeCenter),
}), RNSScreenStackHeaderSubviewTypeTitle, integerValue)
RCT_ENUM_CONVERTER(UISemanticContentAttribute, (@{
@"ltr": @(UISemanticContentAttributeForceLeftToRight),
@"rtl": @(UISemanticContentAttributeForceRightToLeft),
}), UISemanticContentAttributeUnspecified, integerValue)
RCT_ENUM_CONVERTER(UIBlurEffectStyle, ([self blurEffectsForIOSVersion]), UIBlurEffectStyleExtraLight, integerValue)
RCT_ENUM_CONVERTER(RNSStatusBarStyle, (@{
@"auto": @(RNSStatusBarStyleAuto),
@"inverted": @(RNSStatusBarStyleInverted),
@"light": @(RNSStatusBarStyleLight),
@"dark": @(RNSStatusBarStyleDark),
}), RNSStatusBarStyleAuto, integerValue)
@end
@implementation RNSScreenStackHeaderSubviewManager
RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(type, RNSScreenStackHeaderSubviewType)
- (UIView *)view
{
return [[RNSScreenStackHeaderSubview alloc] initWithBridge:self.bridge];
}
@end

View File

@ -0,0 +1,457 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
442389EC22DF259000611BBE /* RNSScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 442389EB22DF259000611BBE /* RNSScreen.m */; };
448078F52114595900280661 /* RNSScreenContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 448078F12114595900280661 /* RNSScreenContainer.m */; };
4482D5EF22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 4482D5EE22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.m */; };
44A67C3122C3B8B40017156F /* RNSScreenStack.m in Sources */ = {isa = PBXBuildFile; fileRef = 44A67C3022C3B8B40017156F /* RNSScreenStack.m */; };
B5C32A48220C6379000FFB8D /* RNSScreenContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 448078F12114595900280661 /* RNSScreenContainer.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B5C32A4A220C6379000FFB8D /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNScreens.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNScreens.a; sourceTree = BUILT_PRODUCTS_DIR; };
442389EB22DF259000611BBE /* RNSScreen.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSScreen.m; sourceTree = "<group>"; };
448078EF2114595900280661 /* RNSScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSScreen.h; sourceTree = "<group>"; };
448078F02114595900280661 /* RNSScreenContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSScreenContainer.h; sourceTree = "<group>"; };
448078F12114595900280661 /* RNSScreenContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSScreenContainer.m; sourceTree = "<group>"; };
4482D5ED22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSScreenStackHeaderConfig.h; sourceTree = "<group>"; };
4482D5EE22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNSScreenStackHeaderConfig.m; sourceTree = "<group>"; };
44A67C2F22C3B8B40017156F /* RNSScreenStack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSScreenStack.h; sourceTree = "<group>"; };
44A67C3022C3B8B40017156F /* RNSScreenStack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSScreenStack.m; sourceTree = "<group>"; };
B5C32A4F220C6379000FFB8D /* libRNScreens-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNScreens-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
B5C32A49220C6379000FFB8D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNScreens.a */,
);
name = Products;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
4482D5ED22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.h */,
4482D5EE22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.m */,
44A67C2F22C3B8B40017156F /* RNSScreenStack.h */,
44A67C3022C3B8B40017156F /* RNSScreenStack.m */,
448078EF2114595900280661 /* RNSScreen.h */,
442389EB22DF259000611BBE /* RNSScreen.m */,
448078F02114595900280661 /* RNSScreenContainer.h */,
448078F12114595900280661 /* RNSScreenContainer.m */,
134814211AA4EA7D00B7C361 /* Products */,
B5C32A4F220C6379000FFB8D /* libRNScreens-tvOS.a */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* RNScreens */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNScreens" */;
buildPhases = (
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNScreens;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNScreens.a */;
productType = "com.apple.product-type.library.static";
};
B5C32A45220C6379000FFB8D /* RNScreens-tvOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = B5C32A4B220C6379000FFB8D /* Build configuration list for PBXNativeTarget "RNScreens-tvOS" */;
buildPhases = (
B5C32A46220C6379000FFB8D /* Sources */,
B5C32A49220C6379000FFB8D /* Frameworks */,
B5C32A4A220C6379000FFB8D /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "RNScreens-tvOS";
productName = RCTDataManager;
productReference = B5C32A4F220C6379000FFB8D /* libRNScreens-tvOS.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 920;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNScreens" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNScreens */,
B5C32A45220C6379000FFB8D /* RNScreens-tvOS */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
442389EC22DF259000611BBE /* RNSScreen.m in Sources */,
448078F52114595900280661 /* RNSScreenContainer.m in Sources */,
44A67C3122C3B8B40017156F /* RNSScreenStack.m in Sources */,
4482D5EF22CB391800D5A5B9 /* RNSScreenStackHeaderConfig.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
B5C32A46220C6379000FFB8D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B5C32A48220C6379000FFB8D /* RNSScreenContainer.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
0CE596A6BAEE45CA860361AD /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNScreens;
SKIP_INSTALL = YES;
};
name = Testflight;
};
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNScreens;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNScreens;
SKIP_INSTALL = YES;
};
name = Release;
};
B5C32A4C220C6379000FFB8D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
};
name = Debug;
};
B5C32A4D220C6379000FFB8D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
};
name = Release;
};
B5C32A4E220C6379000FFB8D /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
};
name = Testflight;
};
C7F03305A3464E75B4F5A6CE /* Testflight */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Testflight;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNScreens" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
C7F03305A3464E75B4F5A6CE /* Testflight */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNScreens" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
0CE596A6BAEE45CA860361AD /* Testflight */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
B5C32A4B220C6379000FFB8D /* Build configuration list for PBXNativeTarget "RNScreens-tvOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
B5C32A4C220C6379000FFB8D /* Debug */,
B5C32A4D220C6379000FFB8D /* Release */,
B5C32A4E220C6379000FFB8D /* Testflight */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

View File

@ -0,0 +1,9 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIViewController (RNScreens)
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,52 @@
#import "UIViewController+RNScreens.h"
#import "RNSScreenContainer.h"
#import <objc/runtime.h>
@implementation UIViewController (RNScreens)
- (UIViewController *)reactNativeScreensChildViewControllerForStatusBarStyle
{
UIViewController *childVC = [self findChildRNScreensViewController];
return childVC ?: [self reactNativeScreensChildViewControllerForStatusBarStyle];
}
- (UIViewController *)reactNativeScreensChildViewControllerForStatusBarHidden
{
UIViewController *childVC = [self findChildRNScreensViewController];
return childVC ?: [self reactNativeScreensChildViewControllerForStatusBarHidden];
}
- (UIStatusBarAnimation)reactNativeScreensPreferredStatusBarUpdateAnimation
{
UIViewController *childVC = [self findChildRNScreensViewController];
return childVC ? childVC.preferredStatusBarUpdateAnimation : [self reactNativeScreensPreferredStatusBarUpdateAnimation];
}
- (UIViewController *)findChildRNScreensViewController
{
UIViewController *lastViewController = [[self childViewControllers] lastObject];
if ([lastViewController conformsToProtocol:@protocol(RNScreensViewControllerDelegate)]) {
return lastViewController;
}
return nil;
}
+ (void)load
{
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
Class uiVCClass = [UIViewController class];
method_exchangeImplementations(class_getInstanceMethod(uiVCClass, @selector(childViewControllerForStatusBarStyle)),
class_getInstanceMethod(uiVCClass, @selector(reactNativeScreensChildViewControllerForStatusBarStyle)));
method_exchangeImplementations(class_getInstanceMethod(uiVCClass, @selector(childViewControllerForStatusBarHidden)),
class_getInstanceMethod(uiVCClass, @selector(reactNativeScreensChildViewControllerForStatusBarHidden)));
method_exchangeImplementations(class_getInstanceMethod(uiVCClass, @selector(preferredStatusBarUpdateAnimation)),
class_getInstanceMethod(uiVCClass, @selector(reactNativeScreensPreferredStatusBarUpdateAnimation)));
});
}
@end