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"
|
|
|
|
|
2015-03-10 21:23:03 +00:00
|
|
|
#import <AVFoundation/AVFoundation.h>
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "Layout.h"
|
2015-07-31 14:37:12 +00:00
|
|
|
#import "RCTAccessibilityManager.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "RCTAnimationType.h"
|
|
|
|
#import "RCTAssert.h"
|
|
|
|
#import "RCTBridge.h"
|
2015-08-06 22:44:15 +00:00
|
|
|
#import "RCTComponent.h"
|
|
|
|
#import "RCTComponentData.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#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-10-26 22:39:06 +00:00
|
|
|
#import "RCTRootViewInternal.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
#import "RCTScrollableProtocol.h"
|
|
|
|
#import "RCTShadowView.h"
|
|
|
|
#import "RCTUtils.h"
|
|
|
|
#import "RCTView.h"
|
|
|
|
#import "RCTViewManager.h"
|
2015-03-26 09:58:06 +00:00
|
|
|
#import "UIView+React.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-08-06 22:44:15 +00:00
|
|
|
static void RCTTraverseViewNodes(id<RCTComponent> view, void (^block)(id<RCTComponent>))
|
2015-07-17 10:53:15 +00:00
|
|
|
{
|
2015-11-11 16:12:13 +00:00
|
|
|
if (view.reactTag) {
|
|
|
|
block(view);
|
|
|
|
|
|
|
|
for (id<RCTComponent> subview in view.reactSubviews) {
|
|
|
|
RCTTraverseViewNodes(subview, block);
|
|
|
|
}
|
2015-07-17 10:53:15 +00:00
|
|
|
}
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-07-31 14:37:12 +00:00
|
|
|
NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification = @"RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification";
|
2015-08-17 13:11:29 +00:00
|
|
|
NSString *const RCTUIManagerDidRegisterRootViewNotification = @"RCTUIManagerDidRegisterRootViewNotification";
|
|
|
|
NSString *const RCTUIManagerDidRemoveRootViewNotification = @"RCTUIManagerDidRemoveRootViewNotification";
|
|
|
|
NSString *const RCTUIManagerRootViewKey = @"RCTUIManagerRootViewKey";
|
2015-07-31 14:37:12 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
|
@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
|
2015-11-03 22:45:46 +00:00
|
|
|
NSMutableSet<NSNumber *> *_rootViewTags;
|
|
|
|
NSMutableArray<dispatch_block_t> *_pendingUIBlocks;
|
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-11-25 11:09:00 +00:00
|
|
|
NSMutableDictionary<NSNumber *, RCTShadowView *> *_shadowViewRegistry; // RCT thread only
|
|
|
|
NSMutableDictionary<NSNumber *, UIView *> *_viewRegistry; // Main thread only
|
|
|
|
|
2015-03-01 23:33:55 +00:00
|
|
|
// Keyed by viewName
|
2015-08-06 22:44:15 +00:00
|
|
|
NSDictionary *_componentDataByName;
|
2015-06-01 10:01:55 +00:00
|
|
|
|
2015-11-03 22:45:46 +00:00
|
|
|
NSMutableSet<id<RCTComponent>> *_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-07-31 14:37:12 +00:00
|
|
|
- (void)didReceiveNewContentSizeMultiplier
|
|
|
|
{
|
|
|
|
__weak RCTUIManager *weakSelf = self;
|
|
|
|
dispatch_async(self.methodQueue, ^{
|
2015-08-11 11:15:59 +00:00
|
|
|
RCTUIManager *strongSelf = weakSelf;
|
2015-07-31 14:37:12 +00:00
|
|
|
if (strongSelf) {
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification
|
|
|
|
object:strongSelf];
|
|
|
|
[strongSelf batchDidComplete];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
- (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-11-03 11:54:23 +00:00
|
|
|
// This only accessed from the shadow queue
|
|
|
|
_pendingUIBlocks = nil;
|
|
|
|
|
2015-05-04 17:35:49 +00:00
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
for (NSNumber *rootViewTag in _rootViewTags) {
|
2015-11-14 18:25:00 +00:00
|
|
|
[(id<RCTInvalidating>)_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-10-08 10:47:43 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
2015-05-04 17:35:49 +00:00
|
|
|
});
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 11:09:00 +00:00
|
|
|
- (NSMutableDictionary<NSNumber *, RCTShadowView *> *)shadowViewRegistry
|
|
|
|
{
|
|
|
|
// NOTE: this method only exists so that it can be accessed by unit tests
|
|
|
|
if (!_shadowViewRegistry) {
|
|
|
|
_shadowViewRegistry = [NSMutableDictionary new];
|
|
|
|
}
|
|
|
|
return _shadowViewRegistry;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry
|
|
|
|
{
|
|
|
|
// NOTE: this method only exists so that it can be accessed by unit tests
|
|
|
|
if (!_viewRegistry) {
|
|
|
|
_viewRegistry = [NSMutableDictionary new];
|
|
|
|
}
|
|
|
|
return _viewRegistry;
|
|
|
|
}
|
|
|
|
|
2015-04-11 22:08:00 +00:00
|
|
|
- (void)setBridge:(RCTBridge *)bridge
|
|
|
|
{
|
|
|
|
RCTAssert(_bridge == nil, @"Should not re-use same UIIManager instance");
|
|
|
|
|
|
|
|
_bridge = bridge;
|
2015-11-25 11:09:00 +00:00
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
_shadowViewRegistry = [NSMutableDictionary new];
|
2015-11-25 11:09:00 +00:00
|
|
|
_viewRegistry = [NSMutableDictionary new];
|
|
|
|
|
|
|
|
// Internal resources
|
|
|
|
_pendingUIBlocks = [NSMutableArray new];
|
|
|
|
_rootViewTags = [NSMutableSet new];
|
|
|
|
|
|
|
|
_bridgeTransactionListeners = [NSMutableSet new];
|
2015-04-11 22:08:00 +00:00
|
|
|
|
|
|
|
// Get view managers from bridge
|
2015-08-17 14:35:34 +00:00
|
|
|
NSMutableDictionary *componentDataByName = [NSMutableDictionary new];
|
2015-11-25 11:09:00 +00:00
|
|
|
for (Class moduleClass in _bridge.moduleClasses) {
|
|
|
|
if ([moduleClass isSubclassOfClass:[RCTViewManager class]]) {
|
|
|
|
RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:moduleClass
|
|
|
|
bridge:_bridge];
|
2015-08-06 22:44:15 +00:00
|
|
|
componentDataByName[componentData.name] = componentData;
|
2015-04-11 22:08:00 +00:00
|
|
|
}
|
2015-08-06 22:44:15 +00:00
|
|
|
}
|
2015-04-11 22:08:00 +00:00
|
|
|
|
2015-08-06 22:44:15 +00:00
|
|
|
_componentDataByName = [componentDataByName copy];
|
2015-10-08 10:47:43 +00:00
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
|
|
selector:@selector(didReceiveNewContentSizeMultiplier)
|
|
|
|
name:RCTAccessibilityManagerDidUpdateMultiplierNotification
|
|
|
|
object:_bridge.accessibilityManager];
|
2015-04-11 22:08:00 +00:00
|
|
|
}
|
|
|
|
|
2015-04-18 17:43:20 +00:00
|
|
|
- (dispatch_queue_t)methodQueue
|
|
|
|
{
|
2015-11-25 11:09:00 +00:00
|
|
|
if (!_shadowQueue) {
|
|
|
|
const char *queueName = "com.facebook.React.ShadowQueue";
|
|
|
|
|
|
|
|
if ([NSOperation instancesRespondToSelector:@selector(qualityOfService)]) {
|
|
|
|
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0);
|
|
|
|
_shadowQueue = dispatch_queue_create(queueName, attr);
|
|
|
|
} else {
|
|
|
|
_shadowQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL);
|
|
|
|
dispatch_set_target_queue(_shadowQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
|
|
|
}
|
|
|
|
}
|
2015-04-18 17:43:20 +00:00
|
|
|
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;
|
2015-08-14 08:59:42 +00:00
|
|
|
if (!_viewRegistry) {
|
2015-06-06 21:12:37 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-08-17 14:35:34 +00:00
|
|
|
RCTShadowView *shadowView = [RCTShadowView new];
|
2015-02-20 04:10:52 +00:00
|
|
|
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-08-17 13:11:29 +00:00
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRegisterRootViewNotification
|
|
|
|
object:self
|
|
|
|
userInfo:@{ RCTUIManagerRootViewKey: rootView }];
|
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-07-28 14:31:26 +00:00
|
|
|
- (void)setFrame:(CGRect)frame forView:(UIView *)view
|
2015-03-25 00:37:03 +00:00
|
|
|
{
|
|
|
|
RCTAssertMainThread();
|
|
|
|
|
2015-10-27 16:20:48 +00:00
|
|
|
// The following variable has no meaning if the view is not a react root view
|
|
|
|
RCTRootViewSizeFlexibility sizeFlexibility = RCTRootViewSizeFlexibilityNone;
|
|
|
|
|
|
|
|
if (RCTIsReactRootView(view.reactTag)) {
|
|
|
|
RCTRootView *rootView = (RCTRootView *)[view superview];
|
|
|
|
if (rootView != nil) {
|
|
|
|
sizeFlexibility = rootView.sizeFlexibility;
|
|
|
|
}
|
|
|
|
}
|
2015-10-26 22:39:04 +00:00
|
|
|
|
2015-07-28 14:31:26 +00:00
|
|
|
NSNumber *reactTag = 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-10-26 22:39:04 +00:00
|
|
|
|
|
|
|
if (RCTIsReactRootView(reactTag)) {
|
|
|
|
rootShadowView.frame = frame;
|
|
|
|
rootShadowView.sizeFlexibility = sizeFlexibility;
|
|
|
|
} else {
|
|
|
|
rootShadowView.frame = frame;
|
|
|
|
}
|
|
|
|
|
2015-11-27 10:52:14 +00:00
|
|
|
[rootShadowView dirtyLayout];
|
2015-03-25 00:37:03 +00:00
|
|
|
|
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;
|
2015-08-14 08:59:42 +00:00
|
|
|
if (!_viewRegistry) {
|
2015-06-06 20:37:51 +00:00
|
|
|
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
|
|
|
|
*/
|
2015-11-03 22:45:46 +00:00
|
|
|
- (void)_purgeChildren:(NSArray<id<RCTComponent>> *)children
|
2015-11-14 18:25:00 +00:00
|
|
|
fromRegistry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-08-06 22:44:15 +00:00
|
|
|
for (id<RCTComponent> child in children) {
|
|
|
|
RCTTraverseViewNodes(registry[child.reactTag], ^(id<RCTComponent> subview) {
|
2015-02-20 04:10:52 +00:00
|
|
|
RCTAssert(![subview isReactRootView], @"Root views should not be unregistered");
|
|
|
|
if ([subview conformsToProtocol:@protocol(RCTInvalidating)]) {
|
|
|
|
[(id<RCTInvalidating>)subview invalidate];
|
|
|
|
}
|
2015-11-14 18:25:00 +00:00
|
|
|
[registry removeObjectForKey:subview.reactTag];
|
2015-06-01 10:01:55 +00:00
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
if (registry == (NSMutableDictionary<NSNumber *, id<RCTComponent>> *)_viewRegistry) {
|
2015-06-01 10:01:55 +00:00
|
|
|
[_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-08-14 08:59:42 +00:00
|
|
|
if (!_viewRegistry) {
|
2015-04-18 13:23:24 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
__weak RCTUIManager *weakViewManager = self;
|
|
|
|
dispatch_block_t outerBlock = ^{
|
|
|
|
RCTUIManager *strongViewManager = weakViewManager;
|
2015-08-14 08:59:42 +00:00
|
|
|
if (strongViewManager && strongViewManager->_viewRegistry) {
|
2015-04-18 13:23:24 +00:00
|
|
|
block(strongViewManager, strongViewManager->_viewRegistry);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
[_pendingUIBlocks addObject:outerBlock];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (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
|
|
|
// 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-11-17 14:35:46 +00:00
|
|
|
NSSet<RCTShadowView *> *viewsWithNewFrames = [rootShadowView collectRootUpdatedFrames];
|
|
|
|
|
|
|
|
if (!viewsWithNewFrames.count) {
|
|
|
|
// no frame change results in no UI update block
|
|
|
|
return nil;
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
[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-11-03 22:45:46 +00:00
|
|
|
NSMutableArray<NSNumber *> *frameReactTags =
|
|
|
|
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
|
|
|
NSMutableArray<NSValue *> *frames =
|
|
|
|
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
|
|
|
NSMutableArray<NSNumber *> *areNew =
|
|
|
|
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
|
|
|
NSMutableArray<NSNumber *> *parentsAreNew =
|
|
|
|
[NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
for (RCTShadowView *shadowView in viewsWithNewFrames) {
|
2015-07-17 10:53:15 +00:00
|
|
|
[frameReactTags addObject:shadowView.reactTag];
|
|
|
|
[frames addObject:[NSValue valueWithCGRect:shadowView.frame]];
|
2015-02-20 04:10:52 +00:00
|
|
|
[areNew addObject:@(shadowView.isNewView)];
|
2015-07-17 10:53:15 +00:00
|
|
|
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-07-17 10:53:15 +00:00
|
|
|
for (RCTShadowView *shadowView in viewsWithNewFrames) {
|
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-11-03 22:45:46 +00:00
|
|
|
NSMutableArray<RCTViewManagerUIBlock> *updateBlocks = [NSMutableArray new];
|
2015-03-01 23:33:55 +00:00
|
|
|
for (RCTShadowView *shadowView in viewsWithNewFrames) {
|
2015-08-06 22:44:15 +00:00
|
|
|
RCTViewManager *manager = [_componentDataByName[shadowView.viewName] manager];
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView];
|
2015-09-03 10:36:32 +00:00
|
|
|
if (shadowView.onLayout) {
|
|
|
|
CGRect frame = shadowView.frame;
|
|
|
|
shadowView.onLayout(@{
|
|
|
|
@"layout": @{
|
|
|
|
@"x": @(frame.origin.x),
|
|
|
|
@"y": @(frame.origin.y),
|
|
|
|
@"width": @(frame.size.width),
|
|
|
|
@"height": @(frame.size.height),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2015-10-26 22:39:06 +00:00
|
|
|
|
|
|
|
if (RCTIsReactRootView(shadowView.reactTag)) {
|
|
|
|
NSNumber *reactTag = shadowView.reactTag;
|
|
|
|
CGSize contentSize = shadowView.frame.size;
|
|
|
|
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
UIView *view = _viewRegistry[reactTag];
|
|
|
|
RCTAssert(view != nil, @"view (for ID %@) not found", reactTag);
|
|
|
|
|
|
|
|
RCTRootView *rootView = (RCTRootView *)[view superview];
|
|
|
|
|
|
|
|
rootView.intrinsicSize = contentSize;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-09-03 10:36:32 +00:00
|
|
|
if (block) {
|
|
|
|
[updateBlocks addObject:block];
|
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Perform layout (possibly animated)
|
2015-11-14 18:25:00 +00:00
|
|
|
return ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
2015-07-17 10:53:15 +00:00
|
|
|
RCTResponseSenderBlock callback = self->_layoutAnimation.callback;
|
2015-09-04 12:26:09 +00:00
|
|
|
|
|
|
|
// It's unsafe to call this callback more than once, so we nil it out here
|
|
|
|
// to make sure that doesn't happen.
|
|
|
|
_layoutAnimation.callback = nil;
|
|
|
|
|
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];
|
|
|
|
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
|
|
|
|
|
|
|
BOOL isNew = [areNew[ii] boolValue];
|
2015-07-17 10:53:15 +00:00
|
|
|
RCTAnimation *updateAnimation = isNew ? nil : _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-17 10:53:15 +00:00
|
|
|
RCTAnimation *createAnimation = shouldAnimateCreation ? _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++;
|
|
|
|
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-17 10:53:15 +00:00
|
|
|
block(self, _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-17 10:53:15 +00:00
|
|
|
block(self, _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-10-16 15:37:27 +00:00
|
|
|
CATransform3D finalTransform = view.layer.transform;
|
|
|
|
CGFloat finalOpacity = view.layer.opacity;
|
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"]) {
|
2015-10-16 15:37:27 +00:00
|
|
|
view.layer.transform = finalTransform;
|
2015-02-20 04:10:52 +00:00
|
|
|
} else if ([createAnimation.property isEqual:@"opacity"]) {
|
2015-10-16 15:37:27 +00:00
|
|
|
view.layer.opacity = finalOpacity;
|
2015-02-20 04:10:52 +00:00
|
|
|
} else {
|
2015-03-01 23:33:55 +00:00
|
|
|
RCTLogError(@"Unsupported layout animation createConfig property %@",
|
|
|
|
createAnimation.property);
|
|
|
|
}
|
|
|
|
for (RCTViewManagerUIBlock block in updateBlocks) {
|
2015-07-17 10:53:15 +00:00
|
|
|
block(self, _viewRegistry);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
} withCompletionBlock:nil];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_amendPendingUIBlocksWithStylePropagationUpdateForRootView:(RCTShadowView *)topView
|
|
|
|
{
|
2015-11-03 22:45:46 +00:00
|
|
|
NSMutableSet<RCTApplierBlock> *applierBlocks = [NSMutableSet setWithCapacity:1];
|
2015-02-20 04:10:52 +00:00
|
|
|
[topView collectUpdatedProperties:applierBlocks parentProperties:@{}];
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-10-27 12:07:44 +00:00
|
|
|
if (applierBlocks.count) {
|
2015-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
2015-10-27 12:07:44 +00:00
|
|
|
for (RCTApplierBlock block in applierBlocks) {
|
|
|
|
block(viewRegistry);
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A method to be called from JS, which takes a container ID and then releases
|
|
|
|
* all subviews for that container upon receipt.
|
|
|
|
*/
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID:(nonnull NSNumber *)containerID)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-08-06 22:44:15 +00:00
|
|
|
id<RCTComponent> 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-11-03 22:45:46 +00:00
|
|
|
NSMutableArray<NSNumber *> *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.
|
|
|
|
*/
|
2015-11-03 22:45:46 +00:00
|
|
|
- (NSArray<id<RCTComponent>> *)_childrenToRemoveFromContainer:(id<RCTComponent>)container
|
|
|
|
atIndices:(NSArray<NSNumber *> *)atIndices
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
// 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.
|
2015-11-03 22:45:46 +00:00
|
|
|
NSMutableArray<id<RCTComponent>> *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-11-05 20:19:56 +00:00
|
|
|
NSString *message = [NSString stringWithFormat:@"removedChildren count (%tu) was not what we expected (%tu)",
|
|
|
|
removedChildren.count, atIndices.count];
|
|
|
|
RCTFatal(RCTErrorWithMessage(message));
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
return removedChildren;
|
|
|
|
}
|
|
|
|
|
2015-11-03 22:45:46 +00:00
|
|
|
- (void)_removeChildren:(NSArray<id<RCTComponent>> *)children
|
|
|
|
fromContainer:(id<RCTComponent>)container
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-11-03 22:45:46 +00:00
|
|
|
for (id<RCTComponent> removedChild in children) {
|
2015-02-20 04:10:52 +00:00
|
|
|
[container removeReactSubview:removedChild];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(removeRootView:(nonnull 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-11-14 18:25:00 +00:00
|
|
|
[self _purgeChildren:(NSArray<id<RCTComponent>> *)rootShadowView.reactSubviews
|
|
|
|
fromRegistry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry];
|
|
|
|
[_shadowViewRegistry removeObjectForKey:rootReactTag];
|
2015-02-20 04:10:52 +00:00
|
|
|
[_rootViewTags removeObject:rootReactTag];
|
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
|
2015-04-11 22:08:00 +00:00
|
|
|
RCTAssertMainThread();
|
2015-02-20 04:10:52 +00:00
|
|
|
UIView *rootView = viewRegistry[rootReactTag];
|
2015-11-14 18:25:00 +00:00
|
|
|
[uiManager _purgeChildren:(NSArray<id<RCTComponent>> *)rootView.reactSubviews
|
|
|
|
fromRegistry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry];
|
|
|
|
[(NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry removeObjectForKey:rootReactTag];
|
2015-08-17 13:11:29 +00:00
|
|
|
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRemoveRootViewNotification
|
|
|
|
object:uiManager
|
|
|
|
userInfo:@{ RCTUIManagerRootViewKey: rootView }];
|
2015-02-20 04:10:52 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-11-03 22:45:46 +00:00
|
|
|
RCT_EXPORT_METHOD(replaceExistingNonRootView:(nonnull NSNumber *)reactTag
|
|
|
|
withView:(nonnull 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);
|
2015-11-03 22:45:46 +00:00
|
|
|
NSArray<NSNumber *> *removeAtIndices = @[@(indexOfView)];
|
|
|
|
NSArray<NSNumber *> *addTags = @[newReactTag];
|
2015-02-20 04:10:52 +00:00
|
|
|
[self manageChildren:superShadowView.reactTag
|
|
|
|
moveFromIndices:nil
|
|
|
|
moveToIndices:nil
|
|
|
|
addChildReactTags:addTags
|
|
|
|
addAtIndices:removeAtIndices
|
|
|
|
removeAtIndices:removeAtIndices];
|
|
|
|
}
|
|
|
|
|
2015-12-03 18:33:03 +00:00
|
|
|
RCT_EXPORT_METHOD(addChildren:(nonnull NSNumber *)containerTag
|
|
|
|
reactTags:(NSNumberArray *)reactTags)
|
|
|
|
{
|
|
|
|
RCTAddChildren(containerTag, reactTags,
|
|
|
|
(NSDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry);
|
|
|
|
|
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
|
|
|
|
|
|
|
|
RCTAddChildren(containerTag, reactTags,
|
|
|
|
(NSDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry);
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void RCTAddChildren(NSNumber *containerTag,
|
|
|
|
NSArray<NSNumber *> *reactTags,
|
|
|
|
NSDictionary<NSNumber *, id<RCTComponent>> *registry)
|
|
|
|
{
|
|
|
|
id<RCTComponent> container = registry[containerTag];
|
|
|
|
NSInteger index = [container reactSubviews].count;
|
|
|
|
for (NSNumber *reactTag in reactTags) {
|
|
|
|
id<RCTComponent> view = registry[reactTag];
|
|
|
|
if (view) {
|
|
|
|
[container insertReactSubview:view atIndex:index++];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerReactTag
|
2015-11-03 22:45:46 +00:00
|
|
|
moveFromIndices:(NSNumberArray *)moveFromIndices
|
|
|
|
moveToIndices:(NSNumberArray *)moveToIndices
|
|
|
|
addChildReactTags:(NSNumberArray *)addChildReactTags
|
|
|
|
addAtIndices:(NSNumberArray *)addAtIndices
|
|
|
|
removeAtIndices:(NSNumberArray *)removeAtIndices)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
[self _manageChildren:containerReactTag
|
2015-07-17 10:53:15 +00:00
|
|
|
moveFromIndices:moveFromIndices
|
|
|
|
moveToIndices:moveToIndices
|
2015-02-20 04:10:52 +00:00
|
|
|
addChildReactTags:addChildReactTags
|
|
|
|
addAtIndices:addAtIndices
|
|
|
|
removeAtIndices:removeAtIndices
|
2015-11-14 18:25:00 +00:00
|
|
|
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)_shadowViewRegistry];
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
|
2015-07-17 10:53:15 +00:00
|
|
|
[uiManager _manageChildren:containerReactTag
|
|
|
|
moveFromIndices:moveFromIndices
|
|
|
|
moveToIndices:moveToIndices
|
|
|
|
addChildReactTags:addChildReactTags
|
|
|
|
addAtIndices:addAtIndices
|
|
|
|
removeAtIndices:removeAtIndices
|
2015-11-14 18:25:00 +00:00
|
|
|
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)viewRegistry];
|
2015-07-17 10:53:15 +00:00
|
|
|
}];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)_manageChildren:(NSNumber *)containerReactTag
|
2015-11-03 22:45:46 +00:00
|
|
|
moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
|
|
|
|
moveToIndices:(NSArray<NSNumber *> *)moveToIndices
|
|
|
|
addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
|
|
|
|
addAtIndices:(NSArray<NSNumber *> *)addAtIndices
|
|
|
|
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices
|
2015-11-14 18:25:00 +00:00
|
|
|
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-12-03 18:33:03 +00:00
|
|
|
RCTAssert(moveFromIndices.count == moveToIndices.count,
|
|
|
|
@"moveFromIndices had size %tu, moveToIndices had size %tu",
|
|
|
|
moveFromIndices.count, moveToIndices.count);
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-12-03 18:33:03 +00:00
|
|
|
RCTAssert(addChildReactTags.count == addAtIndices.count,
|
|
|
|
@"addChildReactTags had size %tu, addAtIndices had size %tu",
|
|
|
|
addChildReactTags.count, addAtIndices.count);
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-12-03 18:33:03 +00:00
|
|
|
id<RCTComponent> container = registry[containerReactTag];
|
|
|
|
NSArray<id<RCTComponent>> *views = [container reactSubviews];
|
|
|
|
|
|
|
|
// Get indices to insert/remove
|
|
|
|
NSUInteger purgeCount = removeAtIndices.count;
|
|
|
|
NSUInteger moveCount = moveFromIndices.count;
|
|
|
|
NSUInteger insertCount = addAtIndices.count;
|
|
|
|
NSUInteger removeCount = purgeCount + moveCount;
|
|
|
|
NSMutableArray<id<RCTComponent>> *toRemove = removeCount ? [NSMutableArray new] : nil;
|
|
|
|
NSMutableDictionary *toInsert = insertCount ? [NSMutableDictionary new] : nil;
|
|
|
|
for (NSNumber *index in removeAtIndices) {
|
|
|
|
id<RCTComponent> view = views[index.integerValue];
|
|
|
|
[toRemove addObject:view];
|
|
|
|
RCTTraverseViewNodes(registry[view.reactTag], ^(id<RCTComponent> subview) {
|
|
|
|
RCTAssert(![subview isReactRootView], @"Root views should not be unregistered");
|
|
|
|
if ([subview conformsToProtocol:@protocol(RCTInvalidating)]) {
|
|
|
|
[(id<RCTInvalidating>)subview invalidate];
|
|
|
|
}
|
|
|
|
[registry removeObjectForKey:subview.reactTag];
|
|
|
|
[_bridgeTransactionListeners removeObject:subview];
|
|
|
|
});
|
2015-07-17 10:53:15 +00:00
|
|
|
}
|
2015-12-03 18:33:03 +00:00
|
|
|
NSInteger i = 0;
|
|
|
|
for (NSNumber *index in moveFromIndices) {
|
|
|
|
id<RCTComponent> view = views[index.integerValue];
|
|
|
|
[toRemove addObject:view];
|
|
|
|
toInsert[moveToIndices[i]] = view;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
i = 0;
|
|
|
|
for (NSNumber *reactTag in addChildReactTags) {
|
|
|
|
toInsert[addAtIndices[i]] = registry[reactTag];
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove old views
|
|
|
|
for (id<RCTComponent> view in toRemove) {
|
|
|
|
[container removeReactSubview:view];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-12-03 18:33:03 +00:00
|
|
|
// Insert new views in ascending order
|
|
|
|
for (NSNumber *index in [toInsert.allKeys sortedArrayUsingSelector:@selector(compare:)]) {
|
|
|
|
[container insertReactSubview:toInsert[index] atIndex:index.integerValue];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(createView:(nonnull NSNumber *)reactTag
|
2015-04-08 15:52:48 +00:00
|
|
|
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-08-06 22:44:15 +00:00
|
|
|
RCTComponentData *componentData = _componentDataByName[viewName];
|
|
|
|
if (componentData == nil) {
|
|
|
|
RCTLogError(@"No component found for view with name \"%@\"", viewName);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-08-06 22:44:15 +00:00
|
|
|
// Register shadow view
|
|
|
|
RCTShadowView *shadowView = [componentData createShadowViewWithTag:reactTag];
|
2015-11-16 17:16:25 +00:00
|
|
|
if (shadowView) {
|
|
|
|
[componentData setProps:props forShadowView:shadowView];
|
|
|
|
_shadowViewRegistry[reactTag] = shadowView;
|
|
|
|
}
|
2015-04-07 21:26:43 +00:00
|
|
|
|
2015-07-17 10:53:15 +00:00
|
|
|
// 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;
|
2015-04-07 21:26:43 +00:00
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
|
|
|
|
UIView *view = [componentData createViewWithTag:reactTag props:props];
|
2015-11-16 17:16:25 +00:00
|
|
|
if (view) {
|
|
|
|
if ([view respondsToSelector:@selector(setBackgroundColor:)]) {
|
|
|
|
((UIView *)view).backgroundColor = backgroundColor;
|
|
|
|
}
|
|
|
|
[componentData setProps:props forView:view];
|
|
|
|
if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) {
|
|
|
|
[uiManager->_bridgeTransactionListeners addObject:view];
|
|
|
|
}
|
|
|
|
((NSMutableDictionary<NSNumber *, UIView *> *)viewRegistry)[reactTag] = view;
|
2015-07-17 10:53:15 +00:00
|
|
|
}
|
|
|
|
}];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
2015-04-11 22:08:00 +00:00
|
|
|
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(updateView:(nonnull NSNumber *)reactTag
|
2015-08-11 22:12:55 +00:00
|
|
|
viewName:(NSString *)viewName // not always reliable, use shadowView.viewName if available
|
2015-04-08 15:52:48 +00:00
|
|
|
props:(NSDictionary *)props)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
|
|
|
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
2015-08-11 22:12:55 +00:00
|
|
|
RCTComponentData *componentData = _componentDataByName[shadowView.viewName ?: viewName];
|
2015-08-06 22:44:15 +00:00
|
|
|
[componentData setProps:props forShadowView:shadowView];
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
2015-07-17 10:53:15 +00:00
|
|
|
UIView *view = viewRegistry[reactTag];
|
2015-08-06 22:44:15 +00:00
|
|
|
[componentData setProps:props forView:view];
|
2015-07-17 10:53:15 +00:00
|
|
|
}];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *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-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(blur:(nonnull NSNumber *)reactTag)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry){
|
2015-02-20 04:10:52 +00:00
|
|
|
UIView *currentResponder = viewRegistry[reactTag];
|
|
|
|
[currentResponder resignFirstResponder];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(findSubviewIn:(nonnull NSNumber *)reactTag atPoint:(CGPoint)point callback:(RCTResponseSenderBlock)callback)
|
|
|
|
{
|
2015-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *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) {
|
2015-08-24 10:14:33 +00:00
|
|
|
target = target.superview;
|
2015-05-26 18:16:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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-12-02 13:12:17 +00:00
|
|
|
- (void)partialBatchDidFlush
|
|
|
|
{
|
|
|
|
if (self.unsafeFlushUIChangesBeforeBatchEnds) {
|
|
|
|
[self flushUIBlocks];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
- (void)batchDidComplete
|
|
|
|
{
|
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)
|
2015-08-06 22:44:15 +00:00
|
|
|
for (RCTComponentData *componentData in _componentDataByName.allValues) {
|
|
|
|
RCTViewManagerUIBlock uiBlock = [componentData.manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
|
2015-05-29 17:27:14 +00:00
|
|
|
[self addUIBlock:uiBlock];
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
// Set up next layout animation
|
|
|
|
if (_nextLayoutAnimation) {
|
|
|
|
RCTLayoutAnimation *layoutAnimation = _nextLayoutAnimation;
|
2015-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *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-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
2015-02-20 04:10:52 +00:00
|
|
|
uiManager->_layoutAnimation = nil;
|
|
|
|
}];
|
|
|
|
_nextLayoutAnimation = nil;
|
|
|
|
}
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-11-17 18:19:48 +00:00
|
|
|
[self addUIBlock:^(RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
|
|
/**
|
|
|
|
* TODO(tadeu): Remove it once and for all
|
|
|
|
*/
|
|
|
|
for (id<RCTComponent> node in uiManager->_bridgeTransactionListeners) {
|
|
|
|
[node reactBridgeDidFinishTransaction];
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
|
2015-03-25 00:37:03 +00:00
|
|
|
[self flushUIBlocks];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)flushUIBlocks
|
|
|
|
{
|
2015-11-03 11:54:23 +00:00
|
|
|
RCTAssertThread(_shadowQueue, @"flushUIBlocks can only be called from the shadow queue");
|
|
|
|
|
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-11-03 22:45:46 +00:00
|
|
|
NSArray<dispatch_block_t> *previousPendingUIBlocks = _pendingUIBlocks;
|
2015-08-17 14:35:34 +00:00
|
|
|
_pendingUIBlocks = [NSMutableArray new];
|
2015-03-01 23:33:55 +00:00
|
|
|
|
2015-10-27 12:07:45 +00:00
|
|
|
if (previousPendingUIBlocks.count) {
|
|
|
|
// Execute the previously queued UI blocks
|
|
|
|
RCTProfileBeginFlowEvent();
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
|
|
RCTProfileEndFlowEvent();
|
2015-11-09 16:42:51 +00:00
|
|
|
RCT_PROFILE_BEGIN_EVENT(0, @"UIManager flushUIBlocks", nil);
|
2015-10-27 12:07:45 +00:00
|
|
|
@try {
|
|
|
|
for (dispatch_block_t block in previousPendingUIBlocks) {
|
|
|
|
block();
|
|
|
|
}
|
2015-10-27 12:07:40 +00:00
|
|
|
}
|
2015-10-27 12:07:45 +00:00
|
|
|
@catch (NSException *exception) {
|
|
|
|
RCTLogError(@"Exception thrown while executing UI block: %@", exception);
|
|
|
|
}
|
2015-11-09 16:42:51 +00:00
|
|
|
RCT_PROFILE_END_EVENT(0, @"objc_call", @{
|
2015-10-27 12:07:45 +00:00
|
|
|
@"count": @(previousPendingUIBlocks.count),
|
|
|
|
});
|
2015-04-20 11:55:05 +00:00
|
|
|
});
|
2015-10-27 12:07:45 +00:00
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(measure:(nonnull NSNumber *)reactTag
|
2015-04-08 15:52:48 +00:00
|
|
|
callback:(RCTResponseSenderBlock)callback)
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *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-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(measureLayout:(nonnull NSNumber *)reactTag
|
|
|
|
relativeTo:(nonnull 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-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(measureLayoutRelativeToParent:(nonnull 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-07-31 13:55:47 +00:00
|
|
|
parentView:(nonnull 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;
|
|
|
|
}
|
2015-11-03 22:45:46 +00:00
|
|
|
NSArray<RCTShadowView *> *childShadowViews = [shadowView reactSubviews];
|
|
|
|
NSMutableArray<NSDictionary *> *results =
|
|
|
|
[[NSMutableArray alloc] initWithCapacity:childShadowViews.count];
|
2015-04-19 19:55:46 +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]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* JS sets what *it* considers to be the responder. Later, scroll views can use
|
|
|
|
* this in order to determine if scrolling is appropriate.
|
|
|
|
*/
|
2015-07-31 13:55:47 +00:00
|
|
|
RCT_EXPORT_METHOD(setJSResponder:(nonnull 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-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
2015-07-17 10:53:15 +00:00
|
|
|
_jsResponder = viewRegistry[reactTag];
|
|
|
|
if (!_jsResponder) {
|
|
|
|
RCTLogError(@"Invalid view set to be the JS responder - tag %zd", 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-11-14 18:25:00 +00:00
|
|
|
[self addUIBlock:^(__unused RCTUIManager *uiManager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
2015-02-20 04:10:52 +00:00
|
|
|
_jsResponder = nil;
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
- (NSDictionary<NSString *, id> *)constantsToExport
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-11-14 18:25:00 +00:00
|
|
|
NSMutableDictionary<NSString *, NSDictionary *> *allJSConstants = [NSMutableDictionary new];
|
|
|
|
NSMutableDictionary<NSString *, NSDictionary *> *directEvents = [NSMutableDictionary new];
|
|
|
|
NSMutableDictionary<NSString *, NSDictionary *> *bubblingEvents = [NSMutableDictionary new];
|
2015-02-20 04:10:52 +00:00
|
|
|
|
Added mechanism for directly mapping JS event handlers to blocks
Summary:
Currently, the system for mapping JS event handlers to blocks is quite clean on the JS side, but is clunky on the native side. The event property is passed as a boolean, which can then be checked by the native side, and if true, the native side is supposed to send an event via the event dispatcher.
This diff adds the facility to declare the property as a block instead. This means that the event side can simply call the block, and it will automatically send the event. Because the blocks for bubbling and direct events are named differently, we can also use this to generate the event registration data and get rid of the arrays of event names.
The name of the event is inferred from the property name, which means that the property for an event called "load" must be called `onLoad` or the mapping won't work. This can be optionally remapped to a different property name on the view itself if necessary, e.g.
RCT_REMAP_VIEW_PROPERTY(onLoad, loadEventBlock, RCTDirectEventBlock)
If you don't want to use this mechanism then for now it is still possible to declare the property as a BOOL instead and use the old mechanism (this approach is now deprecated however, and may eventually be removed altogether).
2015-09-02 12:58:10 +00:00
|
|
|
[_componentDataByName enumerateKeysAndObjectsUsingBlock:
|
|
|
|
^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
NSMutableDictionary<NSString *, id> *constantsNamespace =
|
|
|
|
[NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
|
2015-02-20 04:10:52 +00:00
|
|
|
|
2015-11-18 12:45:22 +00:00
|
|
|
// Add manager class
|
2015-11-25 11:09:00 +00:00
|
|
|
constantsNamespace[@"Manager"] = RCTBridgeModuleNameForClass(componentData.managerClass);
|
Added mechanism for directly mapping JS event handlers to blocks
Summary:
Currently, the system for mapping JS event handlers to blocks is quite clean on the JS side, but is clunky on the native side. The event property is passed as a boolean, which can then be checked by the native side, and if true, the native side is supposed to send an event via the event dispatcher.
This diff adds the facility to declare the property as a block instead. This means that the event side can simply call the block, and it will automatically send the event. Because the blocks for bubbling and direct events are named differently, we can also use this to generate the event registration data and get rid of the arrays of event names.
The name of the event is inferred from the property name, which means that the property for an event called "load" must be called `onLoad` or the mapping won't work. This can be optionally remapped to a different property name on the view itself if necessary, e.g.
RCT_REMAP_VIEW_PROPERTY(onLoad, loadEventBlock, RCTDirectEventBlock)
If you don't want to use this mechanism then for now it is still possible to declare the property as a BOOL instead and use the old mechanism (this approach is now deprecated however, and may eventually be removed altogether).
2015-09-02 12:58:10 +00:00
|
|
|
|
|
|
|
// Add native props
|
2015-11-14 18:25:00 +00:00
|
|
|
NSDictionary<NSString *, id> *viewConfig = [componentData viewConfig];
|
Added mechanism for directly mapping JS event handlers to blocks
Summary:
Currently, the system for mapping JS event handlers to blocks is quite clean on the JS side, but is clunky on the native side. The event property is passed as a boolean, which can then be checked by the native side, and if true, the native side is supposed to send an event via the event dispatcher.
This diff adds the facility to declare the property as a block instead. This means that the event side can simply call the block, and it will automatically send the event. Because the blocks for bubbling and direct events are named differently, we can also use this to generate the event registration data and get rid of the arrays of event names.
The name of the event is inferred from the property name, which means that the property for an event called "load" must be called `onLoad` or the mapping won't work. This can be optionally remapped to a different property name on the view itself if necessary, e.g.
RCT_REMAP_VIEW_PROPERTY(onLoad, loadEventBlock, RCTDirectEventBlock)
If you don't want to use this mechanism then for now it is still possible to declare the property as a BOOL instead and use the old mechanism (this approach is now deprecated however, and may eventually be removed altogether).
2015-09-02 12:58:10 +00:00
|
|
|
constantsNamespace[@"NativeProps"] = viewConfig[@"propTypes"];
|
|
|
|
|
|
|
|
// Add direct events
|
|
|
|
for (NSString *eventName in viewConfig[@"directEvents"]) {
|
|
|
|
if (!directEvents[eventName]) {
|
|
|
|
directEvents[eventName] = @{
|
|
|
|
@"registrationName": [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (RCT_DEBUG && bubblingEvents[eventName]) {
|
|
|
|
RCTLogError(@"Component '%@' re-registered bubbling event '%@' as a "
|
|
|
|
"direct event", componentData.name, eventName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add bubbling events
|
|
|
|
for (NSString *eventName in viewConfig[@"bubblingEvents"]) {
|
|
|
|
if (!bubblingEvents[eventName]) {
|
|
|
|
NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
|
|
|
|
bubblingEvents[eventName] = @{
|
|
|
|
@"phasedRegistrationNames": @{
|
|
|
|
@"bubbled": bubbleName,
|
|
|
|
@"captured": [bubbleName stringByAppendingString:@"Capture"],
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (RCT_DEBUG && directEvents[eventName]) {
|
|
|
|
RCTLogError(@"Component '%@' re-registered direct event '%@' as a "
|
|
|
|
"bubbling event", componentData.name, eventName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
allJSConstants[name] = constantsNamespace;
|
Added mechanism for directly mapping JS event handlers to blocks
Summary:
Currently, the system for mapping JS event handlers to blocks is quite clean on the JS side, but is clunky on the native side. The event property is passed as a boolean, which can then be checked by the native side, and if true, the native side is supposed to send an event via the event dispatcher.
This diff adds the facility to declare the property as a block instead. This means that the event side can simply call the block, and it will automatically send the event. Because the blocks for bubbling and direct events are named differently, we can also use this to generate the event registration data and get rid of the arrays of event names.
The name of the event is inferred from the property name, which means that the property for an event called "load" must be called `onLoad` or the mapping won't work. This can be optionally remapped to a different property name on the view itself if necessary, e.g.
RCT_REMAP_VIEW_PROPERTY(onLoad, loadEventBlock, RCTDirectEventBlock)
If you don't want to use this mechanism then for now it is still possible to declare the property as a BOOL instead and use the old mechanism (this approach is now deprecated however, and may eventually be removed altogether).
2015-09-02 12:58:10 +00:00
|
|
|
}];
|
2015-02-20 04:10:52 +00:00
|
|
|
|
Added mechanism for directly mapping JS event handlers to blocks
Summary:
Currently, the system for mapping JS event handlers to blocks is quite clean on the JS side, but is clunky on the native side. The event property is passed as a boolean, which can then be checked by the native side, and if true, the native side is supposed to send an event via the event dispatcher.
This diff adds the facility to declare the property as a block instead. This means that the event side can simply call the block, and it will automatically send the event. Because the blocks for bubbling and direct events are named differently, we can also use this to generate the event registration data and get rid of the arrays of event names.
The name of the event is inferred from the property name, which means that the property for an event called "load" must be called `onLoad` or the mapping won't work. This can be optionally remapped to a different property name on the view itself if necessary, e.g.
RCT_REMAP_VIEW_PROPERTY(onLoad, loadEventBlock, RCTDirectEventBlock)
If you don't want to use this mechanism then for now it is still possible to declare the property as a BOOL instead and use the old mechanism (this approach is now deprecated however, and may eventually be removed altogether).
2015-09-02 12:58:10 +00:00
|
|
|
[allJSConstants addEntriesFromDictionary:@{
|
|
|
|
@"customBubblingEventTypes": bubblingEvents,
|
|
|
|
@"customDirectEventTypes": directEvents,
|
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
|
|
|
},
|
|
|
|
},
|
|
|
|
}];
|
Added mechanism for directly mapping JS event handlers to blocks
Summary:
Currently, the system for mapping JS event handlers to blocks is quite clean on the JS side, but is clunky on the native side. The event property is passed as a boolean, which can then be checked by the native side, and if true, the native side is supposed to send an event via the event dispatcher.
This diff adds the facility to declare the property as a block instead. This means that the event side can simply call the block, and it will automatically send the event. Because the blocks for bubbling and direct events are named differently, we can also use this to generate the event registration data and get rid of the arrays of event names.
The name of the event is inferred from the property name, which means that the property for an event called "load" must be called `onLoad` or the mapping won't work. This can be optionally remapped to a different property name on the view itself if necessary, e.g.
RCT_REMAP_VIEW_PROPERTY(onLoad, loadEventBlock, RCTDirectEventBlock)
If you don't want to use this mechanism then for now it is still possible to declare the property as a BOOL instead and use the old mechanism (this approach is now deprecated however, and may eventually be removed altogether).
2015-09-02 12:58:10 +00:00
|
|
|
|
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-11-25 11:09:00 +00:00
|
|
|
return [self moduleForClass:[RCTUIManager class]];
|
2015-03-01 23:33:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|