285 lines
8.1 KiB
Plaintext
285 lines
8.1 KiB
Plaintext
#import <UIKit/UIKit.h>
|
|
|
|
#import "RNSDefines.h"
|
|
#import "RNSFullWindowOverlay.h"
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
#import <React/RCTConversions.h>
|
|
#import <React/RCTFabricComponentsPlugins.h>
|
|
#import <React/RCTSurfaceTouchHandler.h>
|
|
#import <react/renderer/components/rnscreens/ComponentDescriptors.h>
|
|
#import <react/renderer/components/rnscreens/Props.h>
|
|
#import <react/renderer/components/rnscreens/RCTComponentViewHelpers.h>
|
|
#import <rnscreens/RNSFullWindowOverlayComponentDescriptor.h>
|
|
#else
|
|
#import <React/RCTTouchHandler.h>
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
@implementation RNSFullWindowOverlayContainer
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame accessibilityViewIsModal:(BOOL)accessibilityViewIsModal
|
|
{
|
|
if (self = [super initWithFrame:frame]) {
|
|
self.accessibilityViewIsModal = accessibilityViewIsModal;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
|
|
{
|
|
for (UIView *view in [self subviews]) {
|
|
if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
|
{
|
|
BOOL canReceiveTouchEvents = ([self isUserInteractionEnabled] && ![self isHidden]);
|
|
if (!canReceiveTouchEvents) {
|
|
return nil;
|
|
}
|
|
|
|
// `hitSubview` is the topmost subview which was hit. The hit point can
|
|
// be outside the bounds of `view` (e.g., if -clipsToBounds is NO).
|
|
UIView *hitSubview = nil;
|
|
BOOL isPointInside = [self pointInside:point withEvent:event];
|
|
if (![self clipsToBounds] || isPointInside) {
|
|
// Take z-index into account when calculating the touch target.
|
|
NSArray<UIView *> *sortedSubviews = [self reactZIndexSortedSubviews];
|
|
|
|
// The default behaviour of UIKit is that if a view does not contain a point,
|
|
// then no subviews will be returned from hit testing, even if they contain
|
|
// the hit point. By doing hit testing directly on the subviews, we bypass
|
|
// the strict containment policy (i.e., UIKit guarantees that every ancestor
|
|
// of the hit view will return YES from -pointInside:withEvent:). See:
|
|
// - https://developer.apple.com/library/ios/qa/qa2013/qa1812.html
|
|
for (UIView *subview in [sortedSubviews reverseObjectEnumerator]) {
|
|
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
|
|
hitSubview = [subview hitTest:convertedPoint withEvent:event];
|
|
if (hitSubview != nil) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return hitSubview;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RNSFullWindowOverlay {
|
|
__weak RCTBridge *_bridge;
|
|
RNSFullWindowOverlayContainer *_container;
|
|
CGRect _reactFrame;
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
RCTSurfaceTouchHandler *_touchHandler;
|
|
#else
|
|
RCTTouchHandler *_touchHandler;
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
}
|
|
|
|
// Needed because of this: https://github.com/facebook/react-native/pull/37274
|
|
+ (void)load
|
|
{
|
|
[super load];
|
|
}
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
- (instancetype)init
|
|
{
|
|
if (self = [super init]) {
|
|
static const auto defaultProps = std::make_shared<const react::RNSFullWindowOverlayProps>();
|
|
_props = defaultProps;
|
|
[self initCommonProps];
|
|
}
|
|
return self;
|
|
}
|
|
#else
|
|
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
|
{
|
|
if (self = [super init]) {
|
|
_bridge = bridge;
|
|
[self initCommonProps];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
- (void)initCommonProps
|
|
{
|
|
// Default value used by container.
|
|
_accessibilityContainerViewIsModal = YES;
|
|
_reactFrame = CGRectNull;
|
|
_container = self.container;
|
|
[self show];
|
|
}
|
|
|
|
- (void)setAccessibilityContainerViewIsModal:(BOOL)accessibilityContainerViewIsModal
|
|
{
|
|
_accessibilityContainerViewIsModal = accessibilityContainerViewIsModal;
|
|
self.container.accessibilityViewIsModal = accessibilityContainerViewIsModal;
|
|
}
|
|
|
|
- (void)addSubview:(UIView *)view
|
|
{
|
|
[_container addSubview:view];
|
|
}
|
|
|
|
- (RNSFullWindowOverlayContainer *)container
|
|
{
|
|
if (_container == nil) {
|
|
_container = [[RNSFullWindowOverlayContainer alloc] initWithFrame:_reactFrame
|
|
accessibilityViewIsModal:_accessibilityContainerViewIsModal];
|
|
}
|
|
|
|
return _container;
|
|
}
|
|
|
|
- (void)show
|
|
{
|
|
UIWindow *window = RCTKeyWindow();
|
|
[window addSubview:_container];
|
|
}
|
|
|
|
- (void)didMoveToSuperview
|
|
{
|
|
if (self.superview == nil) {
|
|
if (_container != nil) {
|
|
[_container removeFromSuperview];
|
|
[_touchHandler detachFromView:_container];
|
|
}
|
|
} else {
|
|
if (_container != nil) {
|
|
UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, _container);
|
|
}
|
|
if (_touchHandler == nil) {
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
_touchHandler = [RCTSurfaceTouchHandler new];
|
|
#else
|
|
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
|
|
#endif
|
|
}
|
|
[_touchHandler attachToView:_container];
|
|
}
|
|
}
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
#pragma mark - Fabric Specific
|
|
|
|
// When the component unmounts we remove it from window's children,
|
|
// so when the component gets recycled we need to add it back.
|
|
- (void)maybeShow
|
|
{
|
|
UIWindow *window = RCTKeyWindow();
|
|
if (![[window subviews] containsObject:self]) {
|
|
[window addSubview:_container];
|
|
}
|
|
}
|
|
|
|
+ (react::ComponentDescriptorProvider)componentDescriptorProvider
|
|
{
|
|
return react::concreteComponentDescriptorProvider<react::RNSFullWindowOverlayComponentDescriptor>();
|
|
}
|
|
|
|
- (void)prepareForRecycle
|
|
{
|
|
[_container removeFromSuperview];
|
|
// Due to view recycling we don't really want to set _container = nil
|
|
// as it won't be instantiated when the component appears for the second time.
|
|
// We could consider nulling in here & using container (lazy getter) everywhere else.
|
|
// _container = nil;
|
|
[super prepareForRecycle];
|
|
}
|
|
|
|
- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
|
|
{
|
|
// When the component unmounts we remove it from window's children,
|
|
// so when the component gets recycled we need to add it back.
|
|
// As for now it is called here as we lack of method that is called
|
|
// just before component gets restored (from recycle pool).
|
|
[self maybeShow];
|
|
[self addSubview:childComponentView];
|
|
}
|
|
|
|
- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
|
|
{
|
|
[childComponentView removeFromSuperview];
|
|
}
|
|
|
|
// We do not set frame for ouselves, but rather for the container.
|
|
RNS_IGNORE_SUPER_CALL_BEGIN
|
|
- (void)updateLayoutMetrics:(react::LayoutMetrics const &)layoutMetrics
|
|
oldLayoutMetrics:(react::LayoutMetrics const &)oldLayoutMetrics
|
|
{
|
|
CGRect frame = RCTCGRectFromRect(layoutMetrics.frame);
|
|
|
|
// Due to view flattening on new architecture there are situations
|
|
// when we receive frames with origin different from (0, 0).
|
|
// We account for this frame manipulation in shadow node by setting
|
|
// RootNodeKind trait for the shadow node making state consistent
|
|
// between Host & Shadow Tree
|
|
frame.origin = CGPointZero;
|
|
|
|
_reactFrame = frame;
|
|
[_container setFrame:frame];
|
|
}
|
|
RNS_IGNORE_SUPER_CALL_END
|
|
|
|
- (void)updateProps:(const facebook::react::Props::Shared &)props
|
|
oldProps:(const facebook::react::Props::Shared &)oldProps
|
|
{
|
|
const auto &oldComponentProps = *std::static_pointer_cast<const react::RNSFullWindowOverlayProps>(_props);
|
|
const auto &newComponentProps = *std::static_pointer_cast<const react::RNSFullWindowOverlayProps>(props);
|
|
|
|
if (newComponentProps.accessibilityContainerViewIsModal != oldComponentProps.accessibilityContainerViewIsModal) {
|
|
[self setAccessibilityContainerViewIsModal:newComponentProps.accessibilityContainerViewIsModal];
|
|
}
|
|
|
|
[super updateProps:props oldProps:oldProps];
|
|
}
|
|
|
|
#else
|
|
#pragma mark - Paper specific
|
|
|
|
- (void)reactSetFrame:(CGRect)frame
|
|
{
|
|
_reactFrame = frame;
|
|
[_container setFrame:frame];
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
[_container removeFromSuperview];
|
|
_container = nil;
|
|
}
|
|
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
@end
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
Class<RCTComponentViewProtocol> RNSFullWindowOverlayCls(void)
|
|
{
|
|
return RNSFullWindowOverlay.class;
|
|
}
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
@implementation RNSFullWindowOverlayManager
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
RCT_EXPORT_VIEW_PROPERTY(accessibilityContainerViewIsModal, BOOL)
|
|
|
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
#else
|
|
- (UIView *)view
|
|
{
|
|
return [[RNSFullWindowOverlay alloc] initWithBridge:self.bridge];
|
|
}
|
|
#endif // RCT_NEW_ARCH_ENABLED
|
|
|
|
@end
|