UNPKG

11.8 kBPlain TextView Raw
1/*
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#import "RCTRootView.h"
9#import "RCTRootViewDelegate.h"
10#import "RCTRootViewInternal.h"
11
12#import <objc/runtime.h>
13
14#import "RCTAssert.h"
15#import "RCTBridge.h"
16#import "RCTBridge+Private.h"
17#import "RCTConstants.h"
18#import "RCTEventDispatcher.h"
19#import "RCTKeyCommands.h"
20#import "RCTLog.h"
21#import "RCTPerformanceLogger.h"
22#import "RCTProfile.h"
23#import "RCTRootContentView.h"
24#import "RCTTouchHandler.h"
25#import "RCTUIManager.h"
26#import "RCTUIManagerUtils.h"
27#import "RCTUtils.h"
28#import "RCTView.h"
29#import "UIView+React.h"
30
31#if TARGET_OS_TV
32#import "RCTTVRemoteHandler.h"
33#import "RCTTVNavigationEventEmitter.h"
34#endif
35
36NSString *const RCTContentDidAppearNotification = @"RCTContentDidAppearNotification";
37
38@interface RCTUIManager (RCTRootView)
39
40- (NSNumber *)allocateRootTag;
41
42@end
43
44@implementation RCTRootView
45{
46 RCTBridge *_bridge;
47 NSString *_moduleName;
48 RCTRootContentView *_contentView;
49 BOOL _passThroughTouches;
50 CGSize _intrinsicContentSize;
51}
52
53- (instancetype)initWithBridge:(RCTBridge *)bridge
54 moduleName:(NSString *)moduleName
55 initialProperties:(NSDictionary *)initialProperties
56{
57 RCTAssertMainQueue();
58 RCTAssert(bridge, @"A bridge instance is required to create an RCTRootView");
59 RCTAssert(moduleName, @"A moduleName is required to create an RCTRootView");
60
61 RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTRootView init]", nil);
62 if (!bridge.isLoading) {
63 [bridge.performanceLogger markStartForTag:RCTPLTTI];
64 }
65
66 if (self = [super initWithFrame:CGRectZero]) {
67 self.backgroundColor = [UIColor whiteColor];
68
69 _bridge = bridge;
70 _moduleName = moduleName;
71 _appProperties = [initialProperties copy];
72 _loadingViewFadeDelay = 0.25;
73 _loadingViewFadeDuration = 0.25;
74 _sizeFlexibility = RCTRootViewSizeFlexibilityNone;
75
76 [[NSNotificationCenter defaultCenter] addObserver:self
77 selector:@selector(bridgeDidReload)
78 name:RCTJavaScriptWillStartLoadingNotification
79 object:_bridge];
80
81 [[NSNotificationCenter defaultCenter] addObserver:self
82 selector:@selector(javaScriptDidLoad:)
83 name:RCTJavaScriptDidLoadNotification
84 object:_bridge];
85
86 [[NSNotificationCenter defaultCenter] addObserver:self
87 selector:@selector(hideLoadingView)
88 name:RCTContentDidAppearNotification
89 object:self];
90
91#if TARGET_OS_TV
92 self.tvRemoteHandler = [RCTTVRemoteHandler new];
93 for (NSString *key in [self.tvRemoteHandler.tvRemoteGestureRecognizers allKeys]) {
94 [self addGestureRecognizer:self.tvRemoteHandler.tvRemoteGestureRecognizers[key]];
95 }
96#endif
97
98 [self showLoadingView];
99
100 // Immediately schedule the application to be started.
101 // (Sometimes actual `_bridge` is already batched bridge here.)
102 [self bundleFinishedLoading:([_bridge batchedBridge] ?: _bridge)];
103 }
104
105 RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
106
107 return self;
108}
109
110- (instancetype)initWithBundleURL:(NSURL *)bundleURL
111 moduleName:(NSString *)moduleName
112 initialProperties:(NSDictionary *)initialProperties
113 launchOptions:(NSDictionary *)launchOptions
114{
115 RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
116 moduleProvider:nil
117 launchOptions:launchOptions];
118
119 return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
120}
121
122RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
123RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
124
125#if TARGET_OS_TV
126- (UIView *)preferredFocusedView
127{
128 if (self.reactPreferredFocusedView) {
129 return self.reactPreferredFocusedView;
130 }
131 return [super preferredFocusedView];
132}
133#endif
134
135#pragma mark - passThroughTouches
136
137- (BOOL)passThroughTouches
138{
139 return _contentView.passThroughTouches;
140}
141
142- (void)setPassThroughTouches:(BOOL)passThroughTouches
143{
144 _passThroughTouches = passThroughTouches;
145 _contentView.passThroughTouches = passThroughTouches;
146}
147
148#pragma mark - Layout
149
150- (CGSize)sizeThatFits:(CGSize)size
151{
152 CGSize fitSize = _intrinsicContentSize;
153 CGSize currentSize = self.bounds.size;
154
155 // Following the current `size` and current `sizeFlexibility` policy.
156 fitSize = CGSizeMake(
157 _sizeFlexibility & RCTRootViewSizeFlexibilityWidth ? fitSize.width : currentSize.width,
158 _sizeFlexibility & RCTRootViewSizeFlexibilityHeight ? fitSize.height : currentSize.height
159 );
160
161 // Following the given size constraints.
162 fitSize = CGSizeMake(
163 MIN(size.width, fitSize.width),
164 MIN(size.height, fitSize.height)
165 );
166
167 return fitSize;
168}
169
170- (void)layoutSubviews
171{
172 [super layoutSubviews];
173 _contentView.frame = self.bounds;
174 _loadingView.center = (CGPoint){
175 CGRectGetMidX(self.bounds),
176 CGRectGetMidY(self.bounds)
177 };
178}
179
180- (UIViewController *)reactViewController
181{
182 return _reactViewController ?: [super reactViewController];
183}
184
185- (BOOL)canBecomeFirstResponder
186{
187 return YES;
188}
189
190- (void)setLoadingView:(UIView *)loadingView
191{
192 _loadingView = loadingView;
193 if (!_contentView.contentHasAppeared) {
194 [self showLoadingView];
195 }
196}
197
198- (void)showLoadingView
199{
200 if (_loadingView && !_contentView.contentHasAppeared) {
201 _loadingView.hidden = NO;
202 [self addSubview:_loadingView];
203 }
204}
205
206- (void)hideLoadingView
207{
208 if (_loadingView.superview == self && _contentView.contentHasAppeared) {
209 if (_loadingViewFadeDuration > 0) {
210 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_loadingViewFadeDelay * NSEC_PER_SEC)),
211 dispatch_get_main_queue(), ^{
212
213 [UIView transitionWithView:self
214 duration:self->_loadingViewFadeDuration
215 options:UIViewAnimationOptionTransitionCrossDissolve
216 animations:^{
217 self->_loadingView.hidden = YES;
218 } completion:^(__unused BOOL finished) {
219 [self->_loadingView removeFromSuperview];
220 }];
221 });
222 } else {
223 _loadingView.hidden = YES;
224 [_loadingView removeFromSuperview];
225 }
226 }
227}
228
229- (NSNumber *)reactTag
230{
231 RCTAssertMainQueue();
232 if (!super.reactTag) {
233 /**
234 * Every root view that is created must have a unique react tag.
235 * Numbering of these tags goes from 1, 11, 21, 31, etc
236 *
237 * NOTE: Since the bridge persists, the RootViews might be reused, so the
238 * react tag must be re-assigned every time a new UIManager is created.
239 */
240 self.reactTag = RCTAllocateRootViewTag();
241 }
242 return super.reactTag;
243}
244
245- (void)bridgeDidReload
246{
247 RCTAssertMainQueue();
248 // Clear the reactTag so it can be re-assigned
249 self.reactTag = nil;
250}
251
252- (void)javaScriptDidLoad:(NSNotification *)notification
253{
254 RCTAssertMainQueue();
255
256 // Use the (batched) bridge that's sent in the notification payload, so the
257 // RCTRootContentView is scoped to the right bridge
258 RCTBridge *bridge = notification.userInfo[@"bridge"];
259 if (bridge != _contentView.bridge) {
260 [self bundleFinishedLoading:bridge];
261 }
262}
263
264- (void)bundleFinishedLoading:(RCTBridge *)bridge
265{
266 RCTAssert(bridge != nil, @"Bridge cannot be nil");
267 if (!bridge.valid) {
268 return;
269 }
270
271 [_contentView removeFromSuperview];
272 _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
273 bridge:bridge
274 reactTag:self.reactTag
275 sizeFlexiblity:_sizeFlexibility];
276 [self runApplication:bridge];
277
278 _contentView.passThroughTouches = _passThroughTouches;
279 [self insertSubview:_contentView atIndex:0];
280
281 if (_sizeFlexibility == RCTRootViewSizeFlexibilityNone) {
282 self.intrinsicContentSize = self.bounds.size;
283 }
284}
285
286- (void)runApplication:(RCTBridge *)bridge
287{
288 NSString *moduleName = _moduleName ?: @"";
289 NSDictionary *appParameters = @{
290 @"rootTag": _contentView.reactTag,
291 @"initialProps": _appProperties ?: @{},
292 };
293
294 RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
295 [bridge enqueueJSCall:@"AppRegistry"
296 method:@"runApplication"
297 args:@[moduleName, appParameters]
298 completion:NULL];
299}
300
301- (void)setSizeFlexibility:(RCTRootViewSizeFlexibility)sizeFlexibility
302{
303 if (_sizeFlexibility == sizeFlexibility) {
304 return;
305 }
306
307 _sizeFlexibility = sizeFlexibility;
308 [self setNeedsLayout];
309 _contentView.sizeFlexibility = _sizeFlexibility;
310}
311
312- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
313{
314 // The root view itself should never receive touches
315 UIView *hitView = [super hitTest:point withEvent:event];
316 if (self.passThroughTouches && hitView == self) {
317 return nil;
318 }
319 return hitView;
320}
321
322- (void)setAppProperties:(NSDictionary *)appProperties
323{
324 RCTAssertMainQueue();
325
326 if ([_appProperties isEqualToDictionary:appProperties]) {
327 return;
328 }
329
330 _appProperties = [appProperties copy];
331
332 if (_contentView && _bridge.valid && !_bridge.loading) {
333 [self runApplication:_bridge];
334 }
335}
336
337- (void)setIntrinsicContentSize:(CGSize)intrinsicContentSize
338{
339 BOOL oldSizeHasAZeroDimension = _intrinsicContentSize.height == 0 || _intrinsicContentSize.width == 0;
340 BOOL newSizeHasAZeroDimension = intrinsicContentSize.height == 0 || intrinsicContentSize.width == 0;
341 BOOL bothSizesHaveAZeroDimension = oldSizeHasAZeroDimension && newSizeHasAZeroDimension;
342
343 BOOL sizesAreEqual = CGSizeEqualToSize(_intrinsicContentSize, intrinsicContentSize);
344
345 _intrinsicContentSize = intrinsicContentSize;
346
347 // Don't notify the delegate if the content remains invisible or its size has not changed
348 if (bothSizesHaveAZeroDimension || sizesAreEqual) {
349 return;
350 }
351
352 [self invalidateIntrinsicContentSize];
353 [self.superview setNeedsLayout];
354
355 [_delegate rootViewDidChangeIntrinsicSize:self];
356}
357
358- (CGSize)intrinsicContentSize
359{
360 return _intrinsicContentSize;
361}
362
363- (void)contentViewInvalidated
364{
365 [_contentView removeFromSuperview];
366 _contentView = nil;
367 [self showLoadingView];
368}
369
370- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
371{
372 [super traitCollectionDidChange:previousTraitCollection];
373
374 [[NSNotificationCenter defaultCenter] postNotificationName:RCTUserInterfaceStyleDidChangeNotification
375 object:self
376 userInfo:@{
377 RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey: self.traitCollection,
378 }];
379}
380
381- (void)dealloc
382{
383 [_contentView invalidate];
384}
385
386@end
387
388@implementation RCTRootView (Deprecated)
389
390- (CGSize)intrinsicSize
391{
392 RCTLogWarn(@"Calling deprecated `[-RCTRootView intrinsicSize]`.");
393 return self.intrinsicContentSize;
394}
395
396- (void)cancelTouches
397{
398 RCTLogWarn(@"`-[RCTRootView cancelTouches]` is deprecated and will be deleted soon.");
399 [[_contentView touchHandler] cancel];
400}
401
402@end