diff --git a/Libraries/Animation/RCTAnimationExperimentalManager.m b/Libraries/Animation/RCTAnimationExperimentalManager.m index 798c1456b..64ee577fe 100644 --- a/Libraries/Animation/RCTAnimationExperimentalManager.m +++ b/Libraries/Animation/RCTAnimationExperimentalManager.m @@ -264,7 +264,9 @@ RCT_EXPORT_METHOD(stopAnimation:(NSNumber *)animationTag) RCTAnimationExperimentalManager *strongSelf = weakSelf; NSNumber *reactTag = strongSelf->_animationRegistry[animationTag]; - if (!reactTag) return; + if (!reactTag) { + return; + } UIView *view = viewRegistry[reactTag]; for (NSString *animationKey in view.layer.animationKeys) { diff --git a/React/Base/RCTDevMenu.h b/React/Base/RCTDevMenu.h index a49e076e6..8057e5708 100644 --- a/React/Base/RCTDevMenu.h +++ b/React/Base/RCTDevMenu.h @@ -9,11 +9,50 @@ #import -@class RCTBridge; +#import "RCTBridge.h" +#import "RCTBridgeModule.h" +#import "RCTInvalidating.h" -@interface RCTDevMenu : NSObject +/** + * Developer menu, useful for exposing extra functionality when debugging. + */ +@interface RCTDevMenu : NSObject -- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER; +/** + * Is the menu enabled. The menu is enabled by default in debug mode, but + * you may wish to disable it so that you can provide your own shake handler. + */ +@property (nonatomic, assign) BOOL shakeToShow; + +/** + * Enables performance profiling. + */ +@property (nonatomic, assign) BOOL profilingEnabled; + +/** + * Enables automatic polling for JS code changes. Only applicable when + * running the app from a server. + */ +@property (nonatomic, assign) BOOL liveReloadEnabled; + +/** + * The time between checks for code changes. Defaults to 1 second. + */ +@property (nonatomic, assign) NSTimeInterval liveReloadPeriod; + +/** + * Manually show the menu. This will. + */ - (void)show; @end + +/** + * This category makes the developer menu instance available via the + * RCTBridge, which is useful for any class that needs to access the menu. + */ +@interface RCTBridge (RCTDevMenu) + +@property (nonatomic, readonly) RCTDevMenu *devMenu; + +@end diff --git a/React/Base/RCTDevMenu.m b/React/Base/RCTDevMenu.m index 7621b1955..f29201f9a 100644 --- a/React/Base/RCTDevMenu.m +++ b/React/Base/RCTDevMenu.m @@ -9,12 +9,13 @@ #import "RCTDevMenu.h" -#import "RCTRedBox.h" +#import "RCTBridge.h" +#import "RCTLog.h" #import "RCTRootView.h" #import "RCTSourceCode.h" -#import "RCTWebViewExecutor.h" +#import "RCTUtils.h" -@interface RCTBridge (RCTDevMenu) +@interface RCTBridge (Profiling) @property (nonatomic, copy, readonly) NSArray *profile; @@ -23,87 +24,206 @@ @end +static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification"; + +@implementation UIWindow (RCTDevMenu) + +- (void)RCT_motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event +{ + if (event.subtype == UIEventSubtypeMotionShake) { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil]; + } +} + +@end + @interface RCTDevMenu () @end @implementation RCTDevMenu { - BOOL _liveReload; - __weak RCTBridge *_bridge; + NSTimer *_updateTimer; + UIActionSheet *_actionSheet; } -- (instancetype)initWithBridge:(RCTBridge *)bridge +@synthesize bridge = _bridge; + +RCT_EXPORT_MODULE() + ++ (void)initialize { - if (self = [super init]) { - _bridge = bridge; + // We're swizzling here because it's poor form to override methods in a category, + // however UIWindow doesn't actually implement motionEnded:withEvent:, so there's + // no need to call the original implementation. + RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:)); +} + +- (instancetype)init +{ + if ((self = [super init])) { + + _shakeToShow = YES; + _liveReloadPeriod = 1.0; // 1 second + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(showOnShake) + name:RCTShowDevMenuNotification + object:nil]; } return self; } +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)showOnShake +{ + if (_shakeToShow) { + [self show]; + } +} + - (void)show { - NSString *debugTitleChrome = _bridge.executorClass != Nil && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging"; - NSString *debugTitleSafari = _bridge.executorClass == [RCTWebViewExecutor class] ? @"Disable Safari Debugging" : @"Enable Safari Debugging"; - NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload"; + if (_actionSheet) { + return; + } + + NSString *debugTitleChrome = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging"; + NSString *debugTitleSafari = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Enable Safari Debugging"; + NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload"; NSString *profilingTitle = _bridge.profile ? @"Stop Profiling" : @"Start Profiling"; - UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development" - delegate:self - cancelButtonTitle:@"Cancel" - destructiveButtonTitle:nil - otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil]; + + UIActionSheet *actionSheet = + [[UIActionSheet alloc] initWithTitle:@"React Native: Development" + delegate:self + cancelButtonTitle:@"Cancel" + destructiveButtonTitle:nil + otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil]; + actionSheet.actionSheetStyle = UIBarStyleBlack; - [actionSheet showInView:[[[[UIApplication sharedApplication] keyWindow] rootViewController] view]]; + [actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view]; } - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { - if (buttonIndex == 0) { - [_bridge reload]; - } else if (buttonIndex == 1) { - Class cls = NSClassFromString(@"RCTWebSocketExecutor"); - _bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil; - [_bridge reload]; - } else if (buttonIndex == 2) { - Class cls = [RCTWebViewExecutor class]; - _bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil; - [_bridge reload]; - } else if (buttonIndex == 3) { - _liveReload = !_liveReload; - [self _pollAndReload]; - } else if (buttonIndex == 4) { - if (_bridge.profile) { - [_bridge stopProfiling]; - } else { - [_bridge startProfiling]; - } - } -} + _actionSheet = nil; -- (void)_pollAndReload -{ - if (_liveReload) { - RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; - NSURL *url = sourceCodeModule.scriptURL; - NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url]; - [self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL]; - } -} - -- (void)_checkForUpdates:(NSURL *)URL -{ - NSMutableURLRequest *longPollRequest = [NSMutableURLRequest requestWithURL:URL]; - longPollRequest.timeoutInterval = 30; - NSHTTPURLResponse *response; - [NSURLConnection sendSynchronousRequest:longPollRequest returningResponse:&response error:nil]; - - dispatch_async(dispatch_get_main_queue(), ^{ - if (_liveReload && response.statusCode == 205) { - [[RCTRedBox sharedInstance] dismiss]; + switch (buttonIndex) { + case 0: { [_bridge reload]; + break; } - [self _pollAndReload]; - }); + case 1: { + Class cls = NSClassFromString(@"RCTWebSocketExecutor"); + _bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil; + [_bridge reload]; + break; + } + case 2: { + Class cls = NSClassFromString(@"RCTWebViewExecutor"); + _bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil; + [_bridge reload]; + break; + } + case 3: { + self.liveReloadEnabled = !_liveReloadEnabled; + break; + } + case 4: { + self.profilingEnabled = !_profilingEnabled; + break; + } + default: + break; + } +} + +- (void)setProfilingEnabled:(BOOL)enabled +{ + if (_profilingEnabled == enabled) { + return; + } + + _profilingEnabled = enabled; + if (_bridge.profile) { + [_bridge stopProfiling]; + } else { + [_bridge startProfiling]; + } +} + +- (void)setLiveReloadEnabled:(BOOL)enabled +{ + if (_liveReloadEnabled == enabled) { + return; + } + + _liveReloadEnabled = enabled; + if (_liveReloadEnabled) { + + _updateTimer = [NSTimer scheduledTimerWithTimeInterval:_liveReloadPeriod + target:self + selector:@selector(pollForUpdates) + userInfo:nil + repeats:YES]; + } else { + + [_updateTimer invalidate]; + _updateTimer = nil; + } +} + +- (void)setLiveReloadPeriod:(NSTimeInterval)liveReloadPeriod +{ + _liveReloadPeriod = liveReloadPeriod; + if (_liveReloadEnabled) { + self.liveReloadEnabled = NO; + self.liveReloadEnabled = YES; + } +} + +- (void)pollForUpdates +{ + RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])]; + if (!sourceCodeModule) { + RCTLogError(@"RCTSourceCode module not found"); + self.liveReloadEnabled = NO; + } + + NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL]; + [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:longPollURL] + queue:[[NSOperationQueue alloc] init] + completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { + + NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response; + if (_liveReloadEnabled && HTTPResponse.statusCode == 205) { + [_bridge reload]; + } + }]; +} + +- (BOOL)isValid +{ + return !_liveReloadEnabled || _updateTimer != nil; +} + +- (void)invalidate +{ + [_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES]; + [_updateTimer invalidate]; + _updateTimer = nil; +} + +@end + +@implementation RCTBridge (RCTDevMenu) + +- (RCTDevMenu *)devMenu +{ + return self.modules[RCTBridgeModuleNameForClass([RCTDevMenu class])]; } @end diff --git a/React/Base/RCTJavaScriptLoader.h b/React/Base/RCTJavaScriptLoader.h index bdc551b4d..8d52529e6 100755 --- a/React/Base/RCTJavaScriptLoader.h +++ b/React/Base/RCTJavaScriptLoader.h @@ -1,4 +1,11 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ #import diff --git a/React/Base/RCTJavaScriptLoader.m b/React/Base/RCTJavaScriptLoader.m index baf2ca344..02785fb41 100755 --- a/React/Base/RCTJavaScriptLoader.m +++ b/React/Base/RCTJavaScriptLoader.m @@ -1,4 +1,11 @@ -// Copyright 2004-present Facebook. All Rights Reserved. +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ #import "RCTJavaScriptLoader.h" diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m index 1770a20a2..4b9653650 100644 --- a/React/Base/RCTLog.m +++ b/React/Base/RCTLog.m @@ -31,8 +31,8 @@ const char *RCTLogLevels[] = { static RCTLogFunction RCTCurrentLogFunction; static RCTLogLevel RCTCurrentLogThreshold; -void RCTLogSetup(void) __attribute__((constructor)); -void RCTLogSetup() +__attribute__((constructor)) +static void RCTLogSetup() { RCTCurrentLogFunction = RCTDefaultLogFunction; diff --git a/React/Base/RCTRootView.h b/React/Base/RCTRootView.h index 1227eba94..ee5a35d7f 100644 --- a/React/Base/RCTRootView.h +++ b/React/Base/RCTRootView.h @@ -57,12 +57,6 @@ */ @property (nonatomic, strong) Class executorClass; -/** - * If YES will watch for shake gestures and show development menu - * with options like "Reload", "Enable Debugging", etc. - */ -@property (nonatomic, assign) BOOL enableDevMenu; - /** * The backing view controller of the root view. */ diff --git a/React/Base/RCTRootView.m b/React/Base/RCTRootView.m index c9a97dfd1..24cfe8417 100644 --- a/React/Base/RCTRootView.m +++ b/React/Base/RCTRootView.m @@ -13,7 +13,6 @@ #import "RCTBridge.h" #import "RCTContextExecutor.h" -#import "RCTDevMenu.h" #import "RCTEventDispatcher.h" #import "RCTKeyCommands.h" #import "RCTLog.h" @@ -42,7 +41,6 @@ @implementation RCTRootView { - RCTDevMenu *_devMenu; RCTBridge *_bridge; RCTTouchHandler *_touchHandler; NSString *_moduleName; @@ -60,12 +58,6 @@ self.backgroundColor = [UIColor whiteColor]; -#ifdef DEBUG - - _enableDevMenu = YES; - -#endif - _bridge = bridge; _moduleName = moduleName; @@ -120,18 +112,6 @@ return YES; } -- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event -{ - if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) { - if (!_devMenu) { - _devMenu = [[RCTDevMenu alloc] initWithBridge:_bridge]; - } - [_devMenu show]; - } else { - [super motionEnded:motion withEvent:event]; - } -} - RCT_IMPORT_METHOD(AppRegistry, runApplication) RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer) diff --git a/React/Executors/RCTContextExecutor.m b/React/Executors/RCTContextExecutor.m index 8475e2afa..39616bf35 100644 --- a/React/Executors/RCTContextExecutor.m +++ b/React/Executors/RCTContextExecutor.m @@ -85,7 +85,14 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object, range:(NSRange){0, message.length} withTemplate:@"[$4$5] \t$2"]; - _RCTLogFormat(RCTLogLevelInfo, NULL, -1, @"%@", message); + // TODO: it would be good if log level was sent as a param, instead of this hack + RCTLogLevel level = RCTLogLevelInfo; + if ([message rangeOfString:@"error" options:NSCaseInsensitiveSearch].length) { + level = RCTLogLevelError; + } else if ([message rangeOfString:@"warning" options:NSCaseInsensitiveSearch].length) { + level = RCTLogLevelWarning; + } + _RCTLogFormat(level, NULL, -1, @"%@", message); } return JSValueMakeUndefined(context); @@ -126,8 +133,6 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) + (void)runRunLoopThread { - // TODO (#5906496): Investigate exactly what this does and why - @autoreleasepool { // copy thread name to pthread name pthread_setname_np([[[NSThread currentThread] name] UTF8String]); @@ -273,11 +278,11 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) } - (void)executeApplicationScript:(NSString *)script - sourceURL:(NSURL *)url + sourceURL:(NSURL *)sourceURL onComplete:(RCTJavaScriptCompleteBlock)onComplete { - RCTAssert(url != nil, @"url should not be nil"); - RCTAssert(onComplete != nil, @"onComplete block should not be nil"); + RCTAssert(sourceURL != nil, @"url should not be nil"); + __weak RCTContextExecutor *weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ RCTContextExecutor *strongSelf = weakSelf; @@ -286,17 +291,18 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) } JSValueRef jsError = NULL; JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script); - JSStringRef sourceURL = JSStringCreateWithCFString((__bridge CFStringRef)url.absoluteString); - JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, sourceURL, 0, &jsError); - JSStringRelease(sourceURL); + JSStringRef jsURL = JSStringCreateWithCFString((__bridge CFStringRef)sourceURL.absoluteString); + JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, &jsError); + JSStringRelease(jsURL); JSStringRelease(execJSString); - NSError *error; - if (!result) { - error = RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError); + if (onComplete) { + NSError *error; + if (!result) { + error = RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError); + } + onComplete(error); } - - onComplete(error); }]; } @@ -314,7 +320,7 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete { - RCTAssert(onComplete != nil, @"onComplete block should not be nil"); + #if DEBUG RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script); #endif @@ -333,19 +339,21 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError) NSString *errorDesc = [NSString stringWithFormat:@"Can't make JSON value from script '%@'", script]; RCTLogError(@"%@", errorDesc); - NSError *error = [NSError errorWithDomain:@"JS" code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; - onComplete(error); + if (onComplete) { + NSError *error = [NSError errorWithDomain:@"JS" code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}]; + onComplete(error); + } return; } JSObjectRef globalObject = JSContextGetGlobalObject(strongSelf->_context.ctx); - JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)objectName); JSObjectSetProperty(strongSelf->_context.ctx, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL); JSStringRelease(JSName); - onComplete(nil); + if (onComplete) { + onComplete(nil); + } }]; - } @end diff --git a/React/Modules/RCTExceptionsManager.m b/React/Modules/RCTExceptionsManager.m index 9e919f3a3..ed30d8741 100644 --- a/React/Modules/RCTExceptionsManager.m +++ b/React/Modules/RCTExceptionsManager.m @@ -19,10 +19,6 @@ NSUInteger _reloadRetries; } -#ifndef DEBUG -static NSUInteger RCTReloadRetries = 0; -#endif - RCT_EXPORT_MODULE() - (instancetype)initWithDelegate:(id)delegate @@ -47,27 +43,32 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message return; } -#ifdef DEBUG +#if DEBUG + [[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack]; + #else - if (RCTReloadRetries < _maxReloadAttempts) { - RCTReloadRetries++; - [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil]; + + static NSUInteger reloadRetries = 0; + const NSUInteger maxMessageLength = 75; + + if (reloadRetries < _maxReloadAttempts) { + + reloadRetries++; + [[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification + object:nil]; + } else { - NSError *error; - const NSUInteger MAX_SANITIZED_LENGTH = 75; + // Filter out numbers so the same base errors are mapped to the same categories independent of incorrect values. NSString *pattern = @"[+-]?\\d+[,.]?\\d*"; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error]; - RCTAssert(error == nil, @"Bad regex pattern: %@", pattern); - NSString *sanitizedMessage = [regex stringByReplacingMatchesInString:message - options:0 - range:NSMakeRange(0, message.length) - withTemplate:@""]; - if (sanitizedMessage.length > MAX_SANITIZED_LENGTH) { - sanitizedMessage = [[sanitizedMessage substringToIndex:MAX_SANITIZED_LENGTH] stringByAppendingString:@"..."]; + NSString *sanitizedMessage = [message stringByReplacingOccurrencesOfString:pattern withString:@"" options:NSRegularExpressionSearch range:(NSRange){0, message.length}]; + + if (sanitizedMessage.length > maxMessageLength) { + sanitizedMessage = [[sanitizedMessage substringToIndex:maxMessageLength] stringByAppendingString:@"..."]; } - NSMutableString *prettyStack = [@"\n" mutableCopy]; + + NSMutableString *prettyStack = [NSMutableString stringWithString:@"\n"]; for (NSDictionary *frame in stack) { [prettyStack appendFormat:@"%@@%@:%@\n", frame[@"methodName"], frame[@"lineNumber"], frame[@"column"]]; } @@ -75,13 +76,21 @@ RCT_EXPORT_METHOD(reportUnhandledException:(NSString *)message NSString *name = [@"Unhandled JS Exception: " stringByAppendingString:sanitizedMessage]; [NSException raise:name format:@"Message: %@, stack: %@", message, prettyStack]; } + #endif + } RCT_EXPORT_METHOD(updateExceptionMessage:(NSString *)message stack:(NSArray *)stack) { + +#if DEBUG + [[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack]; + +#endif + } @end diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 961b5a0b5..2830ce6b0 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -999,7 +999,7 @@ RCT_EXPORT_METHOD(measureLayoutRelativeToParent:(NSNumber *)reactTag * Only layouts for views that are within the rect passed in are returned. Invokes the error callback if the * passed in parent view does not exist. Invokes the supplied callback with the array of computed layouts. */ -RCT_EXPORT_METHOD(measureViewsInRect:(NSDictionary *)rect +RCT_EXPORT_METHOD(measureViewsInRect:(CGRect)rect parentView:(NSNumber *)reactTag errorCallback:(RCTResponseSenderBlock)errorCallback callback:(RCTResponseSenderBlock)callback) @@ -1011,7 +1011,7 @@ RCT_EXPORT_METHOD(measureViewsInRect:(NSDictionary *)rect } NSArray *childShadowViews = [shadowView reactSubviews]; NSMutableArray *results = [[NSMutableArray alloc] initWithCapacity:[childShadowViews count]]; - CGRect layoutRect = [RCTConvert CGRect:rect]; + [childShadowViews enumerateObjectsUsingBlock:^(RCTShadowView *childShadowView, NSUInteger idx, BOOL *stop) { CGRect childLayout = [childShadowView measureLayoutRelativeToAncestor:shadowView]; @@ -1026,10 +1026,11 @@ RCT_EXPORT_METHOD(measureViewsInRect:(NSDictionary *)rect CGFloat width = childLayout.size.width; CGFloat height = childLayout.size.height; - if (leftOffset <= layoutRect.origin.x + layoutRect.size.width && - leftOffset + width >= layoutRect.origin.x && - topOffset <= layoutRect.origin.y + layoutRect.size.height && - topOffset + height >= layoutRect.origin.y) { + if (leftOffset <= rect.origin.x + rect.size.width && + leftOffset + width >= rect.origin.x && + topOffset <= rect.origin.y + rect.size.height && + topOffset + height >= rect.origin.y) { + // This view is within the layout rect NSDictionary *result = @{@"index": @(idx), @"left": @(leftOffset),