2015-08-06 22:44:15 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
*
|
2018-02-17 02:24:55 +00:00
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
2015-08-06 22:44:15 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#import "RCTComponentData.h"
|
|
|
|
|
|
|
|
#import <objc/message.h>
|
|
|
|
|
|
|
|
#import "RCTBridge.h"
|
2016-11-23 15:47:52 +00:00
|
|
|
#import "RCTBridgeModule.h"
|
2016-04-15 20:23:45 +00:00
|
|
|
#import "RCTConvert.h"
|
2017-10-26 23:51:03 +00:00
|
|
|
#import "RCTParserUtils.h"
|
2015-08-06 22:44:15 +00:00
|
|
|
#import "RCTShadowView.h"
|
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
|
|
|
#import "RCTUtils.h"
|
2015-11-14 18:25:00 +00:00
|
|
|
#import "UIView+React.h"
|
2015-08-06 22:44:15 +00:00
|
|
|
|
|
|
|
typedef void (^RCTPropBlock)(id<RCTComponent> view, id json);
|
2017-02-20 13:08:20 +00:00
|
|
|
typedef NSMutableDictionary<NSString *, RCTPropBlock> RCTPropBlockDictionary;
|
2015-08-06 22:44:15 +00:00
|
|
|
|
2017-10-26 23:51:03 +00:00
|
|
|
/**
|
|
|
|
* Get the converter function for the specified type
|
|
|
|
*/
|
|
|
|
static SEL selectorForType(NSString *type)
|
|
|
|
{
|
|
|
|
const char *input = type.UTF8String;
|
|
|
|
return NSSelectorFromString([RCTParseType(&input) stringByAppendingString:@":"]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-06 22:44:15 +00:00
|
|
|
@implementation RCTComponentData
|
|
|
|
{
|
Removed defaultViews
Summary:When a component prop is set to null/undefined, and doesn't have a default value specified in `getDefaultProps`, the null value is sent over the bridge as a sentinel to reset to the original native value.
On iOS this is handled by creating a default view instance for each view type. The default view is then used to look up the unmodified value for any prop that is reset.
This is rather expensive however, as it means that for complex views (e.g. WebView, MapView), a minimum of two instances will be created even if only one is needed, and the default view will remain even after all actual view instances have been released.
This diff replaces the default view mechanism with a system where the default value of each prop is recorded the first time it is set. This avoids the need to keep an extra copy of the whole view.
The only exception is for props that use the `RCT_CUSTOM_VIEW_PROPERTY` macro, which includes the default view as part of the interface. To avoid a breaking change, a default view will still be created for views that use this macro, but only if they are sent a null value (so very rarely, in practice). In a future update we may deprecate or replace `RCT_CUSTOM_VIEW_PROPERTY` if there are significant benefits to doing so.
Reviewed By: javache
Differential Revision: D3012115
fb-gh-sync-id: 259348e54aa8342f444ad182b6f883d2dd684973
shipit-source-id: 259348e54aa8342f444ad182b6f883d2dd684973
2016-03-09 16:55:45 +00:00
|
|
|
id<RCTComponent> _defaultView; // Only needed for RCT_CUSTOM_VIEW_PROPERTY
|
2017-02-20 13:08:20 +00:00
|
|
|
RCTPropBlockDictionary *_viewPropBlocks;
|
|
|
|
RCTPropBlockDictionary *_shadowPropBlocks;
|
2015-12-10 20:04:53 +00:00
|
|
|
__weak RCTBridge *_bridge;
|
2015-08-06 22:44:15 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 11:09:00 +00:00
|
|
|
@synthesize manager = _manager;
|
|
|
|
|
|
|
|
- (instancetype)initWithManagerClass:(Class)managerClass
|
|
|
|
bridge:(RCTBridge *)bridge
|
2015-08-06 22:44:15 +00:00
|
|
|
{
|
|
|
|
if ((self = [super init])) {
|
2015-11-25 11:09:00 +00:00
|
|
|
_bridge = bridge;
|
|
|
|
_managerClass = managerClass;
|
2015-08-17 14:35:34 +00:00
|
|
|
_viewPropBlocks = [NSMutableDictionary new];
|
|
|
|
_shadowPropBlocks = [NSMutableDictionary new];
|
2015-08-06 22:44:15 +00:00
|
|
|
|
Support native ViewManager inheritance on iOS
Summary:
**Motivation**
This is a re-worked version of #14260, by shergin's suggestion.
For iOS, if you want to inherit from a native ViewManagers, your custom ViewManager will not automatically export the parents' props. So the only way to do this today, is to basically copy/paste the parent ViewManager-file, and add your own custom logic.
With this PR, this is made more extensible by exporting the `baseModuleName` (i.e. the iOS `superclass` of the ViewManager), and then using that value to re-establish the inheritance relationship in `requireNativeComponent`.
**Test plan**
I've run this with a test project, and it works fine there. But needs more testing.
Opened this PR as [per shergin's suggestion](https://github.com/facebook/react-native/pull/10946#issuecomment-311860545) though, so we can discuss approach.
**Discussion**
* Android already supports inheritance, so this change should be compatible with that. But, not every prop available on `UIManager.RCTView.NativeProps` is actually exported by every ViewManager. So should `UIManager.RCTView.NativeProps` still be merged with `viewConfig.NativeProps`, even if the individual ViewManager does not export/use them to begin with?
* Does this break other platforms? [UWP](https://github.com/Microsoft/react-native-windows)?
Closes https://github.com/facebook/react-native/pull/14775
Differential Revision: D5392953
Pulled By: shergin
fbshipit-source-id: 5212da616acfba50cc285e2997d183cf8b2cd09f
2017-07-10 22:48:34 +00:00
|
|
|
_name = moduleNameForClass(managerClass);
|
2015-08-06 22:44:15 +00:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2015-11-25 11:09:00 +00:00
|
|
|
- (RCTViewManager *)manager
|
|
|
|
{
|
|
|
|
if (!_manager) {
|
|
|
|
_manager = [_bridge moduleForClass:_managerClass];
|
|
|
|
}
|
|
|
|
return _manager;
|
|
|
|
}
|
|
|
|
|
2015-08-24 10:14:33 +00:00
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
2015-08-06 22:44:15 +00:00
|
|
|
|
2015-12-30 22:15:38 +00:00
|
|
|
- (UIView *)createViewWithTag:(NSNumber *)tag
|
2015-08-06 22:44:15 +00:00
|
|
|
{
|
2016-06-06 14:57:55 +00:00
|
|
|
RCTAssertMainQueue();
|
2015-08-06 22:44:15 +00:00
|
|
|
|
2016-03-16 14:56:25 +00:00
|
|
|
UIView *view = [self.manager view];
|
2015-08-06 22:44:15 +00:00
|
|
|
view.reactTag = tag;
|
2016-09-27 13:19:45 +00:00
|
|
|
#if !TARGET_OS_TV
|
2015-11-14 18:25:00 +00:00
|
|
|
view.multipleTouchEnabled = YES;
|
2016-09-27 13:19:45 +00:00
|
|
|
#endif
|
2015-11-14 18:25:00 +00:00
|
|
|
view.userInteractionEnabled = YES; // required for touch handling
|
|
|
|
view.layer.allowsGroupOpacity = YES; // required for touch handling
|
2015-08-06 22:44:15 +00:00
|
|
|
return view;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (RCTShadowView *)createShadowViewWithTag:(NSNumber *)tag
|
|
|
|
{
|
2015-11-25 11:09:00 +00:00
|
|
|
RCTShadowView *shadowView = [self.manager shadowView];
|
2015-08-06 22:44:15 +00:00
|
|
|
shadowView.reactTag = tag;
|
|
|
|
shadowView.viewName = _name;
|
|
|
|
return shadowView;
|
|
|
|
}
|
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
- (void)callCustomSetter:(SEL)setter onView:(id<RCTComponent>)view withProp:(id)json isShadowView:(BOOL)isShadowView
|
2015-08-06 22:44:15 +00:00
|
|
|
{
|
2017-02-20 13:08:20 +00:00
|
|
|
json = RCTNilIfNull(json);
|
|
|
|
if (!isShadowView) {
|
|
|
|
if (!json && !_defaultView) {
|
|
|
|
// Only create default view if json is null
|
|
|
|
_defaultView = [self createViewWithTag:nil];
|
2015-08-06 22:44:15 +00:00
|
|
|
}
|
2017-02-20 13:08:20 +00:00
|
|
|
((void (*)(id, SEL, id, id, id))objc_msgSend)(self.manager, setter, json, view, _defaultView);
|
|
|
|
} else {
|
|
|
|
((void (*)(id, SEL, id, id))objc_msgSend)(self.manager, setter, json, view);
|
|
|
|
}
|
|
|
|
}
|
2015-08-06 22:44:15 +00:00
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
static RCTPropBlock createEventSetter(NSString *propName, SEL setter, RCTBridge *bridge)
|
|
|
|
{
|
|
|
|
__weak RCTBridge *weakBridge = bridge;
|
|
|
|
return ^(id target, id json) {
|
|
|
|
void (^eventHandler)(NSDictionary *event) = nil;
|
|
|
|
if ([RCTConvert BOOL:json]) {
|
|
|
|
__weak id<RCTComponent> weakTarget = target;
|
|
|
|
eventHandler = ^(NSDictionary *event) {
|
|
|
|
// The component no longer exists, we shouldn't send the event
|
|
|
|
id<RCTComponent> strongTarget = weakTarget;
|
|
|
|
if (!strongTarget) {
|
Removed defaultViews
Summary:When a component prop is set to null/undefined, and doesn't have a default value specified in `getDefaultProps`, the null value is sent over the bridge as a sentinel to reset to the original native value.
On iOS this is handled by creating a default view instance for each view type. The default view is then used to look up the unmodified value for any prop that is reset.
This is rather expensive however, as it means that for complex views (e.g. WebView, MapView), a minimum of two instances will be created even if only one is needed, and the default view will remain even after all actual view instances have been released.
This diff replaces the default view mechanism with a system where the default value of each prop is recorded the first time it is set. This avoids the need to keep an extra copy of the whole view.
The only exception is for props that use the `RCT_CUSTOM_VIEW_PROPERTY` macro, which includes the default view as part of the interface. To avoid a breaking change, a default view will still be created for views that use this macro, but only if they are sent a null value (so very rarely, in practice). In a future update we may deprecate or replace `RCT_CUSTOM_VIEW_PROPERTY` if there are significant benefits to doing so.
Reviewed By: javache
Differential Revision: D3012115
fb-gh-sync-id: 259348e54aa8342f444ad182b6f883d2dd684973
shipit-source-id: 259348e54aa8342f444ad182b6f883d2dd684973
2016-03-09 16:55:45 +00:00
|
|
|
return;
|
|
|
|
}
|
2015-08-06 22:44:15 +00:00
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
NSMutableDictionary *mutableEvent = [NSMutableDictionary dictionaryWithDictionary:event];
|
|
|
|
mutableEvent[@"target"] = strongTarget.reactTag;
|
2016-05-23 16:08:51 +00:00
|
|
|
#pragma clang diagnostic push
|
|
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
2017-02-20 13:08:20 +00:00
|
|
|
[weakBridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(propName) body:mutableEvent];
|
2016-05-23 16:08:51 +00:00
|
|
|
#pragma clang diagnostic pop
|
2017-02-20 13:08:20 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
((void (*)(id, SEL, id))objc_msgSend)(target, setter, eventHandler);
|
|
|
|
};
|
|
|
|
}
|
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
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
static RCTPropBlock createNSInvocationSetter(NSMethodSignature *typeSignature, SEL type, SEL getter, SEL setter)
|
|
|
|
{
|
|
|
|
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
|
|
|
|
typeInvocation.selector = type;
|
|
|
|
typeInvocation.target = [RCTConvert class];
|
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
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
__block NSInvocation *targetInvocation = nil;
|
|
|
|
__block NSMutableData *defaultValue = nil;
|
|
|
|
|
|
|
|
return ^(id target, id json) {
|
|
|
|
if (!target) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get default value
|
|
|
|
if (!defaultValue) {
|
|
|
|
if (!json) {
|
|
|
|
// We only set the defaultValue when we first pass a non-null
|
|
|
|
// value, so if the first value sent for a prop is null, it's
|
|
|
|
// a no-op (we'd be resetting it to its default when its
|
|
|
|
// value is already the default).
|
|
|
|
return;
|
2015-08-06 22:44:15 +00:00
|
|
|
}
|
2017-02-20 13:08:20 +00:00
|
|
|
// Use NSMutableData to store defaultValue instead of malloc, so
|
|
|
|
// it will be freed automatically when setterBlock is released.
|
|
|
|
defaultValue = [[NSMutableData alloc] initWithLength:typeSignature.methodReturnLength];
|
|
|
|
if ([target respondsToSelector:getter]) {
|
|
|
|
NSMethodSignature *signature = [target methodSignatureForSelector:getter];
|
|
|
|
NSInvocation *sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
|
|
|
|
sourceInvocation.selector = getter;
|
|
|
|
[sourceInvocation invokeWithTarget:target];
|
|
|
|
[sourceInvocation getReturnValue:defaultValue.mutableBytes];
|
|
|
|
}
|
|
|
|
}
|
2015-08-06 22:44:15 +00:00
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
// Get value
|
|
|
|
BOOL freeValueOnCompletion = NO;
|
|
|
|
void *value = defaultValue.mutableBytes;
|
|
|
|
if (json) {
|
|
|
|
freeValueOnCompletion = YES;
|
|
|
|
value = malloc(typeSignature.methodReturnLength);
|
2018-08-09 01:24:47 +00:00
|
|
|
if (!value) {
|
|
|
|
// CWE - 391 : Unchecked error condition
|
|
|
|
// https://www.cvedetails.com/cwe-details/391/Unchecked-Error-Condition.html
|
|
|
|
// https://eli.thegreenplace.net/2009/10/30/handling-out-of-memory-conditions-in-c
|
|
|
|
abort();
|
|
|
|
}
|
2017-02-20 13:08:20 +00:00
|
|
|
[typeInvocation setArgument:&json atIndex:2];
|
|
|
|
[typeInvocation invoke];
|
|
|
|
[typeInvocation getReturnValue:value];
|
|
|
|
}
|
2015-08-06 22:44:15 +00:00
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
// Set value
|
|
|
|
if (!targetInvocation) {
|
|
|
|
NSMethodSignature *signature = [target methodSignatureForSelector:setter];
|
|
|
|
targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
|
|
|
|
targetInvocation.selector = setter;
|
|
|
|
}
|
|
|
|
[targetInvocation setArgument:value atIndex:2];
|
|
|
|
[targetInvocation invokeWithTarget:target];
|
|
|
|
if (freeValueOnCompletion) {
|
|
|
|
// Only free the value if we `malloc`d it locally, otherwise it
|
|
|
|
// points to `defaultValue.mutableBytes`, which is managed by ARC.
|
|
|
|
free(value);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2015-08-06 22:44:15 +00:00
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
- (RCTPropBlock)createPropBlock:(NSString *)name isShadowView:(BOOL)isShadowView
|
|
|
|
{
|
|
|
|
// Get type
|
|
|
|
SEL type = NULL;
|
|
|
|
NSString *keyPath = nil;
|
|
|
|
SEL selector = NSSelectorFromString([NSString stringWithFormat:@"propConfig%@_%@", isShadowView ? @"Shadow" : @"", name]);
|
|
|
|
if ([_managerClass respondsToSelector:selector]) {
|
|
|
|
NSArray<NSString *> *typeAndKeyPath = ((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(_managerClass, selector);
|
2017-10-26 23:51:03 +00:00
|
|
|
type = selectorForType(typeAndKeyPath[0]);
|
2017-02-20 13:08:20 +00:00
|
|
|
keyPath = typeAndKeyPath.count > 1 ? typeAndKeyPath[1] : nil;
|
|
|
|
} else {
|
|
|
|
return ^(__unused id view, __unused id json) {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for custom setter
|
|
|
|
if ([keyPath isEqualToString:@"__custom__"]) {
|
|
|
|
// Get custom setter. There is no default view in the shadow case, so the selector is different.
|
|
|
|
NSString *selectorString;
|
|
|
|
if (!isShadowView) {
|
|
|
|
selectorString = [NSString stringWithFormat:@"set_%@:for%@View:withDefaultView:", name, isShadowView ? @"Shadow" : @""];
|
|
|
|
} else {
|
|
|
|
selectorString = [NSString stringWithFormat:@"set_%@:forShadowView:", name];
|
|
|
|
}
|
|
|
|
|
|
|
|
SEL customSetter = NSSelectorFromString(selectorString);
|
|
|
|
__weak RCTComponentData *weakSelf = self;
|
|
|
|
return ^(id<RCTComponent> view, id json) {
|
|
|
|
[weakSelf callCustomSetter:customSetter onView:view withProp:json isShadowView:isShadowView];
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
// Disect keypath
|
|
|
|
NSString *key = name;
|
|
|
|
NSArray<NSString *> *parts = [keyPath componentsSeparatedByString:@"."];
|
|
|
|
if (parts) {
|
|
|
|
key = parts.lastObject;
|
|
|
|
parts = [parts subarrayWithRange:(NSRange){0, parts.count - 1}];
|
2015-08-06 22:44:15 +00:00
|
|
|
}
|
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
// Get property getter
|
|
|
|
SEL getter = NSSelectorFromString(key);
|
2015-08-06 22:44:15 +00:00
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
// Get property setter
|
|
|
|
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
|
|
|
|
[key substringToIndex:1].uppercaseString,
|
|
|
|
[key substringFromIndex:1]]);
|
2015-08-06 22:44:15 +00:00
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
// Build setter block
|
|
|
|
void (^setterBlock)(id target, id json) = nil;
|
|
|
|
if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") ||
|
|
|
|
type == NSSelectorFromString(@"RCTDirectEventBlock:")) {
|
|
|
|
// Special case for event handlers
|
|
|
|
setterBlock = createEventSetter(name, setter, _bridge);
|
|
|
|
} else {
|
|
|
|
// Ordinary property handlers
|
|
|
|
NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
|
|
|
|
if (!typeSignature) {
|
|
|
|
RCTLogError(@"No +[RCTConvert %@] function found.", NSStringFromSelector(type));
|
|
|
|
return ^(__unused id<RCTComponent> view, __unused id json){};
|
|
|
|
}
|
|
|
|
switch (typeSignature.methodReturnType[0]) {
|
|
|
|
|
|
|
|
#define RCT_CASE(_value, _type) \
|
|
|
|
case _value: { \
|
|
|
|
__block BOOL setDefaultValue = NO; \
|
|
|
|
__block _type defaultValue; \
|
|
|
|
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
|
|
|
|
_type (*get)(id, SEL) = (typeof(get))objc_msgSend; \
|
|
|
|
void (*set)(id, SEL, _type) = (typeof(set))objc_msgSend; \
|
|
|
|
setterBlock = ^(id target, id json) { \
|
|
|
|
if (json) { \
|
|
|
|
if (!setDefaultValue && target) { \
|
|
|
|
if ([target respondsToSelector:getter]) { \
|
|
|
|
defaultValue = get(target, getter); \
|
|
|
|
} \
|
|
|
|
setDefaultValue = YES; \
|
|
|
|
} \
|
|
|
|
set(target, setter, convert([RCTConvert class], type, json)); \
|
|
|
|
} else if (setDefaultValue) { \
|
|
|
|
set(target, setter, defaultValue); \
|
|
|
|
} \
|
|
|
|
}; \
|
|
|
|
break; \
|
|
|
|
}
|
|
|
|
|
|
|
|
RCT_CASE(_C_SEL, SEL)
|
|
|
|
RCT_CASE(_C_CHARPTR, const char *)
|
|
|
|
RCT_CASE(_C_CHR, char)
|
|
|
|
RCT_CASE(_C_UCHR, unsigned char)
|
|
|
|
RCT_CASE(_C_SHT, short)
|
|
|
|
RCT_CASE(_C_USHT, unsigned short)
|
|
|
|
RCT_CASE(_C_INT, int)
|
|
|
|
RCT_CASE(_C_UINT, unsigned int)
|
|
|
|
RCT_CASE(_C_LNG, long)
|
|
|
|
RCT_CASE(_C_ULNG, unsigned long)
|
|
|
|
RCT_CASE(_C_LNG_LNG, long long)
|
|
|
|
RCT_CASE(_C_ULNG_LNG, unsigned long long)
|
|
|
|
RCT_CASE(_C_FLT, float)
|
|
|
|
RCT_CASE(_C_DBL, double)
|
|
|
|
RCT_CASE(_C_BOOL, BOOL)
|
|
|
|
RCT_CASE(_C_PTR, void *)
|
|
|
|
RCT_CASE(_C_ID, id)
|
|
|
|
|
|
|
|
case _C_STRUCT_B:
|
|
|
|
default: {
|
|
|
|
setterBlock = createNSInvocationSetter(typeSignature, type, getter, setter);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2015-08-06 22:44:15 +00:00
|
|
|
}
|
|
|
|
|
2017-02-20 13:08:20 +00:00
|
|
|
return ^(__unused id view, __unused id json) {
|
|
|
|
// Follow keypath
|
|
|
|
id target = view;
|
|
|
|
for (NSString *part in parts) {
|
|
|
|
target = [target valueForKey:part];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set property with json
|
|
|
|
setterBlock(target, RCTNilIfNull(json));
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (RCTPropBlock)propBlockForKey:(NSString *)name isShadowView:(BOOL)isShadowView
|
|
|
|
{
|
|
|
|
RCTPropBlockDictionary *propBlocks = isShadowView ? _shadowPropBlocks : _viewPropBlocks;
|
|
|
|
RCTPropBlock propBlock = propBlocks[name];
|
|
|
|
if (!propBlock) {
|
|
|
|
propBlock = [self createPropBlock:name isShadowView:isShadowView];
|
|
|
|
|
|
|
|
#if RCT_DEBUG
|
|
|
|
// Provide more useful log feedback if there's an error
|
|
|
|
RCTPropBlock unwrappedBlock = propBlock;
|
|
|
|
__weak __typeof(self) weakSelf = self;
|
|
|
|
propBlock = ^(id<RCTComponent> view, id json) {
|
|
|
|
NSString *logPrefix = [NSString stringWithFormat:@"Error setting property '%@' of %@ with tag #%@: ",
|
|
|
|
name, weakSelf.name, view.reactTag];
|
|
|
|
RCTPerformBlockWithLogPrefix(^{
|
|
|
|
unwrappedBlock(view, json);
|
|
|
|
}, logPrefix);
|
|
|
|
};
|
|
|
|
#endif
|
2015-08-06 22:44:15 +00:00
|
|
|
propBlocks[name] = [propBlock copy];
|
|
|
|
}
|
|
|
|
return propBlock;
|
|
|
|
}
|
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
- (void)setProps:(NSDictionary<NSString *, id> *)props forView:(id<RCTComponent>)view
|
2015-08-06 22:44:15 +00:00
|
|
|
{
|
|
|
|
if (!view) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
|
2017-02-20 13:08:20 +00:00
|
|
|
[self propBlockForKey:key isShadowView:NO](view, json);
|
2015-08-06 22:44:15 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
- (void)setProps:(NSDictionary<NSString *, id> *)props forShadowView:(RCTShadowView *)shadowView
|
2015-08-06 22:44:15 +00:00
|
|
|
{
|
|
|
|
if (!shadowView) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id json, __unused BOOL *stop) {
|
2017-02-20 13:08:20 +00:00
|
|
|
[self propBlockForKey:key isShadowView:YES](shadowView, json);
|
2015-08-06 22:44:15 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2015-11-14 18:25:00 +00:00
|
|
|
- (NSDictionary<NSString *, id> *)viewConfig
|
2015-08-06 22:44:15 +00:00
|
|
|
{
|
2016-10-27 13:54:28 +00:00
|
|
|
NSMutableArray<NSString *> *bubblingEvents = [NSMutableArray new];
|
2015-11-03 22:45:46 +00:00
|
|
|
NSMutableArray<NSString *> *directEvents = [NSMutableArray new];
|
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
|
|
|
|
2016-10-27 13:54:28 +00:00
|
|
|
#pragma clang diagnostic push
|
|
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
2015-11-25 11:09:00 +00:00
|
|
|
if (RCTClassOverridesInstanceMethod(_managerClass, @selector(customBubblingEventTypes))) {
|
|
|
|
NSArray<NSString *> *events = [self.manager customBubblingEventTypes];
|
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
|
|
|
for (NSString *event in events) {
|
|
|
|
[bubblingEvents addObject:RCTNormalizeInputEventName(event)];
|
|
|
|
}
|
|
|
|
}
|
2016-10-27 13:54:26 +00:00
|
|
|
#pragma clang diagnostic pop
|
2015-08-06 22:44:15 +00:00
|
|
|
|
|
|
|
unsigned int count = 0;
|
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
|
|
|
NSMutableDictionary *propTypes = [NSMutableDictionary new];
|
2015-11-25 11:09:00 +00:00
|
|
|
Method *methods = class_copyMethodList(object_getClass(_managerClass), &count);
|
2015-08-06 22:44:15 +00:00
|
|
|
for (unsigned int i = 0; i < count; i++) {
|
2016-10-27 13:54:26 +00:00
|
|
|
SEL selector = method_getName(methods[i]);
|
|
|
|
const char *selectorName = sel_getName(selector);
|
2016-10-27 19:37:52 +00:00
|
|
|
if (strncmp(selectorName, "propConfig", strlen("propConfig")) != 0) {
|
2016-10-27 13:54:26 +00:00
|
|
|
continue;
|
|
|
|
}
|
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
|
|
|
|
2016-10-27 19:37:52 +00:00
|
|
|
// We need to handle both propConfig_* and propConfigShadow_* methods
|
|
|
|
const char *underscorePos = strchr(selectorName + strlen("propConfig"), '_');
|
|
|
|
if (!underscorePos) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSString *name = @(underscorePos + 1);
|
2016-10-27 13:54:26 +00:00
|
|
|
NSString *type = ((NSArray<NSString *> *(*)(id, SEL))objc_msgSend)(_managerClass, selector)[0];
|
|
|
|
if (RCT_DEBUG && propTypes[name] && ![propTypes[name] isEqualToString:type]) {
|
|
|
|
RCTLogError(@"Property '%@' of component '%@' redefined from '%@' "
|
|
|
|
"to '%@'", name, _name, propTypes[name], type);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ([type isEqualToString:@"RCTBubblingEventBlock"]) {
|
|
|
|
[bubblingEvents addObject:RCTNormalizeInputEventName(name)];
|
|
|
|
propTypes[name] = @"BOOL";
|
|
|
|
} else if ([type isEqualToString:@"RCTDirectEventBlock"]) {
|
|
|
|
[directEvents addObject:RCTNormalizeInputEventName(name)];
|
|
|
|
propTypes[name] = @"BOOL";
|
|
|
|
} else {
|
|
|
|
propTypes[name] = type;
|
2015-08-06 22:44:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
free(methods);
|
|
|
|
|
2016-10-27 13:54:26 +00:00
|
|
|
#if RCT_DEBUG
|
|
|
|
for (NSString *event in bubblingEvents) {
|
|
|
|
if ([directEvents containsObject:event]) {
|
|
|
|
RCTLogError(@"Component '%@' registered '%@' as both a bubbling event "
|
|
|
|
"and a direct event", _name, event);
|
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
|
|
|
}
|
|
|
|
}
|
2016-10-27 13:54:26 +00:00
|
|
|
#endif
|
Support native ViewManager inheritance on iOS
Summary:
**Motivation**
This is a re-worked version of #14260, by shergin's suggestion.
For iOS, if you want to inherit from a native ViewManagers, your custom ViewManager will not automatically export the parents' props. So the only way to do this today, is to basically copy/paste the parent ViewManager-file, and add your own custom logic.
With this PR, this is made more extensible by exporting the `baseModuleName` (i.e. the iOS `superclass` of the ViewManager), and then using that value to re-establish the inheritance relationship in `requireNativeComponent`.
**Test plan**
I've run this with a test project, and it works fine there. But needs more testing.
Opened this PR as [per shergin's suggestion](https://github.com/facebook/react-native/pull/10946#issuecomment-311860545) though, so we can discuss approach.
**Discussion**
* Android already supports inheritance, so this change should be compatible with that. But, not every prop available on `UIManager.RCTView.NativeProps` is actually exported by every ViewManager. So should `UIManager.RCTView.NativeProps` still be merged with `viewConfig.NativeProps`, even if the individual ViewManager does not export/use them to begin with?
* Does this break other platforms? [UWP](https://github.com/Microsoft/react-native-windows)?
Closes https://github.com/facebook/react-native/pull/14775
Differential Revision: D5392953
Pulled By: shergin
fbshipit-source-id: 5212da616acfba50cc285e2997d183cf8b2cd09f
2017-07-10 22:48:34 +00:00
|
|
|
|
|
|
|
Class superClass = [_managerClass superclass];
|
|
|
|
|
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
|
|
|
return @{
|
2016-10-27 13:54:26 +00:00
|
|
|
@"propTypes": propTypes,
|
|
|
|
@"directEvents": directEvents,
|
|
|
|
@"bubblingEvents": bubblingEvents,
|
2017-07-11 19:28:46 +00:00
|
|
|
@"baseModuleName": superClass == [NSObject class] ? (id)kCFNull : moduleNameForClass(superClass),
|
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-08-06 22:44:15 +00:00
|
|
|
}
|
|
|
|
|
Support native ViewManager inheritance on iOS
Summary:
**Motivation**
This is a re-worked version of #14260, by shergin's suggestion.
For iOS, if you want to inherit from a native ViewManagers, your custom ViewManager will not automatically export the parents' props. So the only way to do this today, is to basically copy/paste the parent ViewManager-file, and add your own custom logic.
With this PR, this is made more extensible by exporting the `baseModuleName` (i.e. the iOS `superclass` of the ViewManager), and then using that value to re-establish the inheritance relationship in `requireNativeComponent`.
**Test plan**
I've run this with a test project, and it works fine there. But needs more testing.
Opened this PR as [per shergin's suggestion](https://github.com/facebook/react-native/pull/10946#issuecomment-311860545) though, so we can discuss approach.
**Discussion**
* Android already supports inheritance, so this change should be compatible with that. But, not every prop available on `UIManager.RCTView.NativeProps` is actually exported by every ViewManager. So should `UIManager.RCTView.NativeProps` still be merged with `viewConfig.NativeProps`, even if the individual ViewManager does not export/use them to begin with?
* Does this break other platforms? [UWP](https://github.com/Microsoft/react-native-windows)?
Closes https://github.com/facebook/react-native/pull/14775
Differential Revision: D5392953
Pulled By: shergin
fbshipit-source-id: 5212da616acfba50cc285e2997d183cf8b2cd09f
2017-07-10 22:48:34 +00:00
|
|
|
static NSString *moduleNameForClass(Class managerClass)
|
|
|
|
{
|
|
|
|
// Hackety hack, this partially re-implements RCTBridgeModuleNameForClass
|
|
|
|
// We want to get rid of RCT and RK prefixes, but a lot of JS code still references
|
|
|
|
// view names by prefix. So, while RCTBridgeModuleNameForClass now drops these
|
|
|
|
// prefixes by default, we'll still keep them around here.
|
|
|
|
NSString *name = [managerClass moduleName];
|
|
|
|
if (name.length == 0) {
|
|
|
|
name = NSStringFromClass(managerClass);
|
|
|
|
}
|
|
|
|
if ([name hasPrefix:@"RK"]) {
|
|
|
|
name = [name stringByReplacingCharactersInRange:(NSRange){0, @"RK".length} withString:@"RCT"];
|
|
|
|
}
|
|
|
|
if ([name hasSuffix:@"Manager"]) {
|
|
|
|
name = [name substringToIndex:name.length - @"Manager".length];
|
|
|
|
}
|
|
|
|
|
|
|
|
RCTAssert(name.length, @"Invalid moduleName '%@'", name);
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2015-08-06 22:44:15 +00:00
|
|
|
@end
|