RCTRootView.m 12 KB

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