2015-03-23 20:28:42 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
#import "RCTUIManager.h"
|
|
|
|
|
|
|
|
#import <objc/message.h>
|
|
|
|
|
2015-03-10 21:23:03 +00:00
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "Layout.h"
|
|
|
|
#import "RCTAnimationType.h"
|
|
|
|
#import "RCTAssert.h"
|
|
|
|
#import "RCTBridge.h"
|
|
|
|
#import "RCTConvert.h"
|
2015-04-21 12:26:51 +00:00
|
|
|
#import "RCTDefines.h"
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
#import "RCTEventDispatcher.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "RCTLog.h"
|
2015-04-20 11:55:05 +00:00
|
|
|
#import "RCTProfile.h"
|
2015-03-10 21:23:03 +00:00
|
|
|
#import "RCTRootView.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "RCTScrollableProtocol.h"
|
|
|
|
#import "RCTShadowView.h"
|
|
|
|
#import "RCTSparseArray.h"
|
|
|
|
#import "RCTUtils.h"
|
|
|
|
#import "RCTView.h"
|
|
|
|
#import "RCTViewManager.h"
|
2015-03-10 21:23:03 +00:00
|
|
|
#import "RCTViewNodeProtocol.h"
|
2015-03-26 09:58:06 +00:00
|
|
|
#import "UIView+React.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, void (^block)(id<RCTViewNodeProtocol>));
|
|
|
|
static NSDictionary *RCTPropsMerge(NSDictionary *beforeProps, NSDictionary *newProps);
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
@interface RCTAnimation : NSObject
|
|
|
|
|
|
|
|
@property (nonatomic, readonly) NSTimeInterval duration;
|
|
|
|
@property (nonatomic, readonly) NSTimeInterval delay;
|
|
|
|
@property (nonatomic, readonly, copy) NSString *property;
|
|
|
|
@property (nonatomic, readonly) id fromValue;
|
|
|
|
@property (nonatomic, readonly) id toValue;
|
|
|
|
@property (nonatomic, readonly) CGFloat springDamping;
|
|
|
|
@property (nonatomic, readonly) CGFloat initialVelocity;
|
|
|
|
@property (nonatomic, readonly) RCTAnimationType animationType;
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation RCTAnimation
|
|
|
|
|
2015-06-25 16:14:19 +00:00
|
|
|
static UIViewAnimationOptions UIViewAnimationOptionsFromRCTAnimationType(RCTAnimationType type)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
switch (type) {
|
|
|
|
case RCTAnimationTypeLinear:
|
2015-06-25 16:14:19 +00:00
|
|
|
return UIViewAnimationOptionCurveLinear;
|
2015-02-20 04:10:52 +00:00
|
|
|
case RCTAnimationTypeEaseIn:
|
2015-06-25 16:14:19 +00:00
|
|
|
return UIViewAnimationOptionCurveEaseIn;
|
2015-02-20 04:10:52 +00:00
|
|
|
case RCTAnimationTypeEaseOut:
|
2015-06-25 16:14:19 +00:00
|
|
|
return UIViewAnimationOptionCurveEaseOut;
|
2015-02-20 04:10:52 +00:00
|
|
|
case RCTAnimationTypeEaseInEaseOut:
|
2015-06-25 16:14:19 +00:00
|
|
|
return UIViewAnimationOptionCurveEaseInOut;
|
|
|
|
case RCTAnimationTypeKeyboard:
|
|
|
|
// http://stackoverflow.com/questions/18870447/how-to-use-the-default-ios7-uianimation-curve
|
|
|
|
return (UIViewAnimationOptions)(7 << 16);
|
2015-02-20 04:10:52 +00:00
|
|
|
default:
|
2015-03-25 00:37:03 +00:00
|
|
|
RCTLogError(@"Unsupported animation type %zd", type);
|
2015-06-25 16:14:19 +00:00
|
|
|
return UIViewAnimationOptionCurveEaseInOut;
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)initWithDuration:(NSTimeInterval)duration dictionary:(NSDictionary *)config
|
|
|
|
{
|
|
|
|
if (!config) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((self = [super init])) {
|
|
|
|
_property = [RCTConvert NSString:config[@"property"]];
|
|
|
|
|
2015-03-30 14:12:38 +00:00
|
|
|
_duration = [RCTConvert NSTimeInterval:config[@"duration"]] ?: duration;
|
|
|
|
if (_duration > 0.0 && _duration < 0.01) {
|
|
|
|
RCTLogError(@"RCTLayoutAnimation expects timings to be in ms, not seconds.");
|
|
|
|
_duration = _duration * 1000.0;
|
|
|
|
}
|
|
|
|
|
|
|
|
_delay = [RCTConvert NSTimeInterval:config[@"delay"]];
|
|
|
|
if (_delay > 0.0 && _delay < 0.01) {
|
|
|
|
RCTLogError(@"RCTLayoutAnimation expects timings to be in ms, not seconds.");
|
|
|
|
_delay = _delay * 1000.0;
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
_animationType = [RCTConvert RCTAnimationType:config[@"type"]];
|
|
|
|
if (_animationType == RCTAnimationTypeSpring) {
|
|
|
|
_springDamping = [RCTConvert CGFloat:config[@"springDamping"]];
|
|
|
|
_initialVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]];
|
|
|
|
}
|
|
|
|
_fromValue = config[@"fromValue"];
|
|
|
|
_toValue = config[@"toValue"];
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)performAnimations:(void (^)(void))animations
|
|
|
|
withCompletionBlock:(void (^)(BOOL completed))completionBlock
|
|
|
|
{
|
|
|
|
if (_animationType == RCTAnimationTypeSpring) {
|
|
|
|
|
|
|
|
[UIView animateWithDuration:_duration
|
|
|
|
delay:_delay
|
|
|
|
usingSpringWithDamping:_springDamping
|
|
|
|
initialSpringVelocity:_initialVelocity
|
|
|
|
options:UIViewAnimationOptionBeginFromCurrentState
|
|
|
|
animations:animations
|
|
|
|
completion:completionBlock];
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState |
|
2015-06-25 16:14:19 +00:00
|
|
|
UIViewAnimationOptionsFromRCTAnimationType(_animationType);
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
[UIView animateWithDuration:_duration
|
|
|
|
delay:_delay
|
|
|
|
options:options
|
|
|
|
animations:animations
|
|
|
|
completion:completionBlock];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@interface RCTLayoutAnimation : NSObject
|
|
|
|
|
2015-07-07 21:14:14 +00:00
|
|
|
@property (nonatomic, copy) NSDictionary *config;
|
2015-02-20 04:10:52 +00:00
|
|
|
@property (nonatomic, strong) RCTAnimation *createAnimation;
|
|
|
|
@property (nonatomic, strong) RCTAnimation *updateAnimation;
|
|
|
|
@property (nonatomic, strong) RCTAnimation *deleteAnimation;
|
2015-07-07 21:14:14 +00:00
|
|
|
@property (nonatomic, copy) RCTResponseSenderBlock callback;
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation RCTLayoutAnimation
|
|
|
|
|
|
|
|
- (instancetype)initWithDictionary:(NSDictionary *)config callback:(RCTResponseSenderBlock)callback
|
|
|
|
{
|
|
|
|
if (!config) {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((self = [super init])) {
|
2015-07-07 21:14:14 +00:00
|
|
|
_config = [config copy];
|
2015-03-30 14:12:38 +00:00
|
|
|
NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]];
|
|
|
|
if (duration > 0.0 && duration < 0.01) {
|
|
|
|
RCTLogError(@"RCTLayoutAnimation expects timings to be in ms, not seconds.");
|
|
|
|
duration = duration * 1000.0;
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
_createAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"create"]];
|
|
|
|
_updateAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"update"]];
|
|
|
|
_deleteAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"delete"]];
|
|
|
|
_callback = callback;
|
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-03-25 00:37:03 +00:00
|
|
|
@interface RCTUIManager ()
|
|
|
|
|
|
|
|
// NOTE: these are properties so that they can be accessed by unit tests
|
|
|
|
@property (nonatomic, strong) RCTSparseArray *viewManagerRegistry; // RCT thread only
|
|
|
|
@property (nonatomic, strong) RCTSparseArray *shadowViewRegistry; // RCT thread only
|
|
|
|
@property (nonatomic, strong) RCTSparseArray *viewRegistry; // Main thread only
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
@implementation RCTUIManager
|
|
|
|
{
|
2015-04-18 17:43:20 +00:00
|
|
|
dispatch_queue_t _shadowQueue;
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Root views are only mutated on the shadow queue
|
|
|
|
NSMutableSet *_rootViewTags;
|
|
|
|
NSMutableArray *_pendingUIBlocks;
|
|
|
|
NSLock *_pendingUIBlocksLock;
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Animation
|
|
|
|
RCTLayoutAnimation *_nextLayoutAnimation; // RCT thread only
|
|
|
|
RCTLayoutAnimation *_layoutAnimation; // Main thread only
|
|
|
|
|
2015-03-01 23:33:55 +00:00
|
|
|
// Keyed by viewName
|
2015-02-20 04:10:52 +00:00
|
|
|
NSMutableDictionary *_defaultShadowViews; // RCT thread only
|
|
|
|
NSMutableDictionary *_defaultViews; // Main thread only
|
|
|
|
NSDictionary *_viewManagers;
|
2015-04-17 00:14:11 +00:00
|
|
|
NSDictionary *_viewConfigs;
|
2015-06-01 10:01:55 +00:00
|
|
|
|
|
|
|
NSMutableSet *_bridgeTransactionListeners;
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-04-02 14:33:21 +00:00
|
|
|
@synthesize bridge = _bridge;
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
|
2015-04-07 14:36:26 +00:00
|
|
|
/**
|
|
|
|
* Declared in RCTBridge.
|
|
|
|
*/
|
|
|
|
extern NSString *RCTBridgeModuleNameForClass(Class cls);
|
|
|
|
|
2015-03-01 23:33:55 +00:00
|
|
|
/**
|
|
|
|
* This function derives the view name automatically
|
|
|
|
* from the module name.
|
|
|
|
*/
|
|
|
|
static NSString *RCTViewNameForModuleName(NSString *moduleName)
|
|
|
|
{
|
|
|
|
NSString *name = moduleName;
|
2015-04-11 22:08:00 +00:00
|
|
|
RCTAssert(name.length, @"Invalid moduleName '%@'", moduleName);
|
2015-03-01 23:33:55 +00:00
|
|
|
if ([name hasSuffix:@"Manager"]) {
|
|
|
|
name = [name substringToIndex:name.length - @"Manager".length];
|
|
|
|
}
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2015-04-21 16:48:29 +00:00
|
|
|
// TODO: only send name once instead of a dictionary of name and type keyed by name
|
2015-06-15 14:53:45 +00:00
|
|
|
static NSDictionary *RCTViewConfigForModule(Class managerClass)
|
2015-04-17 00:14:11 +00:00
|
|
|
{
|
2015-04-22 11:03:46 +00:00
|
|
|
unsigned int count = 0;
|
|
|
|
Method *methods = class_copyMethodList(object_getClass(managerClass), &count);
|
|
|
|
NSMutableDictionary *props = [[NSMutableDictionary alloc] initWithCapacity:count];
|
|
|
|
for (unsigned int i = 0; i < count; i++) {
|
2015-04-17 00:14:11 +00:00
|
|
|
Method method = methods[i];
|
2015-04-22 11:03:46 +00:00
|
|
|
NSString *methodName = NSStringFromSelector(method_getName(method));
|
|
|
|
if ([methodName hasPrefix:@"getPropConfig"]) {
|
|
|
|
NSRange nameRange = [methodName rangeOfString:@"_"];
|
2015-04-21 16:48:29 +00:00
|
|
|
if (nameRange.length) {
|
2015-04-22 11:03:46 +00:00
|
|
|
NSString *name = [methodName substringFromIndex:nameRange.location + 1];
|
|
|
|
NSString *type = [managerClass valueForKey:methodName];
|
|
|
|
props[name] = type;
|
2015-04-21 16:48:29 +00:00
|
|
|
}
|
2015-04-17 00:14:11 +00:00
|
|
|
}
|
|
|
|
}
|
2015-04-22 11:03:46 +00:00
|
|
|
free(methods);
|
|
|
|
return props;
|
2015-04-17 00:14:11 +00:00
|
|
|
}
|
|
|
|
|
2015-03-01 23:33:55 +00:00
|
|
|
- (instancetype)init
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
if ((self = [super init])) {
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-04-18 17:43:20 +00:00
|
|
|
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
_pendingUIBlocksLock = [[NSLock alloc] init];
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
_defaultShadowViews = [[NSMutableDictionary alloc] init];
|
|
|
|
_defaultViews = [[NSMutableDictionary alloc] init];
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
_viewManagerRegistry = [[RCTSparseArray alloc] init];
|
|
|
|
_shadowViewRegistry = [[RCTSparseArray alloc] init];
|
|
|
|
_viewRegistry = [[RCTSparseArray alloc] init];
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Internal resources
|
|
|
|
_pendingUIBlocks = [[NSMutableArray alloc] init];
|
|
|
|
_rootViewTags = [[NSMutableSet alloc] init];
|
2015-06-01 10:01:55 +00:00
|
|
|
|
|
|
|
_bridgeTransactionListeners = [[NSMutableSet alloc] init];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)isValid
|
|
|
|
{
|
|
|
|
return _viewRegistry != nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)invalidate
|
|
|
|
{
|
2015-05-04 17:35:49 +00:00
|
|
|
/**
|
|
|
|
* Called on the JS Thread since all modules are invalidated on the JS thread
|
|
|
|
*/
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-05-04 17:35:49 +00:00
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
for (NSNumber *rootViewTag in _rootViewTags) {
|
2015-05-12 14:44:38 +00:00
|
|
|
[_viewRegistry[rootViewTag] invalidate];
|
2015-05-04 17:35:49 +00:00
|
|
|
}
|
2015-04-11 22:08:00 +00:00
|
|
|
|
2015-05-04 17:35:49 +00:00
|
|
|
_rootViewTags = nil;
|
|
|
|
_shadowViewRegistry = nil;
|
|
|
|
_viewRegistry = nil;
|
2015-06-01 10:01:55 +00:00
|
|
|
_bridgeTransactionListeners = nil;
|
2015-05-04 17:35:49 +00:00
|
|
|
_bridge = nil;
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-05-04 17:35:49 +00:00
|
|
|
[_pendingUIBlocksLock lock];
|
|
|
|
_pendingUIBlocks = nil;
|
|
|
|
[_pendingUIBlocksLock unlock];
|
|
|
|
});
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-04-11 22:08:00 +00:00
|
|
|
- (void)setBridge:(RCTBridge *)bridge
|
|
|
|
{
|
|
|
|
RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance");
|
|
|
|
|
|
|
|
_bridge = bridge;
|
|
|
|
_shadowViewRegistry = [[RCTSparseArray alloc] init];
|
|
|
|
|
|
|
|
// Get view managers from bridge
|
|
|
|
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
|
2015-04-17 00:14:11 +00:00
|
|
|
NSMutableDictionary *viewConfigs = [[NSMutableDictionary alloc] init];
|
2015-06-15 14:53:45 +00:00
|
|
|
[_bridge.modules enumerateKeysAndObjectsUsingBlock:
|
|
|
|
^(NSString *moduleName, RCTViewManager *manager, __unused BOOL *stop) {
|
2015-04-11 22:08:00 +00:00
|
|
|
if ([manager isKindOfClass:[RCTViewManager class]]) {
|
2015-04-17 00:14:11 +00:00
|
|
|
NSString *viewName = RCTViewNameForModuleName(moduleName);
|
|
|
|
viewManagers[viewName] = manager;
|
2015-06-15 14:53:45 +00:00
|
|
|
viewConfigs[viewName] = RCTViewConfigForModule([manager class]);
|
2015-04-11 22:08:00 +00:00
|
|
|
}
|
|
|
|
}];
|
|
|
|
|
|
|
|
_viewManagers = [viewManagers copy];
|
2015-04-17 00:14:11 +00:00
|
|
|
_viewConfigs = [viewConfigs copy];
|
2015-04-11 22:08:00 +00:00
|
|
|
}
|
|
|
|
|
2015-04-18 17:43:20 +00:00
|
|
|
- (dispatch_queue_t)methodQueue
|
|
|
|
{
|
|
|
|
return _shadowQueue;
|
|
|
|
}
|
|
|
|
|
2015-06-15 14:53:45 +00:00
|
|
|
- (void)registerRootView:(UIView *)rootView
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
RCTAssertMainThread();
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
NSNumber *reactTag = rootView.reactTag;
|
2015-03-25 00:37:03 +00:00
|
|
|
RCTAssert(RCTIsReactRootView(reactTag),
|
|
|
|
@"View %@ with tag #%@ is not a root view", rootView, reactTag);
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
UIView *existingView = _viewRegistry[reactTag];
|
2015-04-11 22:08:00 +00:00
|
|
|
RCTAssert(existingView == nil || existingView == rootView,
|
|
|
|
@"Expect all root views to have unique tag. Added %@ twice", reactTag);
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Register view
|
|
|
|
_viewRegistry[reactTag] = rootView;
|
|
|
|
CGRect frame = rootView.frame;
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Register shadow view
|
2015-06-06 21:12:37 +00:00
|
|
|
__weak RCTUIManager *weakSelf = self;
|
2015-03-01 23:33:55 +00:00
|
|
|
dispatch_async(_shadowQueue, ^{
|
2015-06-06 21:12:37 +00:00
|
|
|
RCTUIManager *strongSelf = weakSelf;
|
|
|
|
if (!strongSelf.isValid) {
|
|
|
|
return;
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
RCTShadowView *shadowView = [[RCTShadowView alloc] init];
|
|
|
|
shadowView.reactTag = reactTag;
|
|
|
|
shadowView.frame = frame;
|
2015-05-26 11:14:31 +00:00
|
|
|
shadowView.backgroundColor = rootView.backgroundColor;
|
|
|
|
shadowView.viewName = NSStringFromClass([rootView class]);
|
2015-06-06 21:12:37 +00:00
|
|
|
strongSelf->_shadowViewRegistry[shadowView.reactTag] = shadowView;
|
|
|
|
[strongSelf->_rootViewTags addObject:reactTag];
|
2015-02-20 04:10:52 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-06-24 17:14:37 +00:00
|
|
|
- (UIView *)viewForReactTag:(NSNumber *)reactTag
|
|
|
|
{
|
|
|
|
RCTAssertMainThread();
|
|
|
|
return _viewRegistry[reactTag];
|
|
|
|
}
|
|
|
|
|
2015-03-25 00:37:03 +00:00
|
|
|
- (void)setFrame:(CGRect)frame forRootView:(UIView *)rootView
|
|
|
|
{
|
|
|
|
RCTAssertMainThread();
|
|
|
|
|
|
|
|
NSNumber *reactTag = rootView.reactTag;
|
|
|
|
RCTAssert(RCTIsReactRootView(reactTag), @"Specified view %@ is not a root view", reactTag);
|
|
|
|
|
2015-04-18 17:43:20 +00:00
|
|
|
dispatch_async(_shadowQueue, ^{
|
2015-03-25 00:37:03 +00:00
|
|
|
RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
|
2015-04-07 14:36:26 +00:00
|
|
|
RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);
|
2015-03-25 00:37:03 +00:00
|
|
|
rootShadowView.frame = frame;
|
|
|
|
[rootShadowView updateLayout];
|
|
|
|
|
2015-06-12 18:51:09 +00:00
|
|
|
[self batchDidComplete];
|
2015-03-25 00:37:03 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-05-26 11:14:31 +00:00
|
|
|
- (void)setBackgroundColor:(UIColor *)color forRootView:(UIView *)rootView
|
|
|
|
{
|
|
|
|
RCTAssertMainThread();
|
|
|
|
|
|
|
|
NSNumber *reactTag = rootView.reactTag;
|
|
|
|
RCTAssert(RCTIsReactRootView(reactTag), @"Specified view %@ is not a root view", reactTag);
|
|
|
|
|
2015-06-06 20:37:51 +00:00
|
|
|
__weak RCTUIManager *weakSelf = self;
|
2015-05-26 11:14:31 +00:00
|
|
|
dispatch_async(_shadowQueue, ^{
|
2015-06-06 20:37:51 +00:00
|
|
|
RCTUIManager *strongSelf = weakSelf;
|
|
|
|
if (!strongSelf.isValid) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
RCTShadowView *rootShadowView = strongSelf->_shadowViewRegistry[reactTag];
|
2015-05-26 11:14:31 +00:00
|
|
|
RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);
|
|
|
|
rootShadowView.backgroundColor = color;
|
|
|
|
[self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootShadowView];
|
|
|
|
[self flushUIBlocks];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
/**
|
|
|
|
* Unregisters views from registries
|
|
|
|
*/
|
|
|
|
- (void)_purgeChildren:(NSArray *)children fromRegistry:(RCTSparseArray *)registry
|
|
|
|
{
|
|
|
|
for (id<RCTViewNodeProtocol> child in children) {
|
|
|
|
RCTTraverseViewNodes(registry[child.reactTag], ^(id<RCTViewNodeProtocol> subview) {
|
|
|
|
RCTAssert(![subview isReactRootView], @"Root views should not be unregistered");
|
|
|
|
if ([subview conformsToProtocol:@protocol(RCTInvalidating)]) {
|
|
|
|
[(id<RCTInvalidating>)subview invalidate];
|
|
|
|
}
|
|
|
|
registry[subview.reactTag] = nil;
|
2015-06-01 10:01:55 +00:00
|
|
|
|
|
|
|
if (registry == _viewRegistry) {
|
|
|
|
[_bridgeTransactionListeners removeObject:subview];
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)addUIBlock:(RCTViewManagerUIBlock)block
|
|
|
|
{
|
2015-05-25 12:19:53 +00:00
|
|
|
RCTAssertThread(_shadowQueue,
|
|
|
|
@"-[RCTUIManager addUIBlock:] should only be called from the "
|
|
|
|
"UIManager's _shadowQueue (it may be accessed via `bridge.uiManager.methodQueue`)");
|
|
|
|
|
2015-05-27 01:39:37 +00:00
|
|
|
if (!block) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-04-18 13:23:24 +00:00
|
|
|
if (!self.isValid) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
__weak RCTUIManager *weakViewManager = self;
|
|
|
|
dispatch_block_t outerBlock = ^{
|
|
|
|
RCTUIManager *strongViewManager = weakViewManager;
|
2015-04-18 13:23:24 +00:00
|
|
|
if (strongViewManager && strongViewManager.isValid) {
|
|
|
|
block(strongViewManager, strongViewManager->_viewRegistry);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-03-10 16:33:45 +00:00
|
|
|
[_pendingUIBlocksLock lock];
|
2015-02-20 04:10:52 +00:00
|
|
|
[_pendingUIBlocks addObject:outerBlock];
|
|
|
|
[_pendingUIBlocksLock unlock];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)rootShadowView
|
|
|
|
{
|
2015-04-18 17:43:20 +00:00
|
|
|
RCTAssert(![NSThread isMainThread], @"Should be called on shadow thread");
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
NSMutableSet *viewsWithNewFrames = [NSMutableSet setWithCapacity:1];
|
|
|
|
|
|
|
|
// This is nuanced. In the JS thread, we create a new update buffer
|
|
|
|
// `frameTags`/`frames` that is created/mutated in the JS thread. We access
|
|
|
|
// 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.
|
2015-03-01 23:33:55 +00:00
|
|
|
[rootShadowView collectRootUpdatedFrames:viewsWithNewFrames
|
|
|
|
parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}];
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
NSSet *originalViewsWithNewFrames = [viewsWithNewFrames copy];
|
|
|
|
NSMutableArray *viewsToCheck = [viewsWithNewFrames.allObjects mutableCopy];
|
|
|
|
while (viewsToCheck.count > 0) {
|
|
|
|
// Better to remove from the front and append to the end
|
|
|
|
// because of how NSMutableArray is implemented.
|
|
|
|
// (It's a "round" buffer with stored size and offset.)
|
|
|
|
|
|
|
|
RCTShadowView *viewToCheck = viewsToCheck.firstObject;
|
|
|
|
[viewsToCheck removeObjectAtIndex:0];
|
|
|
|
|
|
|
|
if (viewToCheck.layoutOnly) {
|
|
|
|
[viewsWithNewFrames removeObject:viewToCheck];
|
|
|
|
[viewsToCheck addObjectsFromArray:[viewToCheck reactSubviews]];
|
|
|
|
} else {
|
|
|
|
[viewsWithNewFrames addObject:viewToCheck];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
// Parallel arrays are built and then handed off to main thread
|
2015-02-20 04:10:52 +00:00
|
|
|
NSMutableArray *frameReactTags = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
|
|
|
NSMutableArray *frames = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
|
|
|
NSMutableArray *areNew = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
|
|
|
NSMutableArray *parentsAreNew = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
NSMutableArray *onLayoutEvents = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
for (RCTShadowView *shadowView in viewsWithNewFrames) {
|
2015-07-05 07:41:58 +00:00
|
|
|
CGRect frame = shadowView.adjustedFrame;
|
|
|
|
NSNumber *reactTag = shadowView.reactTag;
|
|
|
|
[frameReactTags addObject:reactTag];
|
|
|
|
[frames addObject:[NSValue valueWithCGRect:frame]];
|
2015-02-20 04:10:52 +00:00
|
|
|
[areNew addObject:@(shadowView.isNewView)];
|
2015-07-05 07:41:58 +00:00
|
|
|
|
|
|
|
RCTShadowView *superview = shadowView;
|
|
|
|
BOOL parentIsNew = NO;
|
|
|
|
while (YES) {
|
|
|
|
superview = superview.superview;
|
|
|
|
parentIsNew = superview.isNewView;
|
|
|
|
if (!superview.layoutOnly) {
|
|
|
|
break;
|
|
|
|
}
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
}
|
2015-07-05 07:41:58 +00:00
|
|
|
[parentsAreNew addObject:@(parentIsNew)];
|
|
|
|
|
|
|
|
id event = shadowView.hasOnLayout
|
|
|
|
? RCTShadowViewOnLayoutEventPayload(shadowView.reactTag, frame)
|
|
|
|
: (id)kCFNull;
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
[onLayoutEvents addObject:event];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
for (RCTShadowView *shadowView in originalViewsWithNewFrames) {
|
2015-02-20 04:10:52 +00:00
|
|
|
// We have to do this after we build the parentsAreNew array.
|
|
|
|
shadowView.newView = NO;
|
|
|
|
}
|
|
|
|
|
2015-06-01 15:34:09 +00:00
|
|
|
// These are blocks to be executed on each view, immediately after
|
|
|
|
// reactSetFrame: has been called. Note that if reactSetFrame: is not called,
|
|
|
|
// these won't be called either, so this is not a suitable place to update
|
|
|
|
// properties that aren't related to layout.
|
2015-03-01 23:33:55 +00:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Perform layout (possibly animated)
|
2015-07-05 07:41:58 +00:00
|
|
|
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
|
|
|
RCTResponseSenderBlock callback = uiManager->_layoutAnimation.callback;
|
2015-06-15 14:53:45 +00:00
|
|
|
__block NSUInteger completionsCalled = 0;
|
2015-02-20 04:10:52 +00:00
|
|
|
for (NSUInteger ii = 0; ii < frames.count; ii++) {
|
|
|
|
NSNumber *reactTag = frameReactTags[ii];
|
|
|
|
UIView *view = viewRegistry[reactTag];
|
2015-07-05 07:41:58 +00:00
|
|
|
if (!view) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
CGRect frame = [frames[ii] CGRectValue];
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
id event = onLayoutEvents[ii];
|
|
|
|
|
|
|
|
BOOL isNew = [areNew[ii] boolValue];
|
2015-07-05 07:41:58 +00:00
|
|
|
RCTAnimation *updateAnimation = isNew ? nil : uiManager->_layoutAnimation.updateAnimation;
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
|
2015-07-05 07:41:58 +00:00
|
|
|
RCTAnimation *createAnimation = shouldAnimateCreation ? uiManager->_layoutAnimation.createAnimation : nil;
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-06-15 14:53:45 +00:00
|
|
|
void (^completion)(BOOL) = ^(BOOL finished) {
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
completionsCalled++;
|
2015-06-12 18:05:01 +00:00
|
|
|
if (event != (id)kCFNull) {
|
2015-07-05 07:41:58 +00:00
|
|
|
[uiManager.bridge.eventDispatcher sendInputEventWithName:@"topLayout" body:event];
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
}
|
|
|
|
if (callback && completionsCalled == frames.count - 1) {
|
|
|
|
callback(@[@(finished)]);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Animate view update
|
|
|
|
if (updateAnimation) {
|
|
|
|
[updateAnimation performAnimations:^{
|
2015-03-25 00:37:03 +00:00
|
|
|
[view reactSetFrame:frame];
|
2015-03-01 23:33:55 +00:00
|
|
|
for (RCTViewManagerUIBlock block in updateBlocks) {
|
2015-07-05 07:41:58 +00:00
|
|
|
block(uiManager, viewRegistry);
|
2015-03-01 23:33:55 +00:00
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
} withCompletionBlock:completion];
|
|
|
|
} else {
|
2015-03-25 00:37:03 +00:00
|
|
|
[view reactSetFrame:frame];
|
2015-03-01 23:33:55 +00:00
|
|
|
for (RCTViewManagerUIBlock block in updateBlocks) {
|
2015-07-05 07:41:58 +00:00
|
|
|
block(uiManager, viewRegistry);
|
2015-03-01 23:33:55 +00:00
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
completion(YES);
|
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
|
|
|
|
// Animate view creation
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
if (createAnimation) {
|
2015-02-20 04:10:52 +00:00
|
|
|
if ([createAnimation.property isEqualToString:@"scaleXY"]) {
|
|
|
|
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
|
|
|
|
} else if ([createAnimation.property isEqualToString:@"opacity"]) {
|
|
|
|
view.layer.opacity = 0.0;
|
|
|
|
}
|
|
|
|
[createAnimation performAnimations:^{
|
|
|
|
if ([createAnimation.property isEqual:@"scaleXY"]) {
|
|
|
|
view.layer.transform = CATransform3DIdentity;
|
|
|
|
} else if ([createAnimation.property isEqual:@"opacity"]) {
|
|
|
|
view.layer.opacity = 1.0;
|
|
|
|
} else {
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTLogError(@"Unsupported layout animation createConfig property %@",
|
|
|
|
createAnimation.property);
|
|
|
|
}
|
|
|
|
for (RCTViewManagerUIBlock block in updateBlocks) {
|
2015-07-05 07:41:58 +00:00
|
|
|
block(uiManager, viewRegistry);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
} withCompletionBlock:nil];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-01 23:33:55 +00:00
|
|
|
/**
|
2015-06-01 10:01:55 +00:00
|
|
|
* TODO(tadeu): Remove it once and for all
|
2015-03-01 23:33:55 +00:00
|
|
|
*/
|
2015-06-01 10:01:55 +00:00
|
|
|
for (id<RCTViewNodeProtocol> node in _bridgeTransactionListeners) {
|
|
|
|
[node reactBridgeDidFinishTransaction];
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_amendPendingUIBlocksWithStylePropagationUpdateForRootView:(RCTShadowView *)topView
|
|
|
|
{
|
|
|
|
NSMutableSet *applierBlocks = [NSMutableSet setWithCapacity:1];
|
|
|
|
[topView collectUpdatedProperties:applierBlocks parentProperties:@{}];
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
2015-02-20 04:10:52 +00:00
|
|
|
for (RCTApplierBlock block in applierBlocks) {
|
|
|
|
block(viewRegistry);
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A method to be called from JS, which takes a container ID and then releases
|
|
|
|
* all subviews for that container upon receipt.
|
|
|
|
*/
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID:(NSNumber *)containerID)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-03-31 08:11:18 +00:00
|
|
|
id<RCTViewNodeProtocol> container = _shadowViewRegistry[containerID];
|
2015-02-20 04:10:52 +00:00
|
|
|
RCTAssert(container != nil, @"container view (for ID %@) not found", containerID);
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-06-15 14:53:45 +00:00
|
|
|
NSUInteger subviewsCount = [container reactSubviews].count;
|
2015-02-20 04:10:52 +00:00
|
|
|
NSMutableArray *indices = [[NSMutableArray alloc] initWithCapacity:subviewsCount];
|
2015-06-15 14:53:45 +00:00
|
|
|
for (NSUInteger childIndex = 0; childIndex < subviewsCount; childIndex++) {
|
2015-02-20 04:10:52 +00:00
|
|
|
[indices addObject:@(childIndex)];
|
|
|
|
}
|
|
|
|
|
|
|
|
[self manageChildren:containerID
|
|
|
|
moveFromIndices:nil
|
|
|
|
moveToIndices:nil
|
|
|
|
addChildReactTags:nil
|
|
|
|
addAtIndices:nil
|
|
|
|
removeAtIndices:indices];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Disassociates children from container. Doesn't remove from registries.
|
|
|
|
* TODO: use [NSArray getObjects:buffer] to reuse same fast buffer each time.
|
|
|
|
*
|
|
|
|
* @returns Array of removed items.
|
|
|
|
*/
|
|
|
|
- (NSArray *)_childrenToRemoveFromContainer:(id<RCTViewNodeProtocol>)container
|
|
|
|
atIndices:(NSArray *)atIndices
|
|
|
|
{
|
|
|
|
// If there are no indices to move or the container has no subviews don't bother
|
|
|
|
// We support parents with nil subviews so long as they're all nil so this allows for this behavior
|
2015-06-15 14:53:45 +00:00
|
|
|
if (atIndices.count == 0 || [container reactSubviews].count == 0) {
|
2015-02-20 04:10:52 +00:00
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
// Construction of removed children must be done "up front", before indices are disturbed by removals.
|
|
|
|
NSMutableArray *removedChildren = [NSMutableArray arrayWithCapacity:atIndices.count];
|
2015-04-11 22:08:00 +00:00
|
|
|
RCTAssert(container != nil, @"container view (for ID %@) not found", container);
|
2015-06-15 14:53:45 +00:00
|
|
|
for (NSNumber *indexNumber in atIndices) {
|
|
|
|
NSUInteger index = indexNumber.unsignedIntegerValue;
|
|
|
|
if (index < [container reactSubviews].count) {
|
2015-02-20 04:10:52 +00:00
|
|
|
[removedChildren addObject:[container reactSubviews][index]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (removedChildren.count != atIndices.count) {
|
2015-06-15 14:53:45 +00:00
|
|
|
RCTLogMustFix(@"removedChildren count (%tu) was not what we expected (%tu)",
|
|
|
|
removedChildren.count, atIndices.count);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
return removedChildren;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_removeChildren:(NSArray *)children fromContainer:(id<RCTViewNodeProtocol>)container
|
|
|
|
{
|
|
|
|
for (id removedChild in children) {
|
|
|
|
[container removeReactSubview:removedChild];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(removeRootView:(NSNumber *)rootReactTag)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
RCTShadowView *rootShadowView = _shadowViewRegistry[rootReactTag];
|
|
|
|
RCTAssert(rootShadowView.superview == nil, @"root view cannot have superview (ID %@)", rootReactTag);
|
2015-03-25 00:37:03 +00:00
|
|
|
[self _purgeChildren:rootShadowView.reactSubviews fromRegistry:_shadowViewRegistry];
|
|
|
|
_shadowViewRegistry[rootReactTag] = nil;
|
2015-02-20 04:10:52 +00:00
|
|
|
[_rootViewTags removeObject:rootReactTag];
|
|
|
|
|
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
2015-04-11 22:08:00 +00:00
|
|
|
RCTAssertMainThread();
|
2015-02-20 04:10:52 +00:00
|
|
|
UIView *rootView = viewRegistry[rootReactTag];
|
2015-03-25 00:37:03 +00:00
|
|
|
[uiManager _purgeChildren:rootView.reactSubviews fromRegistry:viewRegistry];
|
|
|
|
viewRegistry[rootReactTag] = nil;
|
2015-02-20 04:10:52 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(replaceExistingNonRootView:(NSNumber *)reactTag withView:(NSNumber *)newReactTag)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
|
|
|
RCTAssert(shadowView != nil, @"shadowView (for ID %@) not found", reactTag);
|
|
|
|
|
|
|
|
RCTShadowView *superShadowView = shadowView.superview;
|
|
|
|
RCTAssert(superShadowView != nil, @"shadowView super (of ID %@) not found", reactTag);
|
|
|
|
|
|
|
|
NSUInteger indexOfView = [superShadowView.reactSubviews indexOfObject:shadowView];
|
|
|
|
RCTAssert(indexOfView != NSNotFound, @"View's superview doesn't claim it as subview (id %@)", reactTag);
|
|
|
|
NSArray *removeAtIndices = @[@(indexOfView)];
|
|
|
|
NSArray *addTags = @[newReactTag];
|
|
|
|
[self manageChildren:superShadowView.reactTag
|
|
|
|
moveFromIndices:nil
|
|
|
|
moveToIndices:nil
|
|
|
|
addChildReactTags:addTags
|
|
|
|
addAtIndices:removeAtIndices
|
|
|
|
removeAtIndices:removeAtIndices];
|
|
|
|
}
|
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
/**
|
|
|
|
* This method modifies the indices received in manageChildren() to take into
|
|
|
|
* account views that are layout only. For example, if JS tells native to insert
|
|
|
|
* view with tag 12 at index 4, but view 12 is layout only, we would want to
|
|
|
|
* insert its children's tags, tags 13 and 14, at indices 4 and 5 instead. This
|
|
|
|
* causes us to have to shift the remaining indices to account for the new
|
|
|
|
* views.
|
|
|
|
*/
|
|
|
|
- (void)modifyManageChildren:(NSNumber *)containerReactTag
|
|
|
|
addChildReactTags:(NSMutableArray *)mutableAddChildReactTags
|
|
|
|
addAtIndices:(NSMutableArray *)mutableAddAtIndices
|
|
|
|
removeAtIndices:(NSMutableArray *)mutableRemoveAtIndices
|
|
|
|
{
|
|
|
|
NSUInteger i;
|
|
|
|
NSMutableArray *containerSubviews = [[_shadowViewRegistry[containerReactTag] reactSubviews] mutableCopy];
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while (i < containerSubviews.count) {
|
|
|
|
RCTShadowView *shadowView = containerSubviews[i];
|
|
|
|
if (!shadowView.layoutOnly) {
|
|
|
|
i++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
[containerSubviews removeObjectAtIndex:i];
|
|
|
|
|
|
|
|
NSArray *subviews = [shadowView reactSubviews];
|
|
|
|
NSUInteger subviewsCount = subviews.count;
|
|
|
|
NSIndexSet *insertionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, subviewsCount)];
|
|
|
|
[containerSubviews insertObjects:subviews atIndexes:insertionIndexes];
|
|
|
|
|
|
|
|
NSUInteger removalIndex = [mutableRemoveAtIndices indexOfObject:@(i)];
|
|
|
|
if (removalIndex != NSNotFound) {
|
|
|
|
[mutableRemoveAtIndices removeObjectAtIndex:removalIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (subviewsCount != 1) {
|
|
|
|
for (NSUInteger j = 0, count = mutableRemoveAtIndices.count; j < count; j++) {
|
|
|
|
NSUInteger atIndex = [mutableRemoveAtIndices[j] unsignedIntegerValue];
|
|
|
|
if (atIndex > i) {
|
|
|
|
mutableRemoveAtIndices[j] = @(atIndex + subviewsCount - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (removalIndex != NSNotFound) {
|
|
|
|
for (NSUInteger j = 0; j < subviewsCount; j++) {
|
|
|
|
[mutableRemoveAtIndices insertObject:@(i + j) atIndex:removalIndex + j];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (removalIndex == NSNotFound && subviewsCount != 1) {
|
|
|
|
for (NSUInteger j = 0, count = mutableAddAtIndices.count; j < count; j++) {
|
|
|
|
NSUInteger atIndex = [mutableAddAtIndices[j] unsignedIntegerValue];
|
|
|
|
if (atIndex > i) {
|
|
|
|
mutableAddAtIndices[j] = @(atIndex + subviewsCount - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
while (i < mutableAddChildReactTags.count) {
|
|
|
|
NSNumber *tag = mutableAddChildReactTags[i];
|
|
|
|
NSNumber *index = mutableAddAtIndices[i];
|
|
|
|
|
|
|
|
RCTShadowView *shadowView = _shadowViewRegistry[tag];
|
|
|
|
if (!shadowView.layoutOnly) {
|
|
|
|
i++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSArray *subviews = [shadowView reactSubviews];
|
|
|
|
NSUInteger subviewsCount = subviews.count;
|
|
|
|
[mutableAddAtIndices removeObjectAtIndex:i];
|
|
|
|
[mutableAddChildReactTags removeObjectAtIndex:i];
|
|
|
|
|
|
|
|
for (NSUInteger j = 0; j < subviewsCount; j++) {
|
|
|
|
[mutableAddChildReactTags insertObject:[subviews[j] reactTag] atIndex:i + j];
|
|
|
|
[mutableAddAtIndices insertObject:@(index.unsignedIntegerValue + j) atIndex:i + j];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (NSUInteger j = i + subviewsCount, count = mutableAddAtIndices.count; j < count; j++) {
|
|
|
|
NSUInteger atIndex = [mutableAddAtIndices[j] unsignedIntegerValue];
|
|
|
|
mutableAddAtIndices[j] = @(atIndex + subviewsCount - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSNumber *)containerReactTag:(NSNumber *)containerReactTag offset:(inout NSUInteger *)offset
|
|
|
|
{
|
|
|
|
RCTShadowView *container = _shadowViewRegistry[containerReactTag];
|
|
|
|
NSNumber *containerSuperviewReactTag = containerReactTag;
|
|
|
|
RCTShadowView *superview = container;
|
|
|
|
|
|
|
|
while (superview.layoutOnly) {
|
|
|
|
RCTShadowView *superviewSuperview = superview.superview;
|
|
|
|
containerSuperviewReactTag = superviewSuperview.reactTag;
|
|
|
|
NSMutableArray *reactSubviews = [[superviewSuperview reactSubviews] mutableCopy];
|
|
|
|
NSUInteger superviewIndex = [reactSubviews indexOfObject:superview];
|
|
|
|
|
|
|
|
NSUInteger i = 0;
|
|
|
|
while (i < superviewIndex) {
|
|
|
|
RCTShadowView *child = reactSubviews[i];
|
|
|
|
if (!child.layoutOnly) {
|
|
|
|
if (offset) {
|
|
|
|
(*offset)++;
|
|
|
|
}
|
|
|
|
|
|
|
|
i++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
[reactSubviews removeObjectAtIndex:i];
|
|
|
|
|
|
|
|
NSArray *subviews = [child reactSubviews];
|
|
|
|
NSUInteger subviewsCount = subviews.count;
|
|
|
|
NSIndexSet *insertionIndexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, subviewsCount)];
|
|
|
|
[reactSubviews insertObjects:subviews atIndexes:insertionIndexes];
|
|
|
|
|
|
|
|
superviewIndex += subviewsCount - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
superview = superviewSuperview;
|
|
|
|
}
|
|
|
|
|
|
|
|
return containerSuperviewReactTag;
|
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
|
|
|
|
moveFromIndices:(NSArray *)moveFromIndices
|
|
|
|
moveToIndices:(NSArray *)moveToIndices
|
|
|
|
addChildReactTags:(NSArray *)addChildReactTags
|
|
|
|
addAtIndices:(NSArray *)addAtIndices
|
|
|
|
removeAtIndices:(NSArray *)removeAtIndices)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-07-05 07:41:58 +00:00
|
|
|
RCTShadowView *container = _shadowViewRegistry[containerReactTag];
|
|
|
|
NSUInteger offset = 0;
|
|
|
|
NSNumber *containerSuperviewReactTag = [self containerReactTag:containerReactTag offset:&offset];
|
|
|
|
|
|
|
|
RCTAssert(moveFromIndices.count == moveToIndices.count, @"Invalid argument: moveFromIndices.count != moveToIndices.count");
|
|
|
|
if (moveFromIndices.count > 0) {
|
|
|
|
NSMutableArray *mutableAddChildReactTags = [addChildReactTags mutableCopy];
|
|
|
|
NSMutableArray *mutableAddAtIndices = [addAtIndices mutableCopy];
|
|
|
|
NSMutableArray *mutableRemoveAtIndices = [removeAtIndices mutableCopy];
|
|
|
|
|
|
|
|
NSArray *containerSubviews = [container reactSubviews];
|
|
|
|
for (NSUInteger i = 0, count = moveFromIndices.count; i < count; i++) {
|
|
|
|
NSNumber *from = moveFromIndices[i];
|
|
|
|
NSNumber *to = moveToIndices[i];
|
|
|
|
[mutableAddChildReactTags addObject:[containerSubviews[from.unsignedIntegerValue] reactTag]];
|
|
|
|
[mutableAddAtIndices addObject:to];
|
|
|
|
[mutableRemoveAtIndices addObject:from];
|
|
|
|
}
|
|
|
|
|
|
|
|
addChildReactTags = mutableAddChildReactTags;
|
|
|
|
addAtIndices = mutableAddAtIndices;
|
|
|
|
removeAtIndices = mutableRemoveAtIndices;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSMutableArray *mutableAddChildReactTags;
|
|
|
|
NSMutableArray *mutableAddAtIndices;
|
|
|
|
NSMutableArray *mutableRemoveAtIndices;
|
|
|
|
|
|
|
|
if (containerSuperviewReactTag) {
|
|
|
|
mutableAddChildReactTags = [addChildReactTags mutableCopy];
|
|
|
|
mutableAddAtIndices = [addAtIndices mutableCopy];
|
|
|
|
mutableRemoveAtIndices = [removeAtIndices mutableCopy];
|
|
|
|
|
|
|
|
[self modifyManageChildren:containerReactTag
|
|
|
|
addChildReactTags:mutableAddChildReactTags
|
|
|
|
addAtIndices:mutableAddAtIndices
|
|
|
|
removeAtIndices:mutableRemoveAtIndices];
|
|
|
|
|
|
|
|
if (offset > 0) {
|
|
|
|
NSUInteger count = MAX(mutableAddAtIndices.count, mutableRemoveAtIndices.count);
|
|
|
|
for (NSUInteger i = 0; i < count; i++) {
|
|
|
|
if (i < mutableAddAtIndices.count) {
|
|
|
|
NSUInteger index = [mutableAddAtIndices[i] unsignedIntegerValue];
|
|
|
|
mutableAddAtIndices[i] = @(index + offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i < mutableRemoveAtIndices.count) {
|
|
|
|
NSUInteger index = [mutableRemoveAtIndices[i] unsignedIntegerValue];
|
|
|
|
mutableRemoveAtIndices[i] = @(index + offset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
[self _manageChildren:containerReactTag
|
|
|
|
addChildReactTags:addChildReactTags
|
|
|
|
addAtIndices:addAtIndices
|
|
|
|
removeAtIndices:removeAtIndices
|
|
|
|
registry:_shadowViewRegistry];
|
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
if (containerSuperviewReactTag) {
|
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
|
|
|
(void)(id []){containerReactTag, @(offset), addChildReactTags, addAtIndices, removeAtIndices};
|
|
|
|
[uiManager _manageChildren:containerSuperviewReactTag
|
|
|
|
addChildReactTags:mutableAddChildReactTags
|
|
|
|
addAtIndices:mutableAddAtIndices
|
|
|
|
removeAtIndices:mutableRemoveAtIndices
|
|
|
|
registry:viewRegistry];
|
|
|
|
}];
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_manageChildren:(NSNumber *)containerReactTag
|
|
|
|
addChildReactTags:(NSArray *)addChildReactTags
|
|
|
|
addAtIndices:(NSArray *)addAtIndices
|
|
|
|
removeAtIndices:(NSArray *)removeAtIndices
|
|
|
|
registry:(RCTSparseArray *)registry
|
|
|
|
{
|
|
|
|
id<RCTViewNodeProtocol> container = registry[containerReactTag];
|
2015-07-05 07:41:58 +00:00
|
|
|
RCTAssert(addChildReactTags.count == addAtIndices.count, @"Invalid arguments: addChildReactTags.count == addAtIndices.count");
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
// Removes are using "before" indices
|
|
|
|
NSArray *removedChildren = [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices];
|
|
|
|
[self _removeChildren:removedChildren fromContainer:container];
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT reactTag in %@", addChildReactTags];
|
|
|
|
NSArray *permanentlyRemovedChildren = [removedChildren filteredArrayUsingPredicate:predicate];
|
2015-02-20 04:10:52 +00:00
|
|
|
[self _purgeChildren:permanentlyRemovedChildren fromRegistry:registry];
|
|
|
|
|
2015-04-18 17:43:20 +00:00
|
|
|
// TODO (#5906496): optimize all these loops - constantly calling array.count is not efficient
|
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
// Figure out what to insert
|
|
|
|
NSMutableDictionary *childrenToAdd = [NSMutableDictionary dictionary];
|
|
|
|
for (NSInteger index = 0, count = addAtIndices.count; index < count; index++) {
|
|
|
|
id<RCTViewNodeProtocol> view = registry[addChildReactTags[index]];
|
2015-02-20 04:10:52 +00:00
|
|
|
if (view) {
|
2015-07-05 07:41:58 +00:00
|
|
|
childrenToAdd[addAtIndices[index]] = view;
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
NSArray *sortedIndices = [[childrenToAdd allKeys] sortedArrayUsingSelector:@selector(compare:)];
|
2015-02-20 04:10:52 +00:00
|
|
|
for (NSNumber *reactIndex in sortedIndices) {
|
2015-07-05 07:41:58 +00:00
|
|
|
[container insertReactSubview:childrenToAdd[reactIndex] atIndex:reactIndex.integerValue];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-07 14:36:26 +00:00
|
|
|
static BOOL RCTCallPropertySetter(NSString *key, SEL setter, id value, id view, id defaultView, RCTViewManager *manager)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
// TODO: cache respondsToSelector tests
|
|
|
|
if ([manager respondsToSelector:setter]) {
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-06-12 18:05:01 +00:00
|
|
|
if (value == (id)kCFNull) {
|
2015-02-20 04:10:52 +00:00
|
|
|
value = nil;
|
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-04-07 14:36:26 +00:00
|
|
|
void (^block)() = ^{
|
|
|
|
((void (*)(id, SEL, id, id, id))objc_msgSend)(manager, setter, value, view, defaultView);
|
|
|
|
};
|
|
|
|
|
2015-04-21 12:26:51 +00:00
|
|
|
if (RCT_DEBUG) {
|
|
|
|
NSString *viewName = RCTViewNameForModuleName(RCTBridgeModuleNameForClass([manager class]));
|
|
|
|
NSString *logPrefix = [NSString stringWithFormat:
|
|
|
|
@"Error setting property '%@' of %@ with tag #%@: ",
|
|
|
|
key, viewName, [view reactTag]];
|
2015-04-07 14:36:26 +00:00
|
|
|
|
2015-04-21 12:26:51 +00:00
|
|
|
RCTPerformBlockWithLogPrefix(block, logPrefix);
|
|
|
|
} else {
|
|
|
|
block();
|
|
|
|
}
|
2015-04-07 14:36:26 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void RCTSetViewProps(NSDictionary *props, UIView *view,
|
|
|
|
UIView *defaultView, RCTViewManager *manager)
|
|
|
|
{
|
2015-06-15 14:53:45 +00:00
|
|
|
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, __unused BOOL *stop) {
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forView:withDefaultView:", key]);
|
2015-04-07 14:36:26 +00:00
|
|
|
RCTCallPropertySetter(key, setter, obj, view, defaultView, manager);
|
2015-03-26 04:29:28 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView,
|
|
|
|
RCTShadowView *defaultView, RCTViewManager *manager)
|
|
|
|
{
|
2015-06-15 14:53:45 +00:00
|
|
|
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, __unused BOOL *stop) {
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forShadowView:withDefaultView:", key]);
|
2015-04-07 14:36:26 +00:00
|
|
|
RCTCallPropertySetter(key, setter, obj, shadowView, defaultView, manager);
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
}];
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Update layout
|
2015-03-25 00:37:03 +00:00
|
|
|
[shadowView updateLayout];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(createView:(NSNumber *)reactTag
|
|
|
|
viewName:(NSString *)viewName
|
[ReactNative] Move module info from bridge to RCTModuleData
Summary:
@public
The info about bridge modules (such as id, name, queue, methods...) was spread
across arrays & dictionaries on the bridge, move it into a specific class.
It also removes a lot of information that was statically cached, and now have
the same lifecycle of the bridge.
Also moved RCTModuleMethod, RCTFrameUpdate and RCTBatchedBridge into it's own
files, for organization sake.
NOTE: This diff seems huge, but most of it was just moving code :)
Test Plan:
Tested UIExplorer & UIExplorer tests, Catalyst, MAdMan and Groups. Everything
looks fine.
2015-06-24 23:34:56 +00:00
|
|
|
rootTag:(__unused NSNumber *)rootTag
|
2015-04-08 15:52:48 +00:00
|
|
|
props:(NSDictionary *)props)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTViewManager *manager = _viewManagers[viewName];
|
2015-02-20 04:10:52 +00:00
|
|
|
if (manager == nil) {
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTLogWarn(@"No manager class found for view with module name \"%@\"", viewName);
|
2015-02-20 04:10:52 +00:00
|
|
|
manager = [[RCTViewManager alloc] init];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register manager
|
|
|
|
_viewManagerRegistry[reactTag] = manager;
|
|
|
|
|
|
|
|
RCTShadowView *shadowView = [manager shadowView];
|
2015-04-07 21:26:43 +00:00
|
|
|
if (shadowView) {
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
// Generate default view, used for resetting default props
|
2015-04-07 21:26:43 +00:00
|
|
|
if (!_defaultShadowViews[viewName]) {
|
|
|
|
_defaultShadowViews[viewName] = [manager shadowView];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-04-07 21:26:43 +00:00
|
|
|
// Set properties
|
|
|
|
shadowView.viewName = viewName;
|
|
|
|
shadowView.reactTag = reactTag;
|
2015-07-05 07:41:58 +00:00
|
|
|
shadowView.allProps = props;
|
2015-04-07 21:26:43 +00:00
|
|
|
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager);
|
|
|
|
}
|
|
|
|
_shadowViewRegistry[reactTag] = shadowView;
|
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
if (!shadowView.layoutOnly) {
|
|
|
|
// Shadow view is the source of truth for background color this is a little
|
|
|
|
// bit counter-intuitive if people try to set background color when setting up
|
|
|
|
// the view, but it's the only way that makes sense given our threading model
|
|
|
|
UIColor *backgroundColor = shadowView.backgroundColor;
|
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, __unused RCTSparseArray *viewRegistry) {
|
|
|
|
[uiManager createView:reactTag viewName:viewName props:props withManager:manager backgroundColor:backgroundColor];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
}
|
2015-04-07 21:26:43 +00:00
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
- (UIView *)createView:(NSNumber *)reactTag viewName:(NSString *)viewName props:(NSDictionary *)props withManager:(RCTViewManager *)manager backgroundColor:(UIColor *)backgroundColor
|
|
|
|
{
|
|
|
|
RCTAssertMainThread();
|
|
|
|
UIView *view = [manager view];
|
|
|
|
if (!view) {
|
|
|
|
return nil;
|
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
// Generate default view, used for resetting default props
|
|
|
|
if (!_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.
|
|
|
|
_defaultViews[viewName] = [manager view];
|
|
|
|
}
|
2015-06-01 10:01:55 +00:00
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
// Set properties
|
|
|
|
view.reactTag = reactTag;
|
|
|
|
view.backgroundColor = backgroundColor;
|
|
|
|
if ([view isKindOfClass:[UIView class]]) {
|
|
|
|
view.multipleTouchEnabled = YES;
|
|
|
|
view.userInteractionEnabled = YES; // required for touch handling
|
|
|
|
view.layer.allowsGroupOpacity = YES; // required for touch handling
|
|
|
|
}
|
|
|
|
RCTSetViewProps(props, view, _defaultViews[viewName], manager);
|
2015-06-25 16:07:32 +00:00
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) {
|
|
|
|
[_bridgeTransactionListeners addObject:view];
|
|
|
|
}
|
|
|
|
_viewRegistry[reactTag] = view;
|
2015-06-25 16:07:32 +00:00
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
return view;
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_INLINE BOOL RCTRectIsDefined(CGRect frame)
|
|
|
|
{
|
|
|
|
return !(isnan(frame.origin.x) || isnan(frame.origin.y) || isnan(frame.size.width) || isnan(frame.size.height));
|
|
|
|
}
|
|
|
|
|
|
|
|
NS_INLINE NSDictionary *RCTShadowViewOnLayoutEventPayload(NSNumber *reactTag, CGRect frame)
|
|
|
|
{
|
|
|
|
return @{
|
|
|
|
@"target": reactTag,
|
|
|
|
@"layout": @{
|
|
|
|
@"x": @(frame.origin.x),
|
|
|
|
@"y": @(frame.origin.y),
|
|
|
|
@"width": @(frame.size.width),
|
|
|
|
@"height": @(frame.size.height),
|
|
|
|
},
|
|
|
|
};
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2015-04-11 22:08:00 +00:00
|
|
|
|
2015-03-01 23:33:55 +00:00
|
|
|
// TODO: remove viewName param as it isn't needed
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(updateView:(NSNumber *)reactTag
|
|
|
|
viewName:(__unused NSString *)_
|
|
|
|
props:(NSDictionary *)props)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
RCTViewManager *viewManager = _viewManagerRegistry[reactTag];
|
2015-04-08 12:42:43 +00:00
|
|
|
NSString *viewName = RCTViewNameForModuleName(RCTBridgeModuleNameForClass([viewManager class]));
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], viewManager);
|
|
|
|
|
2015-07-05 07:41:58 +00:00
|
|
|
const BOOL wasLayoutOnly = shadowView.layoutOnly;
|
|
|
|
NSDictionary *newProps = RCTPropsMerge(shadowView.allProps, props);
|
|
|
|
shadowView.allProps = newProps;
|
|
|
|
|
|
|
|
const BOOL isLayoutOnly = shadowView.layoutOnly;
|
|
|
|
|
|
|
|
if (wasLayoutOnly != isLayoutOnly) {
|
|
|
|
// Add/remove node
|
|
|
|
|
|
|
|
if (isLayoutOnly) {
|
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
|
|
|
RCTAssertMainThread();
|
|
|
|
|
|
|
|
UIView *container = viewRegistry[reactTag];
|
|
|
|
|
|
|
|
const CGRect containerFrame = container.frame;
|
|
|
|
const CGFloat deltaX = containerFrame.origin.x;
|
|
|
|
const CGFloat deltaY = containerFrame.origin.y;
|
|
|
|
|
|
|
|
NSUInteger offset = [container.superview.subviews indexOfObject:container];
|
|
|
|
[container.subviews enumerateObjectsUsingBlock:^(UIView *subview, NSUInteger idx, __unused BOOL *stop) {
|
|
|
|
[container removeReactSubview:subview];
|
|
|
|
|
|
|
|
CGRect subviewFrame = subview.frame;
|
|
|
|
subviewFrame.origin.x += deltaX;
|
|
|
|
subviewFrame.origin.y += deltaY;
|
|
|
|
subview.frame = subviewFrame;
|
|
|
|
|
|
|
|
[container.superview insertReactSubview:subview atIndex:idx + offset];
|
|
|
|
}];
|
|
|
|
|
|
|
|
[container.superview removeReactSubview:container];
|
|
|
|
if ([container conformsToProtocol:@protocol(RCTInvalidating)]) {
|
|
|
|
[(id<RCTInvalidating>)container invalidate];
|
|
|
|
}
|
|
|
|
|
|
|
|
viewRegistry[reactTag] = nil;
|
|
|
|
}];
|
|
|
|
} else {
|
|
|
|
NSMutableArray *mutableAddChildReactTags = [[[shadowView reactSubviews] valueForKey:@"reactTag"] mutableCopy];
|
|
|
|
NSMutableArray *mutableAddAtIndices = [NSMutableArray arrayWithCapacity:mutableAddChildReactTags.count];
|
|
|
|
for (NSUInteger i = 0, count = mutableAddChildReactTags.count; i < count; i++) {
|
|
|
|
[mutableAddAtIndices addObject:@(i)];
|
|
|
|
}
|
|
|
|
|
|
|
|
[self modifyManageChildren:reactTag
|
|
|
|
addChildReactTags:mutableAddChildReactTags
|
|
|
|
addAtIndices:mutableAddAtIndices
|
|
|
|
removeAtIndices:nil];
|
|
|
|
|
|
|
|
NSUInteger offset = [shadowView.superview.reactSubviews indexOfObject:shadowView];
|
|
|
|
NSNumber *containerSuperviewReactTag = [self containerReactTag:shadowView.superview.reactTag offset:&offset];
|
|
|
|
UIColor *backgroundColor = shadowView.backgroundColor;
|
|
|
|
|
|
|
|
CGRect shadowViewFrame = shadowView.adjustedFrame;
|
|
|
|
NSMutableDictionary *newFrames = [NSMutableDictionary dictionaryWithCapacity:mutableAddChildReactTags.count];
|
|
|
|
for (NSNumber *childTag in mutableAddChildReactTags) {
|
|
|
|
RCTShadowView *child = _shadowViewRegistry[childTag];
|
|
|
|
newFrames[childTag] = [NSValue valueWithCGRect:child.adjustedFrame];
|
|
|
|
}
|
|
|
|
|
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
|
|
|
RCTAssertMainThread();
|
|
|
|
|
|
|
|
UIView *containerSuperview = viewRegistry[containerSuperviewReactTag];
|
|
|
|
UIView *container = [uiManager createView:reactTag viewName:viewName props:newProps withManager:viewManager backgroundColor:backgroundColor];
|
|
|
|
|
|
|
|
[containerSuperview insertReactSubview:container atIndex:offset];
|
|
|
|
if (RCTRectIsDefined(shadowViewFrame)) {
|
|
|
|
container.frame = shadowViewFrame;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (NSUInteger i = 0, count = mutableAddAtIndices.count; i < count; i++) {
|
|
|
|
NSNumber *tag = mutableAddChildReactTags[i];
|
|
|
|
UIView *subview = viewRegistry[tag];
|
|
|
|
[containerSuperview removeReactSubview:subview];
|
|
|
|
|
|
|
|
NSUInteger atIndex = [mutableAddAtIndices[i] unsignedIntegerValue];
|
|
|
|
[container insertReactSubview:subview atIndex:atIndex];
|
|
|
|
|
|
|
|
CGRect subviewFrame = [newFrames[tag] CGRectValue];
|
|
|
|
if (RCTRectIsDefined(subviewFrame)) {
|
|
|
|
subview.frame = subviewFrame;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
} else if (!isLayoutOnly) {
|
|
|
|
// Update node
|
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
|
|
|
UIView *view = viewRegistry[reactTag];
|
|
|
|
RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], viewManager);
|
|
|
|
}];
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(focus:(NSNumber *)reactTag)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
if (!reactTag) return;
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
2015-02-20 04:10:52 +00:00
|
|
|
UIView *newResponder = viewRegistry[reactTag];
|
2015-03-25 00:37:03 +00:00
|
|
|
[newResponder reactWillMakeFirstResponder];
|
2015-02-20 04:10:52 +00:00
|
|
|
[newResponder becomeFirstResponder];
|
2015-03-25 00:37:03 +00:00
|
|
|
[newResponder reactDidMakeFirstResponder];
|
2015-02-20 04:10:52 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(blur:(NSNumber *)reactTag)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
if (!reactTag) return;
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
2015-02-20 04:10:52 +00:00
|
|
|
UIView *currentResponder = viewRegistry[reactTag];
|
|
|
|
[currentResponder resignFirstResponder];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-05-26 18:16:25 +00:00
|
|
|
RCT_EXPORT_METHOD(findSubviewIn:(NSNumber *)reactTag atPoint:(CGPoint)point callback:(RCTResponseSenderBlock)callback) {
|
|
|
|
if (!reactTag) {
|
2015-06-12 18:05:01 +00:00
|
|
|
callback(@[(id)kCFNull]);
|
2015-05-26 18:16:25 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
2015-05-26 18:16:25 +00:00
|
|
|
UIView *view = viewRegistry[reactTag];
|
|
|
|
UIView *target = [view hitTest:point withEvent:nil];
|
|
|
|
CGRect frame = [target convertRect:target.bounds toView:view];
|
|
|
|
|
|
|
|
while (target.reactTag == nil && target.superview != nil) {
|
|
|
|
target = [target superview];
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(@[
|
2015-06-12 18:05:01 +00:00
|
|
|
RCTNullIfNil(target.reactTag),
|
2015-05-26 18:16:25 +00:00
|
|
|
@(frame.origin.x),
|
|
|
|
@(frame.origin.y),
|
|
|
|
@(frame.size.width),
|
|
|
|
@(frame.size.height),
|
|
|
|
]);
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
- (void)batchDidComplete
|
|
|
|
{
|
2015-06-02 13:15:53 +00:00
|
|
|
RCTProfileBeginEvent();
|
2015-05-29 17:27:14 +00:00
|
|
|
// 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];
|
|
|
|
[self addUIBlock:uiBlock];
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Set up next layout animation
|
|
|
|
if (_nextLayoutAnimation) {
|
|
|
|
RCTLayoutAnimation *layoutAnimation = _nextLayoutAnimation;
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, __unused RCTSparseArray *viewRegistry) {
|
2015-02-20 04:10:52 +00:00
|
|
|
uiManager->_layoutAnimation = layoutAnimation;
|
|
|
|
}];
|
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Perform layout
|
|
|
|
for (NSNumber *reactTag in _rootViewTags) {
|
|
|
|
RCTShadowView *rootView = _shadowViewRegistry[reactTag];
|
|
|
|
[self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
|
|
|
|
[self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootView];
|
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Clear layout animations
|
|
|
|
if (_nextLayoutAnimation) {
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, __unused RCTSparseArray *viewRegistry) {
|
2015-02-20 04:10:52 +00:00
|
|
|
uiManager->_layoutAnimation = nil;
|
|
|
|
}];
|
|
|
|
_nextLayoutAnimation = nil;
|
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-06-02 13:15:53 +00:00
|
|
|
RCTProfileEndEvent(@"[RCTUIManager batchDidComplete]", @"uimanager", @{
|
|
|
|
@"view_count": @([_viewRegistry count]),
|
|
|
|
});
|
2015-03-25 00:37:03 +00:00
|
|
|
[self flushUIBlocks];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)flushUIBlocks
|
|
|
|
{
|
2015-03-01 23:33:55 +00:00
|
|
|
// 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.
|
2015-02-20 04:10:52 +00:00
|
|
|
[_pendingUIBlocksLock lock];
|
|
|
|
NSArray *previousPendingUIBlocks = _pendingUIBlocks;
|
|
|
|
_pendingUIBlocks = [[NSMutableArray alloc] init];
|
|
|
|
[_pendingUIBlocksLock unlock];
|
2015-03-01 23:33:55 +00:00
|
|
|
|
|
|
|
// Execute the previously queued UI blocks
|
2015-06-17 21:03:50 +00:00
|
|
|
RCTProfileBeginFlowEvent();
|
2015-02-20 04:10:52 +00:00
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
2015-06-17 21:03:50 +00:00
|
|
|
RCTProfileEndFlowEvent();
|
2015-04-20 11:55:05 +00:00
|
|
|
RCTProfileBeginEvent();
|
2015-02-20 04:10:52 +00:00
|
|
|
for (dispatch_block_t block in previousPendingUIBlocks) {
|
|
|
|
block();
|
|
|
|
}
|
2015-04-20 11:55:05 +00:00
|
|
|
RCTProfileEndEvent(@"UIManager flushUIBlocks", @"objc_call", @{
|
|
|
|
@"count": @(previousPendingUIBlocks.count),
|
|
|
|
});
|
2015-02-20 04:10:52 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(measure:(NSNumber *)reactTag
|
|
|
|
callback:(RCTResponseSenderBlock)callback)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
if (!callback) {
|
|
|
|
RCTLogError(@"Called measure with no callback");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
2015-02-20 04:10:52 +00:00
|
|
|
UIView *view = viewRegistry[reactTag];
|
|
|
|
if (!view) {
|
2015-07-14 19:03:17 +00:00
|
|
|
// this view was probably collapsed out
|
|
|
|
RCTLogWarn(@"measure cannot find view with tag #%@", reactTag);
|
|
|
|
callback(@[]);
|
2015-02-20 04:10:52 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
CGRect frame = view.frame;
|
|
|
|
|
|
|
|
UIView *rootView = view;
|
|
|
|
while (rootView && ![rootView isReactRootView]) {
|
|
|
|
rootView = rootView.superview;
|
|
|
|
}
|
|
|
|
|
2015-03-25 00:37:03 +00:00
|
|
|
// TODO: this doesn't work because sometimes view is inside a modal window
|
2015-04-27 20:55:01 +00:00
|
|
|
// RCTAssert([rootView isReactRootView], @"React view is not inside a React root view");
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// 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];
|
|
|
|
|
|
|
|
callback(@[
|
|
|
|
@(frame.origin.x),
|
|
|
|
@(frame.origin.y),
|
|
|
|
@(frame.size.width),
|
|
|
|
@(frame.size.height),
|
|
|
|
@(pagePoint.x),
|
|
|
|
@(pagePoint.y)
|
|
|
|
]);
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-03-25 00:37:03 +00:00
|
|
|
static void RCTMeasureLayout(RCTShadowView *view,
|
|
|
|
RCTShadowView *ancestor,
|
|
|
|
RCTResponseSenderBlock callback)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
if (!view) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!ancestor) {
|
|
|
|
return;
|
|
|
|
}
|
2015-03-25 00:37:03 +00:00
|
|
|
CGRect result = [view measureLayoutRelativeToAncestor:ancestor];
|
2015-02-20 04:10:52 +00:00
|
|
|
if (CGRectIsNull(result)) {
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTLogError(@"view %@ (tag #%@) is not a decendant of %@ (tag #%@)",
|
|
|
|
view, view.reactTag, ancestor, ancestor.reactTag);
|
2015-02-20 04:10:52 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
CGFloat leftOffset = result.origin.x;
|
|
|
|
CGFloat topOffset = result.origin.y;
|
|
|
|
CGFloat width = result.size.width;
|
|
|
|
CGFloat height = result.size.height;
|
|
|
|
if (isnan(leftOffset) || isnan(topOffset) || isnan(width) || isnan(height)) {
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTLogError(@"Attempted to measure layout but offset or dimensions were NaN");
|
2015-02-20 04:10:52 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-03-25 00:37:03 +00:00
|
|
|
callback(@[@(leftOffset), @(topOffset), @(width), @(height)]);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the computed recursive offset layout in a dictionary form. The
|
|
|
|
* returned values are relative to the `ancestor` shadow view. Returns `nil`, if
|
|
|
|
* the `ancestor` shadow view is not actually an `ancestor`. Does not touch
|
|
|
|
* anything on the main UI thread. Invokes supplied callback with (x, y, width,
|
|
|
|
* height).
|
|
|
|
*/
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(measureLayout:(NSNumber *)reactTag
|
|
|
|
relativeTo:(NSNumber *)ancestorReactTag
|
2015-06-15 14:53:45 +00:00
|
|
|
errorCallback:(__unused RCTResponseSenderBlock)errorCallback
|
2015-04-08 15:52:48 +00:00
|
|
|
callback:(RCTResponseSenderBlock)callback)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
|
|
|
RCTShadowView *ancestorShadowView = _shadowViewRegistry[ancestorReactTag];
|
2015-03-25 00:37:03 +00:00
|
|
|
RCTMeasureLayout(shadowView, ancestorShadowView, callback);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the computed recursive offset layout in a dictionary form. The
|
|
|
|
* returned values are relative to the `ancestor` shadow view. Returns `nil`, if
|
|
|
|
* the `ancestor` shadow view is not actually an `ancestor`. Does not touch
|
|
|
|
* anything on the main UI thread. Invokes supplied callback with (x, y, width,
|
|
|
|
* height).
|
|
|
|
*/
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(measureLayoutRelativeToParent:(NSNumber *)reactTag
|
2015-06-15 14:53:45 +00:00
|
|
|
errorCallback:(__unused RCTResponseSenderBlock)errorCallback
|
2015-04-08 15:52:48 +00:00
|
|
|
callback:(RCTResponseSenderBlock)callback)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
2015-03-25 00:37:03 +00:00
|
|
|
RCTMeasureLayout(shadowView, shadowView.reactSuperview, callback);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-04-27 20:55:01 +00:00
|
|
|
* Returns an array of computed offset layouts in a dictionary form. The layouts are of any React subviews
|
2015-02-20 04:10:52 +00:00
|
|
|
* that are immediate descendants to the parent view found within a specified rect. The dictionary result
|
|
|
|
* contains left, top, width, height and an index. The index specifies the position among the other subviews.
|
|
|
|
* 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.
|
|
|
|
*/
|
2015-04-27 10:58:30 +00:00
|
|
|
RCT_EXPORT_METHOD(measureViewsInRect:(CGRect)rect
|
2015-04-08 15:52:48 +00:00
|
|
|
parentView:(NSNumber *)reactTag
|
2015-06-15 14:53:45 +00:00
|
|
|
errorCallback:(__unused RCTResponseSenderBlock)errorCallback
|
2015-04-08 15:52:48 +00:00
|
|
|
callback:(RCTResponseSenderBlock)callback)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
|
|
|
if (!shadowView) {
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTLogError(@"Attempting to measure view that does not exist (tag #%@)", reactTag);
|
2015-02-20 04:10:52 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
NSArray *childShadowViews = [shadowView reactSubviews];
|
|
|
|
NSMutableArray *results = [[NSMutableArray alloc] initWithCapacity:[childShadowViews count]];
|
2015-04-19 19:55:46 +00:00
|
|
|
|
2015-03-23 20:28:42 +00:00
|
|
|
|
2015-06-15 14:53:45 +00:00
|
|
|
[childShadowViews enumerateObjectsUsingBlock:
|
|
|
|
^(RCTShadowView *childShadowView, NSUInteger idx, __unused BOOL *stop) {
|
2015-03-25 00:37:03 +00:00
|
|
|
CGRect childLayout = [childShadowView measureLayoutRelativeToAncestor:shadowView];
|
2015-02-20 04:10:52 +00:00
|
|
|
if (CGRectIsNull(childLayout)) {
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTLogError(@"View %@ (tag #%@) is not a decendant of %@ (tag #%@)",
|
|
|
|
childShadowView, childShadowView.reactTag, shadowView, shadowView.reactTag);
|
2015-02-20 04:10:52 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CGFloat leftOffset = childLayout.origin.x;
|
|
|
|
CGFloat topOffset = childLayout.origin.y;
|
|
|
|
CGFloat width = childLayout.size.width;
|
|
|
|
CGFloat height = childLayout.size.height;
|
|
|
|
|
2015-04-19 19:55:46 +00:00
|
|
|
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) {
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// This view is within the layout rect
|
2015-03-17 10:51:58 +00:00
|
|
|
NSDictionary *result = @{@"index": @(idx),
|
2015-02-20 04:10:52 +00:00
|
|
|
@"left": @(leftOffset),
|
|
|
|
@"top": @(topOffset),
|
|
|
|
@"width": @(width),
|
|
|
|
@"height": @(height)};
|
|
|
|
|
|
|
|
[results addObject:result];
|
|
|
|
}
|
2015-03-17 10:51:58 +00:00
|
|
|
}];
|
2015-02-20 04:10:52 +00:00
|
|
|
callback(@[results]);
|
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(setMainScrollViewTag:(NSNumber *)reactTag)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
|
|
|
// - There should be at most one designated "main scroll view"
|
|
|
|
// - There should be at most one designated "`nativeMainScrollDelegate`"
|
|
|
|
// - The one designated main scroll view should have the one designated
|
|
|
|
// `nativeMainScrollDelegate` set as its `nativeMainScrollDelegate`.
|
|
|
|
if (uiManager.mainScrollView) {
|
|
|
|
uiManager.mainScrollView.nativeMainScrollDelegate = nil;
|
|
|
|
}
|
|
|
|
if (reactTag) {
|
2015-04-11 22:08:00 +00:00
|
|
|
id view = viewRegistry[reactTag];
|
|
|
|
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
|
|
|
uiManager.mainScrollView = (id<RCTScrollableProtocol>)view;
|
|
|
|
uiManager.mainScrollView.nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate;
|
2015-02-20 04:10:52 +00:00
|
|
|
} else {
|
2015-04-11 22:08:00 +00:00
|
|
|
RCTAssert(NO, @"Tag #%@ does not conform to RCTScrollableProtocol", reactTag);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
uiManager.mainScrollView = nil;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-04-11 22:08:00 +00:00
|
|
|
// TODO: we could just pass point property
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(scrollTo:(NSNumber *)reactTag
|
2015-04-11 22:08:00 +00:00
|
|
|
withOffsetX:(CGFloat)offsetX
|
|
|
|
offsetY:(CGFloat)offsetY)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
2015-02-20 04:10:52 +00:00
|
|
|
UIView *view = viewRegistry[reactTag];
|
|
|
|
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
2015-04-11 22:08:00 +00:00
|
|
|
[(id<RCTScrollableProtocol>)view scrollToOffset:(CGPoint){offsetX, offsetY} animated:YES];
|
2015-02-20 04:10:52 +00:00
|
|
|
} else {
|
2015-04-07 14:36:26 +00:00
|
|
|
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-04-11 22:08:00 +00:00
|
|
|
// TODO: we could just pass point property
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(NSNumber *)reactTag
|
2015-04-11 22:08:00 +00:00
|
|
|
offsetX:(CGFloat)offsetX
|
|
|
|
offsetY:(CGFloat)offsetY)
|
2015-03-31 23:04:48 +00:00
|
|
|
{
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
2015-03-31 23:04:48 +00:00
|
|
|
UIView *view = viewRegistry[reactTag];
|
|
|
|
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
2015-04-11 22:08:00 +00:00
|
|
|
[(id<RCTScrollableProtocol>)view scrollToOffset:(CGPoint){offsetX, offsetY} animated:NO];
|
2015-03-31 23:04:48 +00:00
|
|
|
} else {
|
2015-04-07 14:36:26 +00:00
|
|
|
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
|
2015-03-31 23:04:48 +00:00
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag
|
2015-04-27 10:58:30 +00:00
|
|
|
withRect:(CGRect)rect)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
2015-02-20 04:10:52 +00:00
|
|
|
UIView *view = viewRegistry[reactTag];
|
|
|
|
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
2015-04-11 22:08:00 +00:00
|
|
|
[(id<RCTScrollableProtocol>)view zoomToRect:rect animated:YES];
|
2015-02-20 04:10:52 +00:00
|
|
|
} else {
|
2015-04-07 14:36:26 +00:00
|
|
|
RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* JS sets what *it* considers to be the responder. Later, scroll views can use
|
|
|
|
* this in order to determine if scrolling is appropriate.
|
|
|
|
*/
|
2015-06-19 22:58:12 +00:00
|
|
|
RCT_EXPORT_METHOD(setJSResponder:(NSNumber *)reactTag
|
[ReactNative] Move module info from bridge to RCTModuleData
Summary:
@public
The info about bridge modules (such as id, name, queue, methods...) was spread
across arrays & dictionaries on the bridge, move it into a specific class.
It also removes a lot of information that was statically cached, and now have
the same lifecycle of the bridge.
Also moved RCTModuleMethod, RCTFrameUpdate and RCTBatchedBridge into it's own
files, for organization sake.
NOTE: This diff seems huge, but most of it was just moving code :)
Test Plan:
Tested UIExplorer & UIExplorer tests, Catalyst, MAdMan and Groups. Everything
looks fine.
2015-06-24 23:34:56 +00:00
|
|
|
blockNativeResponder:(__unused BOOL)blockNativeResponder)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-07-05 07:41:58 +00:00
|
|
|
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
|
|
|
if (!shadowView) {
|
|
|
|
RCTLogError(@"Invalid view set to be the JS responder - tag %@", reactTag);
|
|
|
|
} else if (shadowView.layoutOnly) {
|
|
|
|
RCTLogError(@"Cannot set JS responder to layout-only view with tag %@ - %@, %@", reactTag, shadowView, shadowView.allProps);
|
|
|
|
} else {
|
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
|
|
|
_jsResponder = viewRegistry[reactTag];
|
|
|
|
}];
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(clearJSResponder)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-06-15 14:53:45 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, __unused RCTSparseArray *viewRegistry) {
|
2015-02-20 04:10:52 +00:00
|
|
|
_jsResponder = nil;
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-03-01 23:33:55 +00:00
|
|
|
// 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
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
NSMutableDictionary *customBubblingEventTypesConfigs = [@{
|
|
|
|
// Bubble dispatched events
|
|
|
|
@"topTap": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
2015-03-06 00:36:41 +00:00
|
|
|
@"bubbled": @"onPress",
|
|
|
|
@"captured": @"onPressCapture"
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topVisibleCellsChange": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onVisibleCellsChange",
|
|
|
|
@"captured": @"onVisibleCellsChangeCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topNavigateBack": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onNavigationComplete",
|
|
|
|
@"captured": @"onNavigationCompleteCapture"
|
|
|
|
}
|
|
|
|
},
|
2015-05-07 14:16:59 +00:00
|
|
|
@"topNavLeftButtonTap": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onNavLeftButtonTap",
|
|
|
|
@"captured": @"onNavLefttButtonTapCapture"
|
|
|
|
}
|
|
|
|
},
|
2015-02-20 04:10:52 +00:00
|
|
|
@"topNavRightButtonTap": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onNavRightButtonTap",
|
|
|
|
@"captured": @"onNavRightButtonTapCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topChange": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onChange",
|
|
|
|
@"captured": @"onChangeCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topFocus": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onFocus",
|
|
|
|
@"captured": @"onFocusCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topBlur": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onBlur",
|
|
|
|
@"captured": @"onBlurCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topSubmitEditing": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onSubmitEditing",
|
|
|
|
@"captured": @"onSubmitEditingCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topEndEditing": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onEndEditing",
|
|
|
|
@"captured": @"onEndEditingCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topTextInput": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onTextInput",
|
|
|
|
@"captured": @"onTextInputCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topTouchStart": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onTouchStart",
|
|
|
|
@"captured": @"onTouchStartCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topTouchMove": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onTouchMove",
|
|
|
|
@"captured": @"onTouchMoveCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topTouchCancel": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onTouchCancel",
|
|
|
|
@"captured": @"onTouchCancelCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
@"topTouchEnd": @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": @"onTouchEnd",
|
|
|
|
@"captured": @"onTouchEndCapture"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
} mutableCopy];
|
|
|
|
|
2015-06-19 03:15:59 +00:00
|
|
|
for (RCTViewManager *manager in _viewManagers.allValues) {
|
2015-03-25 00:37:03 +00:00
|
|
|
if (RCTClassOverridesInstanceMethod([manager class], @selector(customBubblingEventTypes))) {
|
|
|
|
NSDictionary *eventTypes = [manager customBubblingEventTypes];
|
2015-02-20 04:10:52 +00:00
|
|
|
for (NSString *eventName in eventTypes) {
|
2015-04-11 22:08:00 +00:00
|
|
|
RCTAssert(!customBubblingEventTypesConfigs[eventName],
|
|
|
|
@"Event '%@' registered multiple times.", eventName);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
[customBubblingEventTypesConfigs addEntriesFromDictionary:eventTypes];
|
|
|
|
}
|
2015-06-15 14:53:45 +00:00
|
|
|
};
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
return customBubblingEventTypesConfigs;
|
|
|
|
}
|
|
|
|
|
2015-03-01 23:33:55 +00:00
|
|
|
- (NSDictionary *)customDirectEventTypes
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
NSMutableDictionary *customDirectEventTypes = [@{
|
|
|
|
@"topScrollBeginDrag": @{
|
|
|
|
@"registrationName": @"onScrollBeginDrag"
|
|
|
|
},
|
|
|
|
@"topScroll": @{
|
|
|
|
@"registrationName": @"onScroll"
|
|
|
|
},
|
|
|
|
@"topScrollEndDrag": @{
|
|
|
|
@"registrationName": @"onScrollEndDrag"
|
|
|
|
},
|
|
|
|
@"topScrollAnimationEnd": @{
|
|
|
|
@"registrationName": @"onScrollAnimationEnd"
|
|
|
|
},
|
[ReactNative] Introduce onLayout events
Summary:
Simply add an `onLayout` callback to a native view component, and the callback
will be invoked with the current layout information when the view is mounted and
whenever the layout changes.
The only limitation is that scroll position and other stuff the layout system
isn't aware of is not taken into account. This is because onLayout events
wouldn't be triggered for these changes and if they are desired they should be
tracked separately (e.g. with `onScroll`) and combined.
Also fixes some bugs with LayoutAnimation callbacks.
@public
Test Plan:
- Run new LayoutEventsExample in UIExplorer and see it work correctly.
- New integration test passes internally (IntegrationTest project seems busted).
- New jest test case passes.
{F22318433}
```
2015-05-06 15:45:05.848 [info][tid:com.facebook.React.JavaScript] "Running application "UIExplorerApp" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF"
2015-05-06 15:45:05.881 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":123,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.882 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":122,"width":50,"height":50}}
2015-05-06 15:45:05.883 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":204}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":140.5,"height":18}}
2015-05-06 15:45:05.897 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":70.5,"x":20,"width":294,"height":287.5}}
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "layout animation done."
2015-05-06 15:45:09.847 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":82,"width":50,"height":50}}
2015-05-06 15:45:09.848 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":110.5,"x":60,"width":214,"height":287.5}}
2015-05-06 15:45:09.862 [info][tid:com.facebook.React.JavaScript] "received text layout event
", {"target":27,"layout":{"y":206.5,"x":12.5,"width":120,"height":68}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received image layout event
", {"target":23,"layout":{"y":12.5,"x":55,"width":50,"height":50}}
2015-05-06 15:45:09.863 [info][tid:com.facebook.React.JavaScript] "received view layout event
", {"target":22,"layout":{"y":128,"x":60,"width":160,"height":337.5}}
```
2015-05-07 19:11:02 +00:00
|
|
|
@"topLayout": @{
|
|
|
|
@"registrationName": @"onLayout"
|
|
|
|
},
|
2015-02-20 04:10:52 +00:00
|
|
|
@"topSelectionChange": @{
|
|
|
|
@"registrationName": @"onSelectionChange"
|
|
|
|
},
|
|
|
|
@"topMomentumScrollBegin": @{
|
|
|
|
@"registrationName": @"onMomentumScrollBegin"
|
|
|
|
},
|
|
|
|
@"topMomentumScrollEnd": @{
|
|
|
|
@"registrationName": @"onMomentumScrollEnd"
|
|
|
|
},
|
|
|
|
@"topPullToRefresh": @{
|
|
|
|
@"registrationName": @"onPullToRefresh"
|
|
|
|
},
|
|
|
|
@"topLoadingStart": @{
|
|
|
|
@"registrationName": @"onLoadingStart"
|
|
|
|
},
|
|
|
|
@"topLoadingFinish": @{
|
|
|
|
@"registrationName": @"onLoadingFinish"
|
|
|
|
},
|
|
|
|
@"topLoadingError": @{
|
|
|
|
@"registrationName": @"onLoadingError"
|
|
|
|
},
|
2015-05-20 15:33:16 +00:00
|
|
|
@"topAccessibilityTap": @{
|
|
|
|
@"registrationName": @"onAccessibilityTap"
|
|
|
|
},
|
2015-05-19 13:21:52 +00:00
|
|
|
@"topMagicTap": @{
|
|
|
|
@"registrationName": @"onMagicTap"
|
|
|
|
},
|
2015-02-20 04:10:52 +00:00
|
|
|
} mutableCopy];
|
|
|
|
|
2015-06-19 03:15:59 +00:00
|
|
|
for (RCTViewManager *manager in _viewManagers.allValues) {
|
2015-03-25 00:37:03 +00:00
|
|
|
if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) {
|
|
|
|
NSDictionary *eventTypes = [manager customDirectEventTypes];
|
2015-02-20 04:10:52 +00:00
|
|
|
for (NSString *eventName in eventTypes) {
|
2015-04-11 22:08:00 +00:00
|
|
|
RCTAssert(!customDirectEventTypes[eventName], @"Event '%@' registered multiple times.", eventName);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
[customDirectEventTypes addEntriesFromDictionary:eventTypes];
|
|
|
|
}
|
2015-06-15 14:53:45 +00:00
|
|
|
};
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
return customDirectEventTypes;
|
|
|
|
}
|
|
|
|
|
2015-03-01 23:33:55 +00:00
|
|
|
- (NSDictionary *)constantsToExport
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
NSMutableDictionary *allJSConstants = [@{
|
2015-03-01 23:33:55 +00:00
|
|
|
@"customBubblingEventTypes": [self customBubblingEventTypes],
|
|
|
|
@"customDirectEventTypes": [self customDirectEventTypes],
|
2015-02-20 04:10:52 +00:00
|
|
|
@"Dimensions": @{
|
|
|
|
@"window": @{
|
|
|
|
@"width": @(RCTScreenSize().width),
|
|
|
|
@"height": @(RCTScreenSize().height),
|
|
|
|
@"scale": @(RCTScreenScale()),
|
|
|
|
},
|
|
|
|
@"modalFullscreenView": @{
|
|
|
|
@"width": @(RCTScreenSize().width),
|
2015-03-25 00:37:03 +00:00
|
|
|
@"height": @(RCTScreenSize().height),
|
2015-02-20 04:10:52 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
} mutableCopy];
|
|
|
|
|
2015-06-15 14:53:45 +00:00
|
|
|
[_viewManagers enumerateKeysAndObjectsUsingBlock:
|
|
|
|
^(NSString *name, RCTViewManager *manager, __unused BOOL *stop) {
|
|
|
|
|
|
|
|
NSMutableDictionary *constantsNamespace =
|
|
|
|
[NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
|
2015-04-22 11:03:46 +00:00
|
|
|
|
|
|
|
// Add custom constants
|
2015-02-20 04:10:52 +00:00
|
|
|
// TODO: should these be inherited?
|
2015-03-25 00:37:03 +00:00
|
|
|
NSDictionary *constants = RCTClassOverridesInstanceMethod([manager class], @selector(constantsToExport)) ? [manager constantsToExport] : nil;
|
|
|
|
if (constants.count) {
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
|
2015-02-20 04:10:52 +00:00
|
|
|
// add an additional 'Constants' namespace for each class
|
2015-03-01 23:33:55 +00:00
|
|
|
constantsNamespace[@"Constants"] = constants;
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2015-04-22 11:03:46 +00:00
|
|
|
|
|
|
|
// Add native props
|
2015-05-27 02:25:11 +00:00
|
|
|
constantsNamespace[@"NativeProps"] = _viewConfigs[name];
|
2015-04-22 11:03:46 +00:00
|
|
|
|
|
|
|
allJSConstants[name] = [constantsNamespace copy];
|
2015-02-20 04:10:52 +00:00
|
|
|
}];
|
|
|
|
return allJSConstants;
|
|
|
|
}
|
|
|
|
|
2015-04-08 15:52:48 +00:00
|
|
|
RCT_EXPORT_METHOD(configureNextLayoutAnimation:(NSDictionary *)config
|
|
|
|
withCallback:(RCTResponseSenderBlock)callback
|
2015-06-15 14:53:45 +00:00
|
|
|
errorCallback:(__unused RCTResponseSenderBlock)errorCallback)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-07-07 21:14:14 +00:00
|
|
|
if (_nextLayoutAnimation && ![config isEqualToDictionary:_nextLayoutAnimation.config]) {
|
|
|
|
RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", _nextLayoutAnimation.config, config);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
if (config[@"delete"] != nil) {
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTLogError(@"LayoutAnimation only supports create and update right now. Config: %@", config);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2015-04-08 12:42:43 +00:00
|
|
|
_nextLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDictionary:config
|
|
|
|
callback:callback];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static UIView *_jsResponder;
|
|
|
|
|
|
|
|
+ (UIView *)JSResponder
|
|
|
|
{
|
|
|
|
return _jsResponder;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
2015-03-01 23:33:55 +00:00
|
|
|
|
|
|
|
@implementation RCTBridge (RCTUIManager)
|
|
|
|
|
|
|
|
- (RCTUIManager *)uiManager
|
|
|
|
{
|
2015-04-08 12:42:43 +00:00
|
|
|
return self.modules[RCTBridgeModuleNameForClass([RCTUIManager class])];
|
2015-03-01 23:33:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
2015-07-05 07:41:58 +00:00
|
|
|
|
|
|
|
static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, void (^block)(id<RCTViewNodeProtocol>))
|
|
|
|
{
|
|
|
|
if (view.reactTag) block(view);
|
|
|
|
for (id<RCTViewNodeProtocol> subview in view.reactSubviews) {
|
|
|
|
RCTTraverseViewNodes(subview, block);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static NSDictionary *RCTPropsMerge(NSDictionary *beforeProps, NSDictionary *newProps)
|
|
|
|
{
|
|
|
|
NSMutableDictionary *afterProps = [NSMutableDictionary dictionaryWithDictionary:beforeProps];
|
|
|
|
|
|
|
|
// Can't use -addEntriesFromDictionary: because we want to remove keys with NSNull values.
|
|
|
|
[newProps enumerateKeysAndObjectsUsingBlock:^(id key, id obj, __unused BOOL *stop) {
|
|
|
|
if (obj == (id)kCFNull) {
|
|
|
|
[afterProps removeObjectForKey:key];
|
|
|
|
} else {
|
|
|
|
afterProps[key] = obj;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
|
|
|
|
return afterProps;
|
|
|
|
}
|