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