UNPKG

6.46 kBPlain TextView Raw
1#import <UIKit/UIKit.h>
2
3#import "RNSFullWindowOverlay.h"
4
5#ifdef RCT_NEW_ARCH_ENABLED
6#import <React/RCTConversions.h>
7#import <React/RCTFabricComponentsPlugins.h>
8#import <React/RCTSurfaceTouchHandler.h>
9#import <react/renderer/components/rnscreens/ComponentDescriptors.h>
10#import <react/renderer/components/rnscreens/Props.h>
11#import <react/renderer/components/rnscreens/RCTComponentViewHelpers.h>
12#else
13#import <React/RCTTouchHandler.h>
14#endif // RCT_NEW_ARCH_ENABLED
15
16@implementation RNSFullWindowOverlayContainer
17
18- (instancetype)initWithFrame:(CGRect)frame
19{
20 if (self = [super initWithFrame:frame]) {
21 self.accessibilityViewIsModal = YES;
22 }
23 return self;
24}
25
26- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
27{
28 for (UIView *view in [self subviews]) {
29 if (view.userInteractionEnabled && [view pointInside:[self convertPoint:point toView:view] withEvent:event]) {
30 return YES;
31 }
32 }
33 return NO;
34}
35
36- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
37{
38 BOOL canReceiveTouchEvents = ([self isUserInteractionEnabled] && ![self isHidden]);
39 if (!canReceiveTouchEvents) {
40 return nil;
41 }
42
43 // `hitSubview` is the topmost subview which was hit. The hit point can
44 // be outside the bounds of `view` (e.g., if -clipsToBounds is NO).
45 UIView *hitSubview = nil;
46 BOOL isPointInside = [self pointInside:point withEvent:event];
47 if (![self clipsToBounds] || isPointInside) {
48 // Take z-index into account when calculating the touch target.
49 NSArray<UIView *> *sortedSubviews = [self reactZIndexSortedSubviews];
50
51 // The default behaviour of UIKit is that if a view does not contain a point,
52 // then no subviews will be returned from hit testing, even if they contain
53 // the hit point. By doing hit testing directly on the subviews, we bypass
54 // the strict containment policy (i.e., UIKit guarantees that every ancestor
55 // of the hit view will return YES from -pointInside:withEvent:). See:
56 // - https://developer.apple.com/library/ios/qa/qa2013/qa1812.html
57 for (UIView *subview in [sortedSubviews reverseObjectEnumerator]) {
58 CGPoint convertedPoint = [subview convertPoint:point fromView:self];
59 hitSubview = [subview hitTest:convertedPoint withEvent:event];
60 if (hitSubview != nil) {
61 break;
62 }
63 }
64 }
65 return hitSubview;
66}
67
68@end
69
70@implementation RNSFullWindowOverlay {
71 __weak RCTBridge *_bridge;
72 RNSFullWindowOverlayContainer *_container;
73 CGRect _reactFrame;
74#ifdef RCT_NEW_ARCH_ENABLED
75 RCTSurfaceTouchHandler *_touchHandler;
76#else
77 RCTTouchHandler *_touchHandler;
78#endif // RCT_NEW_ARCH_ENABLED
79}
80
81#ifdef RCT_NEW_ARCH_ENABLED
82- (instancetype)init
83{
84 if (self = [super init]) {
85 static const auto defaultProps = std::make_shared<const react::RNSFullWindowOverlayProps>();
86 _props = defaultProps;
87 [self _initCommon];
88 }
89 return self;
90}
91#endif // RCT_NEW_ARCH_ENABLED
92
93- (instancetype)initWithBridge:(RCTBridge *)bridge
94{
95 if (self = [super init]) {
96 _bridge = bridge;
97 [self _initCommon];
98 }
99
100 return self;
101}
102
103- (void)_initCommon
104{
105 _reactFrame = CGRectNull;
106 _container = self.container;
107 [self show];
108}
109
110- (void)addSubview:(UIView *)view
111{
112 [_container addSubview:view];
113}
114
115- (RNSFullWindowOverlayContainer *)container
116{
117 if (_container == nil) {
118 _container = [[RNSFullWindowOverlayContainer alloc] initWithFrame:_reactFrame];
119 }
120
121 return _container;
122}
123
124- (void)show
125{
126 UIWindow *window = RCTKeyWindow();
127 [window addSubview:_container];
128}
129
130- (void)didMoveToSuperview
131{
132 if (self.superview == nil) {
133 if (_container != nil) {
134 [_container removeFromSuperview];
135 [_touchHandler detachFromView:_container];
136 }
137 } else {
138 if (_container != nil) {
139 UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, _container);
140 }
141 if (_touchHandler == nil) {
142#ifdef RCT_NEW_ARCH_ENABLED
143 _touchHandler = [RCTSurfaceTouchHandler new];
144#else
145 _touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
146#endif
147 }
148 [_touchHandler attachToView:_container];
149 }
150}
151
152#ifdef RCT_NEW_ARCH_ENABLED
153#pragma mark - Fabric Specific
154
155// When the component unmounts we remove it from window's children,
156// so when the component gets recycled we need to add it back.
157- (void)maybeShow
158{
159 UIWindow *window = RCTKeyWindow();
160 if (![[window subviews] containsObject:self]) {
161 [window addSubview:_container];
162 }
163}
164
165+ (react::ComponentDescriptorProvider)componentDescriptorProvider
166{
167 return react::concreteComponentDescriptorProvider<react::RNSFullWindowOverlayComponentDescriptor>();
168}
169
170- (void)prepareForRecycle
171{
172 [_container removeFromSuperview];
173 // Due to view recycling we don't really want to set _container = nil
174 // as it won't be instantiated when the component appears for the second time.
175 // We could consider nulling in here & using container (lazy getter) everywhere else.
176 // _container = nil;
177 [super prepareForRecycle];
178}
179
180- (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
181{
182 // When the component unmounts we remove it from window's children,
183 // so when the component gets recycled we need to add it back.
184 // As for now it is called here as we lack of method that is called
185 // just before component gets restored (from recycle pool).
186 [self maybeShow];
187 [self addSubview:childComponentView];
188}
189
190- (void)unmountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView index:(NSInteger)index
191{
192 [childComponentView removeFromSuperview];
193}
194
195- (void)updateLayoutMetrics:(react::LayoutMetrics const &)layoutMetrics
196 oldLayoutMetrics:(react::LayoutMetrics const &)oldLayoutMetrics
197{
198 CGRect frame = RCTCGRectFromRect(layoutMetrics.frame);
199 _reactFrame = frame;
200 [_container setFrame:frame];
201}
202
203#else
204#pragma mark - Paper specific
205
206- (void)reactSetFrame:(CGRect)frame
207{
208 _reactFrame = frame;
209 [_container setFrame:frame];
210}
211
212- (void)invalidate
213{
214 [_container removeFromSuperview];
215 _container = nil;
216}
217
218#endif // RCT_NEW_ARCH_ENABLED
219
220@end
221
222#ifdef RCT_NEW_ARCH_ENABLED
223Class<RCTComponentViewProtocol> RNSFullWindowOverlayCls(void)
224{
225 return RNSFullWindowOverlay.class;
226}
227#endif // RCT_NEW_ARCH_ENABLED
228
229@implementation RNSFullWindowOverlayManager
230
231RCT_EXPORT_MODULE()
232
233#ifdef RCT_NEW_ARCH_ENABLED
234#else
235- (UIView *)view
236{
237 return [[RNSFullWindowOverlay alloc] initWithBridge:self.bridge];
238}
239#endif // RCT_NEW_ARCH_ENABLED
240
241@end