Updates from Mon Mar 2

- [ReactNative] Move merge & mergeInto from downstream to vendor | Christopher Chedeau
- [ReactNative] Replace all the call sites of mergeInto by Object.assign | Christopher Chedeau
- [WIP] Migrated View Managers over to new architecture | Nick Lockwood
- [ReactNative] Replace all the call sites of copyProperties by Object.assign | Christopher Chedeau
- [ReactNative] Migrate navigator.geolocation to open source | Christopher Chedeau
- [ReactNative] Remove README.md, LICENSE and .travis.yml from fbobjc | Christopher Chedeau
- [react-packager] Better transform errors | Amjad Masad
- [React Native][react-packager] Fix test runner and fialing tests | Amjad Masad
This commit is contained in:
Christopher Chedeau
2015-03-02 11:36:55 -08:00
parent 0b09ed0667
commit 7060141247
109 changed files with 9051 additions and 8540 deletions

View File

@@ -48,10 +48,10 @@
NSArray *buttons = args[@"buttons"];
if (!title && !message) {
RCTLogMustFix(@"Must specify either an alert title, or message, or both");
RCTLogError(@"Must specify either an alert title, or message, or both");
return;
} else if (buttons.count == 0) {
RCTLogMustFix(@"Must have at least one button.");
RCTLogError(@"Must have at least one button.");
return;
}
@@ -68,7 +68,7 @@
NSInteger index = 0;
for (NSDictionary *button in buttons) {
if (button.count != 1) {
RCTLogMustFix(@"Button definitions should have exactly one key.");
RCTLogError(@"Button definitions should have exactly one key.");
}
NSString *buttonKey = [button.allKeys firstObject];
NSString *buttonTitle = [button[buttonKey] description];

View File

@@ -0,0 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTBridgeModule.h"
@interface RCTLocationObserver : NSObject<RCTBridgeModule>
@end

View File

@@ -0,0 +1,185 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTLocationObserver.h"
#import <CoreLocation/CLLocationManager.h>
#import <CoreLocation/CLLocationManagerDelegate.h>
#import <FBReactKit/RCTAssert.h>
#import <FBReactKit/RCTLog.h>
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
// TODO (#5906496): Shouldn't these be configurable?
const CLLocationAccuracy RCTLocationAccuracy = 500.0; // meters
@interface RCTPendingLocationRequest : NSObject
@property (nonatomic, copy) RCTResponseSenderBlock successBlock;
@property (nonatomic, copy) RCTResponseSenderBlock errorBlock;
@end
@implementation RCTPendingLocationRequest @end
@interface RCTLocationObserver () <CLLocationManagerDelegate>
@end
@implementation RCTLocationObserver
{
CLLocationManager *_locationManager;
NSDictionary *_lastLocationEvent;
NSMutableDictionary *_pendingRequests;
}
@synthesize bridge = _bridge;
#pragma mark - Lifecycle
- (instancetype)init
{
if ((self = [super init])) {
_pendingRequests = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc
{
[_locationManager stopUpdatingLocation];
}
#pragma mark - Public API
- (void)startObserving
{
RCT_EXPORT();
dispatch_async(dispatch_get_main_queue(), ^{
// Create the location manager if this object does not
// already have one, and it must be created and accessed
// on the main thread
if (nil == _locationManager) {
_locationManager = [[CLLocationManager alloc] init];
}
_locationManager.delegate = self;
_locationManager.desiredAccuracy = RCTLocationAccuracy;
// Set a movement threshold for new events.
_locationManager.distanceFilter = RCTLocationAccuracy; // meters
if([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
[_locationManager requestWhenInUseAuthorization];
}
[_locationManager startUpdatingLocation];
});
}
- (void)stopObserving
{
RCT_EXPORT();
dispatch_async(dispatch_get_main_queue(), ^{
[_locationManager stopUpdatingLocation];
_lastLocationEvent = nil;
});
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *loc = [locations lastObject];
NSDictionary *event = @{
@"coords": @{
@"latitude": @(loc.coordinate.latitude),
@"longitude": @(loc.coordinate.longitude),
@"altitude": @(loc.altitude),
@"accuracy": @(RCTLocationAccuracy),
@"altitudeAccuracy": @(RCTLocationAccuracy),
@"heading": @(loc.course),
@"speed": @(loc.speed),
},
@"timestamp": @(CACurrentMediaTime())
};
[_bridge.eventDispatcher sendDeviceEventWithName:@"geoLocationDidChange" body:event];
NSArray *pendingRequestsCopy;
// TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
@synchronized(self) {
pendingRequestsCopy = [_pendingRequests allValues];
[_pendingRequests removeAllObjects];
_lastLocationEvent = event;
}
for (RCTPendingLocationRequest *request in pendingRequestsCopy) {
if (request.successBlock) {
request.successBlock(@[event]);
}
}
}
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
{
NSArray *pendingRequestsCopy;
// TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
@synchronized(self) {
pendingRequestsCopy = [_pendingRequests allValues];
[_pendingRequests removeAllObjects];
}
NSString *errorMsg = @"User denied location service or location service not available.";
for (RCTPendingLocationRequest *request in pendingRequestsCopy) {
if (request.errorBlock) {
request.errorBlock(@[errorMsg]);
}
}
}
- (void)getCurrentPosition:(RCTResponseSenderBlock)geoSuccess withErrorCallback:(RCTResponseSenderBlock)geoError
{
RCT_EXPORT();
NSDictionary *lastLocationCopy;
// TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize
@synchronized(self) {
if (![CLLocationManager locationServicesEnabled] || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied) {
if (geoError) {
NSString *errorMsg = @"User denied location service or location service not available.";
geoError(@[errorMsg]);
return;
}
}
// If a request for the current position comes in before the OS has informed us, we wait for the first
// OS event and then call our callbacks. This obviates the need for handling of the otherwise
// common failure case of requesting the geolocation until it succeeds, assuming we would have
// instead returned an error if it wasn't yet available.
if (!_lastLocationEvent) {
NSInteger requestID = [_pendingRequests count];
RCTPendingLocationRequest *request = [[RCTPendingLocationRequest alloc] init];
request.successBlock = geoSuccess;
request.errorBlock = geoError;
_pendingRequests[@(requestID)] = request;
return;
} else {
lastLocationCopy = [_lastLocationEvent copy];
}
}
if (geoSuccess) {
geoSuccess(@[lastLocationCopy]);
}
}
@end

View File

@@ -49,7 +49,7 @@ static BOOL RCTViewControllerBasedStatusBarAppearance()
});
}
+ (NSDictionary *)constantsToExport
- (NSDictionary *)constantsToExport
{
return @{
@"Style": @{

View File

@@ -51,35 +51,35 @@
@implementation RCTTiming
{
RCTSparseArray *_timers;
RCTBridge *_bridge;
id _updateTimer;
}
@synthesize bridge = _bridge;
+ (NSArray *)JSMethods
{
return @[@"RCTJSTimers.callTimers"];
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
- (instancetype)init
{
if ((self = [super init])) {
_bridge = bridge;
_timers = [[RCTSparseArray alloc] init];
[self startTimers];
for (NSString *name in @[UIApplicationWillResignActiveNotification,
UIApplicationDidEnterBackgroundNotification,
UIApplicationWillTerminateNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(stopTimers)
name:name
object:nil];
}
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
UIApplicationWillEnterForegroundNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(startTimers)
name:name
@@ -114,14 +114,14 @@
- (void)startTimers
{
RCTAssertMainThread();
if (![self isValid] || _updateTimer != nil) {
if (![self isValid] || _updateTimer != nil || _timers.count == 0) {
return;
}
_updateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
if (_updateTimer) {
[_updateTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_updateTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
} else {
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60)
@@ -135,7 +135,7 @@
- (void)update
{
RCTAssertMainThread();
NSMutableArray *timersToCall = [[NSMutableArray alloc] init];
for (RCTTimer *timer in _timers.allObjects) {
if ([timer updateFoundNeedsJSUpdate]) {
@@ -145,7 +145,7 @@
_timers[timer.callbackID] = nil;
}
}
// call timers that need to be called
if ([timersToCall count] > 0) {
[_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[timersToCall]];
@@ -185,6 +185,7 @@
repeats:repeats];
dispatch_async(dispatch_get_main_queue(), ^{
_timers[callbackID] = timer;
[self startTimers];
});
}
@@ -195,6 +196,9 @@
if (timerID) {
dispatch_async(dispatch_get_main_queue(), ^{
_timers[timerID] = nil;
if (_timers.count == 0) {
[self stopTimers];
}
});
} else {
RCTLogWarn(@"Called deleteTimer: with a nil timerID");

View File

@@ -2,13 +2,18 @@
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTBridgeModule.h"
#import "RCTInvalidating.h"
#import "RCTViewManager.h"
@class RCTRootView;
@protocol RCTScrollableProtocol;
/**
* The RCTUIManager is the module responsible for updating the view hierarchy.
*/
@interface RCTUIManager : NSObject <RCTBridgeModule, RCTInvalidating>
@property (nonatomic, weak) id<RCTScrollableProtocol> mainScrollView;
@@ -19,8 +24,33 @@
*/
@property (nonatomic, readwrite, weak) id<UIScrollViewDelegate> nativeMainScrollDelegate;
/**
* Register a root view with the RCTUIManager. Theoretically, a single manager
* can support multiple root views, however this feature is not currently exposed
* and may eventually be removed.
*/
- (void)registerRootView:(RCTRootView *)rootView;
/**
* Schedule a block to be executed on the UI thread. Useful if you need to execute
* view logic after all currently queued view updates have completed.
*/
- (void)addUIBlock:(RCTViewManagerUIBlock)block;
/**
* The view that is currently first responder, according to the JS context.
*/
+ (UIView *)JSResponder;
@end
/**
* This category makes the current RCTUIManager instance available via the
* RCTBridge, which is useful for RCTBridgeModules or RCTViewManagers that
* need to access the RCTUIManager.
*/
@interface RCTBridge (RCTUIManager)
@property (nonatomic, readonly) RCTUIManager *uiManager;
@end

View File

@@ -32,47 +32,6 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
}
}
static NSDictionary *RCTViewModuleClasses(void)
{
static NSMutableDictionary *modules;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
modules = [NSMutableDictionary dictionary];
unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++) {
Class cls = classes[i];
if (!class_getSuperclass(cls)) {
// Class has no superclass - it's probably something weird
continue;
}
if (![cls isSubclassOfClass:[RCTViewManager class]]) {
// Not a view module
continue;
}
// Get module name
NSString *moduleName = [cls moduleName];
// Check module name is unique
id existingClass = modules[moduleName];
RCTCAssert(existingClass == Nil, @"Attempted to register view module class %@ "
"for the name '%@', but name was already registered by class %@", cls, moduleName, existingClass);
// Add to module list
modules[moduleName] = cls;
}
free(classes);
});
return modules;
}
@interface RCTAnimation : NSObject
@property (nonatomic, readonly) NSTimeInterval duration;
@@ -190,48 +149,70 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
@implementation RCTUIManager
{
dispatch_queue_t _shadowQueue;
// Root views are only mutated on the shadow queue
NSMutableSet *_rootViewTags;
NSMutableArray *_pendingUIBlocks;
NSLock *_pendingUIBlocksLock;
// Animation
RCTLayoutAnimation *_nextLayoutAnimation; // RCT thread only
RCTLayoutAnimation *_layoutAnimation; // Main thread only
// Keyed by moduleName
// Keyed by viewName
NSMutableDictionary *_defaultShadowViews; // RCT thread only
NSMutableDictionary *_defaultViews; // Main thread only
NSDictionary *_viewManagers;
// Keyed by React tag
RCTSparseArray *_viewManagerRegistry; // RCT thread only
RCTSparseArray *_shadowViewRegistry; // RCT thread only
RCTSparseArray *_viewRegistry; // Main thread only
__weak RCTBridge *_bridge;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
@synthesize bridge =_bridge;
/**
* This function derives the view name automatically
* from the module name.
*/
static NSString *RCTViewNameForModuleName(NSString *moduleName)
{
NSString *name = moduleName;
if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length];
}
return name;
}
/**
* This private constructor should only be called when creating
* isolated UIImanager instances for testing. Normal initialization
* is via -init:, which is called automatically by the bridge.
*/
- (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue
{
if ((self = [self init])) {
_shadowQueue = shadowQueue;
_viewManagers = [[NSMutableDictionary alloc] init];
}
return self;
}
- (instancetype)init
{
if ((self = [super init])) {
_bridge = bridge;
_pendingUIBlocksLock = [[NSLock alloc] init];
// Instantiate view managers
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
viewManagers[moduleName] = [[moduleClass alloc] initWithEventDispatcher:_bridge.eventDispatcher];
}];
_viewManagers = viewManagers;
_defaultShadowViews = [[NSMutableDictionary alloc] init];
_defaultViews = [[NSMutableDictionary alloc] init];
_viewManagerRegistry = [[RCTSparseArray alloc] init];
_shadowViewRegistry = [[RCTSparseArray alloc] init];
_viewRegistry = [[RCTSparseArray alloc] init];
// Internal resources
_pendingUIBlocks = [[NSMutableArray alloc] init];
_rootViewTags = [[NSMutableSet alloc] init];
@@ -239,16 +220,35 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
return self;
}
- (instancetype)init
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
}
- (void)setBridge:(RCTBridge *)bridge
{
if (_bridge) {
// Clear previous bridge data
[self invalidate];
}
if (bridge) {
_bridge = bridge;
_shadowQueue = _bridge.shadowQueue;
// Get view managers from bridge
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[_bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, RCTViewManager *manager, BOOL *stop) {
if ([manager isKindOfClass:[RCTViewManager class]]) {
viewManagers[RCTViewNameForModuleName(moduleName)] = manager;
}
}];
_viewManagers = [viewManagers copy];
}
}
- (BOOL)isValid
{
return _viewRegistry != nil;
@@ -260,6 +260,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
_viewRegistry = nil;
_shadowViewRegistry = nil;
_bridge = nil;
[_pendingUIBlocksLock lock];
_pendingUIBlocks = nil;
@@ -269,22 +270,22 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
- (void)registerRootView:(RCTRootView *)rootView;
{
RCTAssertMainThread();
NSNumber *reactTag = rootView.reactTag;
UIView *existingView = _viewRegistry[reactTag];
RCTCAssert(existingView == nil || existingView == rootView,
@"Expect all root views to have unique tag. Added %@ twice", reactTag);
// Register view
_viewRegistry[reactTag] = rootView;
CGRect frame = rootView.frame;
// Register manager (TODO: should we do this, or leave it nil?)
_viewManagerRegistry[reactTag] = _viewManagers[[RCTViewManager moduleName]];
_viewManagerRegistry[reactTag] = _viewManagers[@"View"];
// Register shadow view
dispatch_async(_bridge.shadowQueue, ^{
dispatch_async(_shadowQueue, ^{
RCTShadowView *shadowView = [[RCTShadowView alloc] init];
shadowView.reactTag = reactTag;
shadowView.frame = frame;
@@ -315,7 +316,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
- (void)addUIBlock:(RCTViewManagerUIBlock)block
{
RCTAssert(![NSThread isMainThread], @"This method should only be called on the shadow thread");
__weak RCTUIManager *weakViewManager = self;
__weak RCTSparseArray *weakViewRegistry = _viewRegistry;
dispatch_block_t outerBlock = ^{
@@ -333,6 +334,8 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)rootShadowView
{
RCTAssert(![NSThread isMainThread], @"This should never be executed on main thread.");
NSMutableSet *viewsWithNewFrames = [NSMutableSet setWithCapacity:1];
// This is nuanced. In the JS thread, we create a new update buffer
@@ -340,7 +343,8 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
// these structures in the UI-thread block. `NSMutableArray` is not thread
// safe so we rely on the fact that we never mutate it after it's passed to
// the main thread.
[rootShadowView collectRootUpdatedFrames:viewsWithNewFrames parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}];
[rootShadowView collectRootUpdatedFrames:viewsWithNewFrames
parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}];
// Parallel arrays
NSMutableArray *frameReactTags = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
@@ -354,12 +358,19 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
[areNew addObject:@(shadowView.isNewView)];
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
}
for (RCTShadowView *shadowView in viewsWithNewFrames) {
// We have to do this after we build the parentsAreNew array.
shadowView.newView = NO;
}
NSMutableArray *updateBlocks = [[NSMutableArray alloc] init];
for (RCTShadowView *shadowView in viewsWithNewFrames) {
RCTViewManager *manager = _viewManagerRegistry[shadowView.reactTag];
RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView];
if (block) [updateBlocks addObject:block];
}
// Perform layout (possibly animated)
NSNumber *rootViewTag = rootShadowView.reactTag;
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
@@ -373,15 +384,16 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
// Convert the frame so it works with anchorPoint = center.
CGPoint position = {CGRectGetMidX(frame), CGRectGetMidY(frame)};
CGRect bounds = {0, 0, frame.size};
// Avoid crashes due to nan coords
if (isnan(position.x) || isnan(position.y) ||
isnan(bounds.origin.x) || isnan(bounds.origin.y) ||
isnan(bounds.size.width) || isnan(bounds.size.height)) {
RCTLogError(@"Invalid layout for (%zd)%@. position: %@. bounds: %@", [view reactTag], self, NSStringFromCGPoint(position), NSStringFromCGRect(bounds));
RCTLogError(@"Invalid layout for (%@)%@. position: %@. bounds: %@",
[view reactTag], self, NSStringFromCGPoint(position), NSStringFromCGRect(bounds));
continue;
}
void (^completion)(BOOL finished) = ^(BOOL finished) {
if (self->_layoutAnimation.callback) {
self->_layoutAnimation.callback(@[@(finished)]);
@@ -395,14 +407,20 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
[updateAnimation performAnimations:^{
view.layer.position = position;
view.layer.bounds = bounds;
for (RCTViewManagerUIBlock block in updateBlocks) {
block(self, _viewRegistry);
}
} withCompletionBlock:completion];
} else {
view.layer.position = position;
view.layer.bounds = bounds;
for (RCTViewManagerUIBlock block in updateBlocks) {
block(self, _viewRegistry);
}
completion(YES);
}
// Animate view creations
// Animate view creation
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
RCTAnimation *createAnimation = _layoutAnimation.createAnimation;
if (shouldAnimateCreation && createAnimation) {
@@ -417,12 +435,22 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
} else if ([createAnimation.property isEqual:@"opacity"]) {
view.layer.opacity = 1.0;
} else {
RCTLogError(@"Unsupported layout animation createConfig property %@", createAnimation.property);
RCTLogError(@"Unsupported layout animation createConfig property %@",
createAnimation.property);
}
for (RCTViewManagerUIBlock block in updateBlocks) {
block(self, _viewRegistry);
}
} withCompletionBlock:nil];
}
}
/**
* Enumerate all active (attached to a parent) views and call
* reactBridgeDidFinishTransaction on them if they implement it.
* TODO: this is quite inefficient. If this was handled via the
* ViewManager instead, it could be done more efficiently.
*/
RCTRootView *rootView = _viewRegistry[rootViewTag];
RCTTraverseViewNodes(rootView, ^(id<RCTViewNodeProtocol> view) {
if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) {
@@ -436,7 +464,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
{
NSMutableSet *applierBlocks = [NSMutableSet setWithCapacity:1];
[topView collectUpdatedProperties:applierBlocks parentProperties:@{}];
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
for (RCTApplierBlock block in applierBlocks) {
block(viewRegistry);
@@ -454,7 +482,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
id<RCTViewNodeProtocol> container = _viewRegistry[containerID];
RCTAssert(container != nil, @"container view (for ID %@) not found", containerID);
NSUInteger subviewsCount = [[container reactSubviews] count];
NSMutableArray *indices = [[NSMutableArray alloc] initWithCapacity:subviewsCount];
for (NSInteger childIndex = 0; childIndex < subviewsCount; childIndex++) {
@@ -616,11 +644,11 @@ static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView,
{
// TODO: cache respondsToSelector tests
if ([manager respondsToSelector:setter]) {
if (value == [NSNull null]) {
value = nil;
}
((void (*)(id, SEL, id, id, id))objc_msgSend)(manager, setter, value, view, defaultView);
return YES;
}
@@ -631,9 +659,9 @@ static void RCTSetViewProps(NSDictionary *props, UIView *view,
UIView *defaultView, RCTViewManager *manager)
{
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forView:withDefaultView:", key]);
// For regular views we don't attempt to set properties
// unless the view property has been explicitly exported.
RCTCallPropertySetter(setter, obj, view, defaultView, manager);
@@ -644,13 +672,13 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
RCTShadowView *defaultView, RCTViewManager *manager)
{
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forShadowView:withDefaultView:", key]);
// For shadow views we call any custom setter methods by default,
// but if none is specified, we attempt to set property anyway.
if (!RCTCallPropertySetter(setter, obj, shadowView, defaultView, manager)) {
if (obj == [NSNull null]) {
// Copy property from default view to current
// Note: not just doing `[defaultView valueForKey:key]`, the
@@ -661,20 +689,20 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}
}
}];
// Update layout
[shadowView updateShadowViewLayout];
}
- (void)createAndRegisterViewWithReactTag:(NSNumber *)reactTag
moduleName:(NSString *)moduleName
viewName:(NSString *)viewName
props:(NSDictionary *)props
{
RCT_EXPORT(createView);
RCTViewManager *manager = _viewManagers[moduleName];
RCTViewManager *manager = _viewManagers[viewName];
if (manager == nil) {
RCTLogWarn(@"No manager class found for view with module name \"%@\"", moduleName);
RCTLogWarn(@"No manager class found for view with module name \"%@\"", viewName);
manager = [[RCTViewManager alloc] init];
}
@@ -682,57 +710,57 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
_viewManagerRegistry[reactTag] = manager;
// Generate default view, used for resetting default props
if (!_defaultShadowViews[moduleName]) {
_defaultShadowViews[moduleName] = [manager shadowView];
if (!_defaultShadowViews[viewName]) {
_defaultShadowViews[viewName] = [manager shadowView];
}
RCTShadowView *shadowView = [manager shadowView];
shadowView.moduleName = moduleName;
shadowView.moduleName = viewName;
shadowView.reactTag = reactTag;
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[moduleName], manager);
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager);
_shadowViewRegistry[shadowView.reactTag] = shadowView;
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
RCTCAssertMainThread();
// Generate default view, used for resetting default props
if (!uiManager->_defaultViews[moduleName]) {
if (!uiManager->_defaultViews[viewName]) {
// Note the default is setup after the props are read for the first time ever
// for this className - this is ok because we only use the default for restoring
// defaults, which never happens on first creation.
uiManager->_defaultViews[moduleName] = [manager view];
uiManager->_defaultViews[viewName] = [manager view];
}
UIView *view = [manager view];
if (view) {
// Set required properties
view.reactTag = reactTag;
view.multipleTouchEnabled = YES;
view.userInteractionEnabled = YES; // required for touch handling
view.layer.allowsGroupOpacity = YES; // required for touch handling
// Set custom properties
RCTSetViewProps(props, view, uiManager->_defaultViews[moduleName], manager);
RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], manager);
}
viewRegistry[view.reactTag] = view;
}];
}
// TODO: remove moduleName param as it isn't needed
- (void)updateView:(NSNumber *)reactTag moduleName:(__unused NSString *)_ props:(NSDictionary *)props
// TODO: remove viewName param as it isn't needed
- (void)updateView:(NSNumber *)reactTag viewName:(__unused NSString *)_ props:(NSDictionary *)props
{
RCT_EXPORT();
RCTViewManager *viewManager = _viewManagerRegistry[reactTag];
NSString *moduleName = [[viewManager class] moduleName];
NSString *viewName = RCTViewNameForModuleName([[viewManager class] moduleName]);
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[moduleName], viewManager);
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], viewManager);
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
UIView *view = uiManager->_viewRegistry[reactTag];
RCTSetViewProps(props, view, uiManager->_defaultViews[moduleName], viewManager);
RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], viewManager);
}];
}
@@ -760,16 +788,15 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
- (void)batchDidComplete
{
// First copy the previous blocks into a temporary variable, then reset the
// pending blocks to a new array. This guards against mutation while
// processing the pending blocks in another thread.
// Gather blocks to be executed now that all view hierarchy manipulations have
// been completed (note that these may still take place before layout has finished)
for (RCTViewManager *manager in _viewManagers.allValues) {
RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
if (uiBlock) {
[self addUIBlock:uiBlock];
}
}
// Set up next layout animation
if (_nextLayoutAnimation) {
RCTLayoutAnimation *layoutAnimation = _nextLayoutAnimation;
@@ -777,14 +804,14 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
uiManager->_layoutAnimation = layoutAnimation;
}];
}
// Perform layout
for (NSNumber *reactTag in _rootViewTags) {
RCTShadowView *rootView = _shadowViewRegistry[reactTag];
[self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
[self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootView];
}
// Clear layout animations
if (_nextLayoutAnimation) {
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
@@ -792,12 +819,16 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}];
_nextLayoutAnimation = nil;
}
// First copy the previous blocks into a temporary variable, then reset the
// pending blocks to a new array. This guards against mutation while
// processing the pending blocks in another thread.
[_pendingUIBlocksLock lock];
NSArray *previousPendingUIBlocks = _pendingUIBlocks;
_pendingUIBlocks = [[NSMutableArray alloc] init];
[_pendingUIBlocksLock unlock];
// Execute the previously queued UI blocks
dispatch_async(dispatch_get_main_queue(), ^{
for (dispatch_block_t block in previousPendingUIBlocks) {
block();
@@ -828,7 +859,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}
RCTCAssert([rootView isReactRootView], @"React view not inside RCTRootView");
// By convention, all coordinates, whether they be touch coordinates, or
// measurement coordinates are with respect to the root view.
CGPoint pagePoint = [view.superview convertPoint:frame.origin toView:rootView];
@@ -844,37 +875,6 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}];
}
- (void)requestSchedulingJavaScriptNavigation:(NSNumber *)reactTag
errorCallback:(RCTResponseSenderBlock)errorCallback
callback:(RCTResponseSenderBlock)callback
{
RCT_EXPORT();
if (!callback || !errorCallback) {
RCTLogError(@"Callback not provided for navigation scheduling.");
return;
}
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
if (reactTag) {
//TODO: This is nasty - why is RCTNavigator hard-coded?
id rkObject = viewRegistry[reactTag];
if ([rkObject isKindOfClass:[RCTNavigator class]]) {
RCTNavigator *navigator = (RCTNavigator *)rkObject;
BOOL wasAcquired = [navigator requestSchedulingJavaScriptNavigation];
callback(@[@(wasAcquired)]);
} else {
NSString *msg =
[NSString stringWithFormat: @"Cannot set lock: Tag %@ is not an RCTNavigator", reactTag];
errorCallback(@[RCTAPIErrorObject(msg)]);
}
} else {
NSString *msg = [NSString stringWithFormat: @"Tag not specified for requestSchedulingJavaScriptNavigation"];
errorCallback(@[RCTAPIErrorObject(msg)]);
}
}];
}
/**
* TODO: This could be modified to accept any `RCTViewNodeProtocol`, if
* appropriate changes were made to that protocol to support `superview`
@@ -884,22 +884,20 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
+ (void)measureLayoutOnNodes:(RCTShadowView *)view
ancestor:(RCTShadowView *)ancestor
errorCallback:(RCTResponseSenderBlock)errorCallback
callback:(RCTResponseSenderBlock)callback
callback:(__unused RCTResponseSenderBlock)callback
{
if (!view) {
NSString *msg = [NSString stringWithFormat: @"Attempting to measure view that does not exist %@", view];
errorCallback(@[RCTAPIErrorObject(msg)]);
RCTLogError(@"Attempting to measure view that does not exist");
return;
}
if (!ancestor) {
NSString *msg = [NSString stringWithFormat: @"Attempting to measure relative to ancestor that does not exist %@", ancestor];
errorCallback(@[RCTAPIErrorObject(msg)]);
RCTLogError(@"Attempting to measure relative to ancestor that does not exist");
return;
}
CGRect result = [RCTShadowView measureLayout:view relativeTo:ancestor];
if (CGRectIsNull(result)) {
NSString *msg = [NSString stringWithFormat: @"view %@ is not an decendant of %@", view, ancestor];
errorCallback(@[RCTAPIErrorObject(msg)]);
RCTLogError(@"view %@ (tag #%@) is not a decendant of %@ (tag #%@)",
view, view.reactTag, ancestor, ancestor.reactTag);
return;
}
CGFloat leftOffset = result.origin.x;
@@ -907,7 +905,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
CGFloat width = result.size.width;
CGFloat height = result.size.height;
if (isnan(leftOffset) || isnan(topOffset) || isnan(width) || isnan(height)) {
errorCallback(@[RCTAPIErrorObject(@"Attempted to measure layout but offset or dimensions were NaN")]);
RCTLogError(@"Attempted to measure layout but offset or dimensions were NaN");
return;
}
callback(@[@(topOffset), @(leftOffset), @(width), @(height)]);
@@ -965,8 +963,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
if (!shadowView) {
NSString *msg = [NSString stringWithFormat: @"Attempting to measure view that does not exist %@", shadowView];
errorCallback(@[RCTAPIErrorObject(msg)]);
RCTLogError(@"Attempting to measure view that does not exist (tag #%@)", reactTag);
return;
}
NSArray *childShadowViews = [shadowView reactSubviews];
@@ -977,8 +974,8 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
RCTShadowView *childShadowView = [childShadowViews objectAtIndex:ii];
CGRect childLayout = [RCTShadowView measureLayout:childShadowView relativeTo:shadowView];
if (CGRectIsNull(childLayout)) {
NSString *msg = [NSString stringWithFormat: @"view %@ is not a decendant of %@", childShadowView, shadowView];
errorCallback(@[RCTAPIErrorObject(msg)]);
RCTLogError(@"View %@ (tag #%@) is not a decendant of %@ (tag #%@)",
childShadowView, childShadowView.reactTag, shadowView, shadowView.reactTag);
return;
}
@@ -1088,7 +1085,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
_jsResponder = viewRegistry[reactTag];
if (!_jsResponder) {
RCTLogMustFix(@"Invalid view set to be the JS responder - tag %zd", reactTag);
RCTLogError(@"Invalid view set to be the JS responder - tag %zd", reactTag);
}
}];
}
@@ -1102,7 +1099,11 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}];
}
+ (NSDictionary *)allBubblingEventTypesConfigs
// TODO: these event types should be distributed among the modules
// that declare them. Also, events should be registerable by any class
// that can call event handlers, not just UIViewManagers. This code
// also seems highly redundant - every event has the same properties.
- (NSDictionary *)customBubblingEventTypes
{
NSMutableDictionary *customBubblingEventTypesConfigs = [@{
// Bubble dispatched events
@@ -1192,11 +1193,12 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
},
} mutableCopy];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
[_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
if (RCTClassOverridesClassMethod(cls, @selector(customBubblingEventTypes))) {
NSDictionary *eventTypes = [cls customBubblingEventTypes];
for (NSString *eventName in eventTypes) {
RCTCAssert(!customBubblingEventTypesConfigs[eventName], @"Event '%@' registered multiple times.", eventName);
RCTCAssert(!customBubblingEventTypesConfigs[eventName],
@"Event '%@' registered multiple times.", eventName);
}
[customBubblingEventTypesConfigs addEntriesFromDictionary:eventTypes];
}
@@ -1205,7 +1207,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
return customBubblingEventTypesConfigs;
}
+ (NSDictionary *)allDirectEventTypesConfigs
- (NSDictionary *)customDirectEventTypes
{
NSMutableDictionary *customDirectEventTypes = [@{
@"topScrollBeginDrag": @{
@@ -1243,7 +1245,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
},
} mutableCopy];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
[_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
if (RCTClassOverridesClassMethod(cls, @selector(customDirectEventTypes))) {
NSDictionary *eventTypes = [cls customDirectEventTypes];
for (NSString *eventName in eventTypes) {
@@ -1256,11 +1258,11 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
return customDirectEventTypes;
}
+ (NSDictionary *)constantsToExport
- (NSDictionary *)constantsToExport
{
NSMutableDictionary *allJSConstants = [@{
@"customBubblingEventTypes": [self allBubblingEventTypesConfigs],
@"customDirectEventTypes": [self allDirectEventTypesConfigs],
@"customBubblingEventTypes": [self customBubblingEventTypes],
@"customDirectEventTypes": [self customDirectEventTypes],
@"NSTextAlignment": @{
@"Left": @(NSTextAlignmentLeft),
@"Center": @(NSTextAlignmentCenter),
@@ -1320,15 +1322,15 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
},
} mutableCopy];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
[_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
// TODO: should these be inherited?
NSDictionary *constants = RCTClassOverridesClassMethod(cls, @selector(constantsToExport)) ? [cls constantsToExport] : nil;
if ([constants count]) {
NSMutableDictionary *namespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
RCTAssert(namespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
NSMutableDictionary *constantsNamespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
// add an additional 'Constants' namespace for each class
namespace[@"Constants"] = constants;
allJSConstants[name] = [namespace copy];
constantsNamespace[@"Constants"] = constants;
allJSConstants[name] = [constantsNamespace copy];
}
}];
@@ -1342,10 +1344,11 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
RCT_EXPORT();
if (_nextLayoutAnimation) {
RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", _nextLayoutAnimation, config);
RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.",
_nextLayoutAnimation, config);
}
if (config[@"delete"] != nil) {
RCTLogError(@"LayoutAnimation only supports create and update right now. Config: %@", config);
RCTLogError(@"LayoutAnimation only supports create and update right now. Config: %@", config);
}
_nextLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDictionary:config callback:callback];
}
@@ -1389,3 +1392,12 @@ static UIView *_jsResponder;
}
@end
@implementation RCTBridge (RCTUIManager)
- (RCTUIManager *)uiManager
{
return self.modules[NSStringFromClass([RCTUIManager class])];
}
@end