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).
This commit is contained in:
Nick Lockwood
2015-09-02 05:58:10 -07:00
parent 4379aa00de
commit 848839858b
46 changed files with 548 additions and 508 deletions

View File

@@ -277,7 +277,9 @@ if (Platform.OS === 'android') {
uiViewClassName: 'RCTMap',
});
} else {
var RCTMap = requireNativeComponent('RCTMap', MapView);
var RCTMap = requireNativeComponent('RCTMap', MapView, {
nativeOnly: {onChange: true, onPress: true}
});
}
module.exports = MapView;

View File

@@ -107,6 +107,8 @@ var styles = StyleSheet.create({
},
});
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS, {
nativeOnly: { onChange: true },
});
module.exports = SliderIOS;

View File

@@ -108,6 +108,8 @@ var styles = StyleSheet.create({
},
});
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS);
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS, {
nativeOnly: { onChange: true }
});
module.exports = SwitchIOS;

View File

@@ -226,7 +226,13 @@ var WebView = React.createClass({
},
});
var RCTWebView = requireNativeComponent('RCTWebView', WebView);
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
nativeOnly: {
onLoadingStart: true,
onLoadingError: true,
onLoadingFinish: true,
},
});
var styles = StyleSheet.create({
container: {

View File

@@ -21,11 +21,11 @@
@interface RCTImageView ()
@property (nonatomic, assign) BOOL onLoadStart;
@property (nonatomic, assign) BOOL onProgress;
@property (nonatomic, assign) BOOL onError;
@property (nonatomic, assign) BOOL onLoad;
@property (nonatomic, assign) BOOL onLoadEnd;
@property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
@property (nonatomic, copy) RCTDirectEventBlock onProgress;
@property (nonatomic, copy) RCTDirectEventBlock onError;
@property (nonatomic, copy) RCTDirectEventBlock onLoad;
@property (nonatomic, copy) RCTDirectEventBlock onLoadEnd;
@end
@@ -116,19 +116,16 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
if (_onLoadStart) {
NSDictionary *event = @{ @"target": self.reactTag };
[_bridge.eventDispatcher sendInputEventWithName:@"loadStart" body:event];
_onLoadStart(nil);
}
RCTImageLoaderProgressBlock progressHandler = nil;
if (_onProgress) {
progressHandler = ^(int64_t loaded, int64_t total) {
NSDictionary *event = @{
@"target": self.reactTag,
_onProgress(@{
@"loaded": @((double)loaded),
@"total": @((double)total),
};
[_bridge.eventDispatcher sendInputEventWithName:@"progress" body:event];
});
};
}
@@ -147,21 +144,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
}
if (error) {
if (_onError) {
NSDictionary *event = @{
@"target": self.reactTag,
@"error": error.localizedDescription,
};
[_bridge.eventDispatcher sendInputEventWithName:@"error" body:event];
_onError(@{ @"error": error.localizedDescription });
}
} else {
if (_onLoad) {
NSDictionary *event = @{ @"target": self.reactTag };
[_bridge.eventDispatcher sendInputEventWithName:@"load" body:event];
_onLoad(nil);
}
}
if (_onLoadEnd) {
NSDictionary *event = @{ @"target": self.reactTag };
[_bridge.eventDispatcher sendInputEventWithName:@"loadEnd" body:event];
_onLoadEnd(nil);
}
}];
} else {

View File

@@ -27,11 +27,11 @@ RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
RCT_EXPORT_VIEW_PROPERTY(src, NSString)
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onProgress, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onError, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onLoad, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
{
if (json) {
@@ -43,15 +43,4 @@ RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
}
}
- (NSArray *)customDirectEventTypes
{
return @[
@"loadStart",
@"progress",
@"error",
@"load",
@"loadEnd",
];
}
@end

View File

@@ -455,6 +455,9 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
[frames addObject:[NSValue valueWithCGRect:shadowView.frame]];
[areNew addObject:@(shadowView.isNewView)];
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
// TODO (#8214142): this can be greatly simplified by sending the layout
// event directly from the shadow thread, which may be better anyway.
id event = (id)kCFNull;
if (shadowView.onLayout) {
event = @{
@@ -1128,67 +1131,68 @@ RCT_EXPORT_METHOD(clearJSResponder)
}];
}
- (NSDictionary *)bubblingEventsConfig
{
NSMutableDictionary *customBubblingEventTypesConfigs = [NSMutableDictionary new];
for (RCTComponentData *componentData in _componentDataByName.allValues) {
RCTViewManager *manager = componentData.manager;
if (RCTClassOverridesInstanceMethod([manager class], @selector(customBubblingEventTypes))) {
NSArray *events = [manager customBubblingEventTypes];
if (RCT_DEBUG) {
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
@"customBubblingEventTypes must return an array, but %@ returned %@",
[manager class], [events class]);
}
for (NSString *eventName in events) {
NSString *topName = RCTNormalizeInputEventName(eventName);
if (!customBubblingEventTypesConfigs[topName]) {
NSString *bubbleName = [topName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
customBubblingEventTypesConfigs[topName] = @{
@"phasedRegistrationNames": @{
@"bubbled": bubbleName,
@"captured": [bubbleName stringByAppendingString:@"Capture"],
}
};
}
}
}
};
return customBubblingEventTypesConfigs;
}
- (NSDictionary *)directEventsConfig
{
NSMutableDictionary *customDirectEventTypes = [NSMutableDictionary new];
for (RCTComponentData *componentData in _componentDataByName.allValues) {
RCTViewManager *manager = componentData.manager;
if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) {
NSArray *events = [manager customDirectEventTypes];
if (RCT_DEBUG) {
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
@"customDirectEventTypes must return an array, but %@ returned %@",
[manager class], [events class]);
}
for (NSString *eventName in events) {
NSString *topName = RCTNormalizeInputEventName(eventName);
if (!customDirectEventTypes[topName]) {
customDirectEventTypes[topName] = @{
@"registrationName": [topName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
};
}
}
}
};
return customDirectEventTypes;
}
- (NSDictionary *)constantsToExport
{
NSMutableDictionary *allJSConstants = [@{
@"customBubblingEventTypes": [self bubblingEventsConfig],
@"customDirectEventTypes": [self directEventsConfig],
NSMutableDictionary *allJSConstants = [NSMutableDictionary new];
NSMutableDictionary *directEvents = [NSMutableDictionary new];
NSMutableDictionary *bubblingEvents = [NSMutableDictionary new];
[_componentDataByName enumerateKeysAndObjectsUsingBlock:
^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
RCTViewManager *manager = componentData.manager;
NSMutableDictionary *constantsNamespace =
[NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
// Add custom constants
// TODO: should these be inherited?
NSDictionary *constants = RCTClassOverridesInstanceMethod([manager class], @selector(constantsToExport)) ? [manager constantsToExport] : nil;
if (constants.count) {
RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
// add an additional 'Constants' namespace for each class
constantsNamespace[@"Constants"] = constants;
}
// Add native props
NSDictionary *viewConfig = [componentData viewConfig];
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);
}
}
allJSConstants[name] = [constantsNamespace copy];
}];
[allJSConstants addEntriesFromDictionary:@{
@"customBubblingEventTypes": bubblingEvents,
@"customDirectEventTypes": directEvents,
@"Dimensions": @{
@"window": @{
@"width": @(RCTScreenSize().width),
@@ -1200,28 +1204,8 @@ RCT_EXPORT_METHOD(clearJSResponder)
@"height": @(RCTScreenSize().height),
},
},
} mutableCopy];
[_componentDataByName enumerateKeysAndObjectsUsingBlock:
^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
RCTViewManager *manager = componentData.manager;
NSMutableDictionary *constantsNamespace =
[NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
// Add custom constants
// TODO: should these be inherited?
NSDictionary *constants = RCTClassOverridesInstanceMethod([manager class], @selector(constantsToExport)) ? [manager constantsToExport] : nil;
if (constants.count) {
RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
// add an additional 'Constants' namespace for each class
constantsNamespace[@"Constants"] = constants;
}
// Add native props
constantsNamespace[@"NativeProps"] = [componentData viewConfig];
allJSConstants[name] = [constantsNamespace copy];
}];
return allJSConstants;
}

View File

@@ -10,6 +10,7 @@
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */; };
131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */; };
131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */; };
133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */; };
13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */; };
13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */; };
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; };
@@ -104,6 +105,8 @@
131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControl.m; sourceTree = "<group>"; };
131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSegmentedControlManager.h; sourceTree = "<group>"; };
131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControlManager.m; sourceTree = "<group>"; };
133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePicker.h; sourceTree = "<group>"; };
133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePicker.m; sourceTree = "<group>"; };
13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationType.h; sourceTree = "<group>"; };
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = "<group>"; };
13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewControllerProtocol.h; sourceTree = "<group>"; };
@@ -345,6 +348,8 @@
13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */,
13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */,
13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */,
133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */,
133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */,
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */,
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */,
14435CE11AAC4AE100FC20F4 /* RCTMap.h */,
@@ -597,6 +602,7 @@
13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */,
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */,
133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */,
14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */,
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,

View File

@@ -9,6 +9,13 @@
#import <CoreGraphics/CoreGraphics.h>
/**
* These block types can be used for mapping input event handlers from JS to view
* properties. Unlike JS method callbacks, these can be called multiple times.
*/
typedef void (^RCTDirectEventBlock)(NSDictionary *body);
typedef void (^RCTBubblingEventBlock)(NSDictionary *body);
/**
* Logical node in a tree of application components. Both `ShadowView` and
* `UIView` conforms to this. Allows us to write utilities that reason about

View File

@@ -13,6 +13,7 @@
#import "RCTBridge.h"
#import "RCTShadowView.h"
#import "RCTUtils.h"
#import "RCTViewManager.h"
typedef void (^RCTPropBlock)(id<RCTComponent> view, id json);
@@ -140,77 +141,95 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
// Build setter block
void (^setterBlock)(id target, id source, id json) = nil;
NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
switch (typeSignature.methodReturnType[0]) {
if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") ||
type == NSSelectorFromString(@"RCTDirectEventBlock:")) {
#define RCT_CASE(_value, _type) \
case _value: { \
_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 source, id json) { \
set(target, setter, json ? convert([RCTConvert class], type, json) : get(source, getter)); \
}; \
break; \
}
// Special case for event handlers
__weak RCTViewManager *weakManager = _manager;
setterBlock = ^(id target, __unused id source, id json) {
__weak id<RCTComponent> weakTarget = target;
((void (*)(id, SEL, id))objc_msgSend)(target, setter, [RCTConvert BOOL:json] ? ^(NSDictionary *body) {
body = [NSMutableDictionary dictionaryWithDictionary:body];
((NSMutableDictionary *)body)[@"target"] = weakTarget.reactTag;
[weakManager.bridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(name) body:body];
} : nil);
};
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)
} else {
case _C_STRUCT_B:
default: {
// Ordinary property handlers
NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
switch (typeSignature.methodReturnType[0]) {
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = type;
typeInvocation.target = [RCTConvert class];
#define RCT_CASE(_value, _type) \
case _value: { \
_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 source, id json) { \
set(target, setter, json ? convert([RCTConvert class], type, json) : get(source, getter)); \
}; \
break; \
}
__block NSInvocation *sourceInvocation = nil;
__block NSInvocation *targetInvocation = nil;
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)
setterBlock = ^(id target, id source, id json) { \
case _C_STRUCT_B:
default: {
// Get value
void *value = malloc(typeSignature.methodReturnLength);
if (json) {
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:value];
} else {
if (!sourceInvocation && source) {
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
sourceInvocation.selector = getter;
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = type;
typeInvocation.target = [RCTConvert class];
__block NSInvocation *sourceInvocation = nil;
__block NSInvocation *targetInvocation = nil;
setterBlock = ^(id target, id source, id json) { \
// Get value
void *value = malloc(typeSignature.methodReturnLength);
if (json) {
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:value];
} else {
if (!sourceInvocation && source) {
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
sourceInvocation.selector = getter;
}
[sourceInvocation invokeWithTarget:source];
[sourceInvocation getReturnValue:value];
}
[sourceInvocation invokeWithTarget:source];
[sourceInvocation getReturnValue:value];
}
// Set value
if (!targetInvocation && target) {
NSMethodSignature *signature = [target methodSignatureForSelector:setter];
targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
targetInvocation.selector = setter;
}
[targetInvocation setArgument:value atIndex:2];
[targetInvocation invokeWithTarget:target];
free(value);
};
break;
// Set value
if (!targetInvocation && target) {
NSMethodSignature *signature = [target methodSignatureForSelector:setter];
targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
targetInvocation.selector = setter;
}
[targetInvocation setArgument:value atIndex:2];
[targetInvocation invokeWithTarget:target];
free(value);
};
break;
}
}
}
@@ -292,9 +311,35 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (NSDictionary *)viewConfig
{
Class managerClass = [_manager class];
NSMutableDictionary *propTypes = [NSMutableDictionary new];
NSMutableArray *directEvents = [NSMutableArray new];
if (RCTClassOverridesInstanceMethod(managerClass, @selector(customDirectEventTypes))) {
NSArray *events = [_manager customDirectEventTypes];
if (RCT_DEBUG) {
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
@"customDirectEventTypes must return an array, but %@ returned %@",
managerClass, [events class]);
}
for (NSString *event in events) {
[directEvents addObject:RCTNormalizeInputEventName(event)];
}
}
NSMutableArray *bubblingEvents = [NSMutableArray new];
if (RCTClassOverridesInstanceMethod(managerClass, @selector(customBubblingEventTypes))) {
NSArray *events = [_manager customBubblingEventTypes];
if (RCT_DEBUG) {
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
@"customBubblingEventTypes must return an array, but %@ returned %@",
managerClass, [events class]);
}
for (NSString *event in events) {
[bubblingEvents addObject:RCTNormalizeInputEventName(event)];
}
}
unsigned int count = 0;
NSMutableDictionary *propTypes = [NSMutableDictionary new];
Method *methods = class_copyMethodList(object_getClass(managerClass), &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methods[i];
@@ -309,13 +354,41 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
RCTLogError(@"Property '%@' of component '%@' redefined from '%@' "
"to '%@'", name, _name, propTypes[name], type);
}
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;
}
}
}
}
free(methods);
return propTypes;
if (RCT_DEBUG) {
for (NSString *event in directEvents) {
if ([bubblingEvents containsObject:event]) {
RCTLogError(@"Component '%@' registered '%@' as both a bubbling event "
"and a direct event", _name, event);
}
}
for (NSString *event in bubblingEvents) {
if ([directEvents containsObject:event]) {
RCTLogError(@"Component '%@' registered '%@' as both a bubbling event "
"and a direct event", _name, event);
}
}
}
return @{
@"propTypes" : propTypes,
@"directEvents" : directEvents,
@"bubblingEvents" : bubblingEvents,
};
}
@end

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@interface RCTDatePicker : UIDatePicker
@end

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDatePicker.h"
#import "RCTUtils.h"
#import "UIView+React.h"
@interface RCTDatePicker ()
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@end
@implementation RCTDatePicker
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
[self addTarget:self action:@selector(didChange)
forControlEvents:UIControlEventValueChanged];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)didChange
{
if (_onChange) {
_onChange(@{ @"timestamp": @(self.date.timeIntervalSince1970 * 1000.0) });
}
}
@end

View File

@@ -10,6 +10,7 @@
#import "RCTDatePickerManager.h"
#import "RCTBridge.h"
#import "RCTDatePicker.h"
#import "RCTEventDispatcher.h"
#import "UIView+React.h"
@@ -30,14 +31,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
// TODO: we crash here if the RCTDatePickerManager is released
// while the UIDatePicker is still sending onChange events. To
// fix this we should maybe subclass UIDatePicker and make it
// be its own event target.
UIDatePicker *picker = [UIDatePicker new];
[picker addTarget:self action:@selector(onChange:)
forControlEvents:UIControlEventValueChanged];
return picker;
return [RCTDatePicker new];
}
RCT_EXPORT_VIEW_PROPERTY(date, NSDate)
@@ -47,15 +41,6 @@ RCT_EXPORT_VIEW_PROPERTY(minuteInterval, NSInteger)
RCT_REMAP_VIEW_PROPERTY(mode, datePickerMode, UIDatePickerMode)
RCT_REMAP_VIEW_PROPERTY(timeZoneOffsetInMinutes, timeZone, NSTimeZone)
- (void)onChange:(UIDatePicker *)sender
{
NSDictionary *event = @{
@"target": sender.reactTag,
@"timestamp": @(sender.date.timeIntervalSince1970 * 1000.0)
};
[self.bridge.eventDispatcher sendInputEventWithName:@"change" body:event];
}
- (NSDictionary *)constantsToExport
{
UIDatePicker *view = [UIDatePicker new];

View File

@@ -11,13 +11,12 @@
#import <UIKit/UIKit.h>
#import "RCTConvert+MapKit.h"
#import "RCTComponent.h"
extern const CLLocationDegrees RCTMapDefaultSpan;
extern const NSTimeInterval RCTMapRegionChangeObserveInterval;
extern const CGFloat RCTMapZoomBoundBuffer;
@class RCTEventDispatcher;
@interface RCTMap: MKMapView
@property (nonatomic, assign) BOOL followUserLocation;
@@ -28,6 +27,9 @@ extern const CGFloat RCTMapZoomBoundBuffer;
@property (nonatomic, strong) NSTimer *regionChangeObserveTimer;
@property (nonatomic, strong) NSMutableArray *annotationIds;
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
- (void)setAnnotations:(RCTPointAnnotationArray *)annotations;
@end

View File

@@ -46,6 +46,8 @@ RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(mapType, MKMapType)
RCT_EXPORT_VIEW_PROPERTY(annotations, RCTPointAnnotationArray)
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
{
[view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
@@ -53,35 +55,27 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
#pragma mark MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
- (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
if (![view.annotation isKindOfClass:[MKUserLocation class]]) {
if (mapView.onPress && [view.annotation isKindOfClass:[RCTPointAnnotation class]]) {
RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation;
NSString *title = view.annotation.title ?: @"";
NSString *subtitle = view.annotation.subtitle ?: @"";
NSDictionary *event = @{
@"target": mapView.reactTag,
@"action": @"annotation-click",
@"annotation": @{
@"id": annotation.identifier,
@"title": title,
@"subtitle": subtitle,
@"latitude": @(annotation.coordinate.latitude),
@"longitude": @(annotation.coordinate.longitude)
}
};
[self.bridge.eventDispatcher sendInputEventWithName:@"press" body:event];
mapView.onPress(@{
@"action": @"annotation-click",
@"annotation": @{
@"id": annotation.identifier,
@"title": annotation.title ?: @"",
@"subtitle": annotation.subtitle ?: @"",
@"latitude": @(annotation.coordinate.latitude),
@"longitude": @(annotation.coordinate.longitude)
}
});
}
}
- (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(RCTPointAnnotation *)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]) {
if (![annotation isKindOfClass:[RCTPointAnnotation class]]) {
return nil;
}
@@ -103,23 +97,20 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
return annotationView;
}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
// Pass to js
RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation;
NSString *side = (control == view.leftCalloutAccessoryView) ? @"left" : @"right";
if (mapView.onPress) {
NSDictionary *event = @{
@"target": mapView.reactTag,
@"side": side,
// Pass to js
RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation;
mapView.onPress(@{
@"side": (control == view.leftCalloutAccessoryView) ? @"left" : @"right",
@"action": @"callout-click",
@"annotationId": annotation.identifier
};
[self.bridge.eventDispatcher sendInputEventWithName:@"press" body:event];
});
}
}
- (void)mapView:(RCTMap *)mapView didUpdateUserLocation:(MKUserLocation *)location
{
if (mapView.followUserLocation) {
@@ -205,24 +196,24 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
- (void)_emitRegionChangeEvent:(RCTMap *)mapView continuous:(BOOL)continuous
{
MKCoordinateRegion region = mapView.region;
if (!CLLocationCoordinate2DIsValid(region.center)) {
return;
}
#define FLUSH_NAN(value) (isnan(value) ? 0 : value)
NSDictionary *event = @{
@"target": mapView.reactTag,
@"continuous": @(continuous),
@"region": @{
@"latitude": @(FLUSH_NAN(region.center.latitude)),
@"longitude": @(FLUSH_NAN(region.center.longitude)),
@"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)),
@"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)),
if (mapView.onChange) {
MKCoordinateRegion region = mapView.region;
if (!CLLocationCoordinate2DIsValid(region.center)) {
return;
}
};
[self.bridge.eventDispatcher sendInputEventWithName:@"change" body:event];
#define FLUSH_NAN(value) (isnan(value) ? 0 : value)
mapView.onChange(@{
@"continuous": @(continuous),
@"region": @{
@"latitude": @(FLUSH_NAN(region.center.latitude)),
@"longitude": @(FLUSH_NAN(region.center.longitude)),
@"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)),
@"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)),
}
});
}
}
@end

View File

@@ -9,6 +9,8 @@
#import <UIKit/UIKit.h>
#import "RCTComponent.h"
@interface RCTNavItem : UIView
@property (nonatomic, copy) NSString *title;
@@ -29,4 +31,7 @@
@property (nonatomic, readonly) UIBarButtonItem *leftButtonItem;
@property (nonatomic, readonly) UIBarButtonItem *rightButtonItem;
@property (nonatomic, copy) RCTBubblingEventBlock onNavLeftButtonTap;
@property (nonatomic, copy) RCTBubblingEventBlock onNavRightButtonTap;
@end

View File

@@ -63,15 +63,18 @@
{
if (!_leftButtonItem) {
if (_leftButtonIcon) {
_leftButtonItem = [[UIBarButtonItem alloc] initWithImage:_leftButtonIcon
style:UIBarButtonItemStylePlain
target:nil
action:nil];
_leftButtonItem =
[[UIBarButtonItem alloc] initWithImage:_leftButtonIcon
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleNavLeftButtonTapped)];
} else if (_leftButtonTitle.length) {
_leftButtonItem = [[UIBarButtonItem alloc] initWithTitle:_leftButtonTitle
style:UIBarButtonItemStylePlain
target:nil
action:nil];
_leftButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_leftButtonTitle
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleNavLeftButtonTapped)];
} else {
_leftButtonItem = nil;
}
@@ -79,6 +82,13 @@
return _leftButtonItem;
}
- (void)handleNavLeftButtonTapped
{
if (_onNavLeftButtonTap) {
_onNavLeftButtonTap(nil);
}
}
- (void)setRightButtonTitle:(NSString *)rightButtonTitle
{
_rightButtonTitle = rightButtonTitle;
@@ -95,15 +105,18 @@
{
if (!_rightButtonItem) {
if (_rightButtonIcon) {
_rightButtonItem = [[UIBarButtonItem alloc] initWithImage:_rightButtonIcon
style:UIBarButtonItemStylePlain
target:nil
action:nil];
_rightButtonItem =
[[UIBarButtonItem alloc] initWithImage:_rightButtonIcon
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleNavRightButtonTapped)];
} else if (_rightButtonTitle.length) {
_rightButtonItem = [[UIBarButtonItem alloc] initWithTitle:_rightButtonTitle
style:UIBarButtonItemStylePlain
target:nil
action:nil];
_rightButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_rightButtonTitle
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleNavRightButtonTapped)];
} else {
_rightButtonItem = nil;
}
@@ -111,4 +124,11 @@
return _rightButtonItem;
}
- (void)handleNavRightButtonTapped
{
if (_onNavRightButtonTap) {
_onNavRightButtonTap(nil);
}
}
@end

View File

@@ -39,4 +39,7 @@ RCT_EXPORT_VIEW_PROPERTY(leftButtonIcon, UIImage)
RCT_EXPORT_VIEW_PROPERTY(rightButtonIcon, UIImage)
RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString)
RCT_EXPORT_VIEW_PROPERTY(onNavLeftButtonTap, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onNavRightButtonTap, RCTBubblingEventBlock)
@end

View File

@@ -193,6 +193,9 @@ NSInteger kNeverProgressed = -10000;
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate>
@property (nonatomic, copy) RCTDirectEventBlock onNavigationProgress;
@property (nonatomic, copy) RCTBubblingEventBlock onNavigationComplete;
@property (nonatomic, assign) NSInteger previousRequestedTopOfStack;
// Previous views are only mainted in order to detect incorrect
@@ -308,12 +311,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
return;
}
_mostRecentProgress = nextProgress;
[_bridge.eventDispatcher sendInputEventWithName:@"navigationProgress" body:@{
@"fromIndex": @(_currentlyTransitioningFrom),
@"toIndex": @(_currentlyTransitioningTo),
@"progress": @(nextProgress),
@"target": self.reactTag
}];
if (_onNavigationProgress) {
_onNavigationProgress(@{
@"fromIndex": @(_currentlyTransitioningFrom),
@"toIndex": @(_currentlyTransitioningTo),
@"progress": @(nextProgress),
});
}
}
}
@@ -416,10 +420,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)handleTopOfStackChanged
{
[_bridge.eventDispatcher sendInputEventWithName:@"navigationComplete" body:@{
@"target":self.reactTag,
@"stackLength":@(_navigationController.viewControllers.count)
}];
if (_onNavigationComplete) {
_onNavigationComplete(@{
@"stackLength":@(_navigationController.viewControllers.count)
});
}
}
- (void)dispatchFakeScrollEvent
@@ -502,7 +507,7 @@ BOOL jsGettingtooSlow =
if (jsGettingAhead) {
if (reactPushOne) {
UIView *lastView = _currentViews.lastObject;
RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView eventDispatcher:_bridge.eventDispatcher];
RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView];
vc.navigationListener = self;
_numberOfViewControllerMovesToIgnore = 1;
[_navigationController pushViewController:vc animated:(currentReactCount > 1)];

View File

@@ -25,22 +25,8 @@ RCT_EXPORT_MODULE()
}
RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack, NSInteger)
- (NSArray *)customBubblingEventTypes
{
return @[
@"navigationComplete",
@"navLeftButtonTap",
@"navRightButtonTap",
];
}
- (NSArray *)customDirectEventTypes
{
return @[
@"navigationProgress",
];
}
RCT_EXPORT_VIEW_PROPERTY(onNavigationProgress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onNavigationComplete, RCTBubblingEventBlock)
// TODO: remove error callbacks
RCT_EXPORT_METHOD(requestSchedulingJavaScriptNavigation:(nonnull NSNumber *)reactTag

View File

@@ -9,13 +9,6 @@
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@interface RCTPicker : UIPickerView
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@property (nonatomic, copy) NSArray *items;
@property (nonatomic, assign) NSInteger selectedIndex;
@end

View File

@@ -9,37 +9,28 @@
#import "RCTPicker.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTUtils.h"
#import "UIView+React.h"
const NSInteger UNINITIALIZED_INDEX = -1;
@interface RCTPicker() <UIPickerViewDataSource, UIPickerViewDelegate>
@property (nonatomic, copy) NSArray *items;
@property (nonatomic, assign) NSInteger selectedIndex;
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@end
@implementation RCTPicker
{
RCTEventDispatcher *_eventDispatcher;
NSArray *_items;
NSInteger _selectedIndex;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
- (instancetype)initWithFrame:(CGRect)frame
{
RCTAssertParam(eventDispatcher);
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
_selectedIndex = UNINITIALIZED_INDEX;
if ((self = [super initWithFrame:frame])) {
_selectedIndex = NSNotFound;
self.delegate = self;
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)setItems:(NSArray *)items
@@ -51,7 +42,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)setSelectedIndex:(NSInteger)selectedIndex
{
if (_selectedIndex != selectedIndex) {
BOOL animated = _selectedIndex != UNINITIALIZED_INDEX; // Don't animate the initial value
BOOL animated = _selectedIndex != NSNotFound; // Don't animate the initial value
_selectedIndex = selectedIndex;
dispatch_async(dispatch_get_main_queue(), ^{
[self selectRow:selectedIndex inComponent:0 animated:animated];
@@ -94,13 +85,12 @@ numberOfRowsInComponent:(__unused NSInteger)component
didSelectRow:(NSInteger)row inComponent:(__unused NSInteger)component
{
_selectedIndex = row;
NSDictionary *event = @{
@"target": self.reactTag,
@"newIndex": @(row),
@"newValue": [self valueForRow:row]
};
[_eventDispatcher sendInputEventWithName:@"change" body:event];
if (_onChange) {
_onChange(@{
@"newIndex": @(row),
@"newValue": [self valueForRow:row]
});
}
}
@end

View File

@@ -19,7 +19,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTPicker alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
return [RCTPicker new];
}
RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray)

View File

@@ -9,13 +9,12 @@
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
#import "RCTComponent.h"
@interface RCTSegmentedControl : UISegmentedControl
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@property (nonatomic, copy) NSArray *values;
@property (nonatomic, assign) NSInteger selectedIndex;
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@end

View File

@@ -14,17 +14,13 @@
#import "UIView+React.h"
@implementation RCTSegmentedControl
{
RCTEventDispatcher *_eventDispatcher;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
if ((self = [super initWithFrame:frame])) {
_selectedIndex = self.selectedSegmentIndex;
[self addTarget:self action:@selector(onChange:)
forControlEvents:UIControlEventValueChanged];
[self addTarget:self action:@selector(didChange)
forControlEvents:UIControlEventValueChanged];
}
return self;
}
@@ -45,14 +41,14 @@
super.selectedSegmentIndex = selectedIndex;
}
- (void)onChange:(UISegmentedControl *)sender
- (void)didChange
{
NSDictionary *event = @{
@"target": self.reactTag,
@"value": [self titleForSegmentAtIndex:sender.selectedSegmentIndex],
@"selectedSegmentIndex": @(sender.selectedSegmentIndex)
};
[_eventDispatcher sendInputEventWithName:@"change" body:event];
if (_onChange) {
_onChange(@{
@"value": [self titleForSegmentAtIndex:_selectedIndex],
@"selectedSegmentIndex": @(_selectedIndex)
});
}
}
@end

View File

@@ -19,7 +19,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTSegmentedControl alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
return [RCTSegmentedControl new];
}
RCT_EXPORT_VIEW_PROPERTY(values, NSStringArray)
@@ -27,6 +27,7 @@ RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(momentary, BOOL)
RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
- (NSDictionary *)constantsToExport
{

View File

@@ -39,7 +39,7 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry);
@property (nonatomic, copy) NSString *viewName;
@property (nonatomic, strong) UIColor *backgroundColor; // Used to propagate to children
@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle;
@property (nonatomic, assign) BOOL onLayout;
@property (nonatomic, copy) RCTDirectEventBlock onLayout;
/**
* isNewView - Used to track the first time the view is introduced into the hierarchy. It is initialized YES, then is

View File

@@ -9,6 +9,10 @@
#import <UIKit/UIKit.h>
#import "RCTComponent.h"
@interface RCTSlider : UISlider
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@end

View File

@@ -27,25 +27,24 @@ RCT_EXPORT_MODULE()
return slider;
}
static void RCTSendSliderEvent(RCTSliderManager *self, UISlider *sender, BOOL continuous)
static void RCTSendSliderEvent(RCTSlider *sender, BOOL continuous)
{
NSDictionary *event = @{
@"target": sender.reactTag,
@"value": @(sender.value),
@"continuous": @(continuous),
};
[self.bridge.eventDispatcher sendInputEventWithName:@"change" body:event];
if (sender.onChange) {
sender.onChange(@{
@"value": @(sender.value),
@"continuous": @(continuous),
});
}
}
- (void)sliderValueChanged:(UISlider *)sender
- (void)sliderValueChanged:(RCTSlider *)sender
{
RCTSendSliderEvent(self, sender, YES);
RCTSendSliderEvent(sender, YES);
}
- (void)sliderTouchEnd:(UISlider *)sender
- (void)sliderTouchEnd:(RCTSlider *)sender
{
RCTSendSliderEvent(self, sender, NO);
RCTSendSliderEvent(sender, NO);
}
RCT_EXPORT_VIEW_PROPERTY(value, float);
@@ -53,5 +52,6 @@ RCT_EXPORT_VIEW_PROPERTY(minimumValue, float);
RCT_EXPORT_VIEW_PROPERTY(maximumValue, float);
RCT_EXPORT_VIEW_PROPERTY(minimumTrackTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(maximumTrackTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock);
@end

View File

@@ -10,8 +10,11 @@
#import <UIKit/UIKit.h>
#import "RCTComponent.h"
@interface RCTSwitch : UISwitch
@property (nonatomic, assign) BOOL wasOn;
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@end

View File

@@ -30,11 +30,9 @@ RCT_EXPORT_MODULE()
- (void)onChange:(RCTSwitch *)sender
{
if (sender.wasOn != sender.on) {
[self.bridge.eventDispatcher sendInputEventWithName:@"change" body:@{
@"target": sender.reactTag,
@"value": @(sender.on)
}];
if (sender.onChange) {
sender.onChange(@{ @"value": @(sender.on) });
}
sender.wasOn = sender.on;
}
}
@@ -43,6 +41,7 @@ RCT_EXPORT_VIEW_PROPERTY(onTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(thumbTintColor, UIColor);
RCT_REMAP_VIEW_PROPERTY(value, on, BOOL);
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock);
RCT_CUSTOM_VIEW_PROPERTY(disabled, BOOL, RCTSwitch)
{
if (json) {

View File

@@ -9,14 +9,10 @@
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@interface RCTTabBar : UIView
@property (nonatomic, strong) UIColor *tintColor;
@property (nonatomic, strong) UIColor *barTintColor;
@property (nonatomic, assign) BOOL translucent;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@end

View File

@@ -25,17 +25,13 @@
@implementation RCTTabBar
{
BOOL _tabsChanged;
RCTEventDispatcher *_eventDispatcher;
UITabBarController *_tabController;
NSMutableArray *_tabViews;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
- (instancetype)initWithFrame:(CGRect)frame
{
RCTAssertParam(eventDispatcher);
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
if ((self = [super initWithFrame:frame])) {
_tabViews = [NSMutableArray new];
_tabController = [UITabBarController new];
_tabController.delegate = self;
@@ -44,7 +40,6 @@
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (UIViewController *)reactViewController
@@ -100,8 +95,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
for (RCTTabBarItem *tab in [self reactSubviews]) {
UIViewController *controller = tab.reactViewController;
if (!controller) {
controller = [[RCTWrapperViewController alloc] initWithContentView:tab
eventDispatcher:_eventDispatcher];
controller = [[RCTWrapperViewController alloc] initWithContentView:tab];
}
[viewControllers addObject:controller];
}
@@ -154,7 +148,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
{
NSUInteger index = [tabBarController.viewControllers indexOfObject:viewController];
RCTTabBarItem *tab = [self reactSubviews][index];
[_eventDispatcher sendInputEventWithName:@"press" body:@{@"target": tab.reactTag}];
if (tab.onPress) tab.onPress(nil);
return NO;
}

View File

@@ -9,10 +9,13 @@
#import <UIKit/UIKit.h>
#import "RCTComponent.h"
@interface RCTTabBarItem : UIView
@property (nonatomic, copy) id icon;
@property (nonatomic, assign, getter=isSelected) BOOL selected;
@property (nonatomic, readonly) UITabBarItem *barItem;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@end

View File

@@ -21,10 +21,11 @@ RCT_EXPORT_MODULE()
return [RCTTabBarItem new];
}
RCT_EXPORT_VIEW_PROPERTY(selected, BOOL);
RCT_EXPORT_VIEW_PROPERTY(icon, id);
RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage);
RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString);
RCT_EXPORT_VIEW_PROPERTY(selected, BOOL)
RCT_EXPORT_VIEW_PROPERTY(icon, id)
RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage)
RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem)
{
view.barItem.title = json ? [RCTConvert NSString:json] : defaultView.barItem.title;

View File

@@ -18,7 +18,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTTabBar alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
return [RCTTabBar new];
}
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)

View File

@@ -11,17 +11,20 @@
#import <UIKit/UIKit.h>
#import "RCTComponent.h"
#import "RCTPointerEvents.h"
@protocol RCTAutoInsetsProtocol;
@class RCTView;
typedef void (^RCTViewEventHandler)(RCTView *view);
@interface RCTView : UIView
@property (nonatomic, copy) RCTViewEventHandler accessibilityTapHandler;
@property (nonatomic, copy) RCTViewEventHandler magicTapHandler;
/**
* Accessibility event handlers
*/
@property (nonatomic, copy) RCTDirectEventBlock onAccessibilityTap;
@property (nonatomic, copy) RCTDirectEventBlock onMagicTap;
/**
* Used to control how touch events are processed.

View File

@@ -167,8 +167,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
- (BOOL)accessibilityActivate
{
if (self.accessibilityTapHandler) {
self.accessibilityTapHandler(self);
if (_onAccessibilityTap) {
_onAccessibilityTap(nil);
return YES;
} else {
return NO;
@@ -177,8 +177,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
- (BOOL)accessibilityPerformMagicTap
{
if (self.magicTapHandler) {
self.magicTapHandler(self);
if (_onMagicTap) {
_onMagicTap(nil);
return YES;
} else {
return NO;

View File

@@ -11,10 +11,12 @@
#import "RCTBridgeModule.h"
#import "RCTConvert.h"
#import "RCTComponent.h"
#import "RCTDefines.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
@class RCTBridge;
@class RCTEventDispatcher;
@class RCTShadowView;
@class RCTSparseArray;
@class RCTUIManager;
@@ -37,7 +39,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
* return a fresh instance each time. The view module MUST NOT cache the returned
* view and return the same instance for subsequent calls.
*/
- (UIView *)view;
- (UIView<RCTComponent> *)view;
/**
* This method instantiates a native view using the props passed into the component.
@@ -57,6 +59,8 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
- (RCTShadowView *)shadowView;
/**
* DEPRECATED: declare properties of type RCTBubblingEventBlock instead
*
* Returns an array of names of events that can be sent by native views. This
* should return bubbling, directly-dispatched event types. The event name
* should not include a prefix such as 'on' or 'top', as this will be applied
@@ -69,6 +73,8 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
- (NSArray *)customBubblingEventTypes;
/**
* DEPRECATED: declare properties of type RCTDirectEventBlock instead
*
* Returns an array of names of events that can be sent by native views. This
* should return non-bubbling, directly-dispatched event types. The event name
* should not include a prefix such as 'on' or 'top', as this will be applied

View File

@@ -54,7 +54,7 @@ RCT_EXPORT_MODULE()
return _bridge.uiManager.methodQueue;
}
- (UIView *)viewWithProps:(NSDictionary *)props
- (UIView *)viewWithProps:(__unused NSDictionary *)props
{
return [self view];
}
@@ -76,7 +76,6 @@ RCT_EXPORT_MODULE()
// Generic events
@"press",
@"change",
@"change",
@"focus",
@"blur",
@"submitEditing",
@@ -92,11 +91,7 @@ RCT_EXPORT_MODULE()
- (NSArray *)customDirectEventTypes
{
return @[
@"layout",
@"accessibilityTap",
@"magicTap",
];
return @[];
}
- (NSDictionary *)constantsToExport
@@ -195,27 +190,8 @@ RCT_CUSTOM_VIEW_PROPERTY(borderWidth, CGFloat, RCTView)
view.layer.borderWidth = json ? [RCTConvert CGFloat:json] : defaultView.layer.borderWidth;
}
}
RCT_CUSTOM_VIEW_PROPERTY(onAccessibilityTap, BOOL, __unused RCTView)
{
view.accessibilityTapHandler = [self eventHandlerWithName:@"accessibilityTap" json:json];
}
RCT_CUSTOM_VIEW_PROPERTY(onMagicTap, BOOL, __unused RCTView)
{
view.magicTapHandler = [self eventHandlerWithName:@"magicTap" json:json];
}
- (RCTViewEventHandler)eventHandlerWithName:(NSString *)eventName json:(id)json
{
RCTViewEventHandler handler = nil;
if ([RCTConvert BOOL:json]) {
__weak RCTViewManager *weakSelf = self;
handler = ^(RCTView *tappedView) {
NSDictionary *body = @{ @"target": tappedView.reactTag };
[weakSelf.bridge.eventDispatcher sendInputEventWithName:eventName body:body];
};
}
return handler;
}
RCT_EXPORT_VIEW_PROPERTY(onAccessibilityTap, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMagicTap, RCTDirectEventBlock)
#define RCT_VIEW_BORDER_PROPERTY(SIDE) \
RCT_CUSTOM_VIEW_PROPERTY(border##SIDE##Width, CGFloat, RCTView) \
@@ -291,6 +267,6 @@ RCT_EXPORT_SHADOW_PROPERTY(alignItems, css_align_t)
RCT_EXPORT_SHADOW_PROPERTY(alignSelf, css_align_t)
RCT_EXPORT_SHADOW_PROPERTY(position, css_position_type_t)
RCT_EXPORT_SHADOW_PROPERTY(onLayout, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(onLayout, RCTDirectEventBlock)
@end

View File

@@ -17,8 +17,6 @@
*/
extern NSString *const RCTJSNavigationScheme;
@class RCTEventDispatcher;
@interface RCTWebView : RCTView
@property (nonatomic, strong) NSURL *URL;
@@ -26,8 +24,6 @@ extern NSString *const RCTJSNavigationScheme;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, copy) NSString *injectedJavaScript;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
- (void)goForward;
- (void)goBack;
- (void)reload;

View File

@@ -22,24 +22,24 @@ NSString *const RCTJSNavigationScheme = @"react-js-navigation";
@interface RCTWebView () <UIWebViewDelegate, RCTAutoInsetsProtocol>
@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
@end
@implementation RCTWebView
{
RCTEventDispatcher *_eventDispatcher;
UIWebView *_webView;
NSString *_injectedJavaScript;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
- (instancetype)initWithFrame:(CGRect)frame
{
RCTAssertParam(eventDispatcher);
if ((self = [super initWithFrame:CGRectZero])) {
if ((self = [super initWithFrame:frame])) {
super.backgroundColor = [UIColor clearColor];
_automaticallyAdjustContentInsets = YES;
_contentInset = UIEdgeInsetsZero;
_eventDispatcher = eventDispatcher;
_webView = [[UIWebView alloc] initWithFrame:self.bounds];
_webView.delegate = self;
[self addSubview:_webView];
@@ -47,7 +47,6 @@ NSString *const RCTJSNavigationScheme = @"react-js-navigation";
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)goForward
@@ -123,13 +122,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (NSMutableDictionary *)baseEvent
{
NSURL *url = _webView.request.URL;
NSString *title = [_webView stringByEvaluatingJavaScriptFromString:@"document.title"];
NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary: @{
@"target": self.reactTag,
@"url": url ? url.absoluteString : @"",
@"url": _webView.request.URL.absoluteString ?: @"",
@"loading" : @(_webView.loading),
@"title": title,
@"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"],
@"canGoBack": @(_webView.canGoBack),
@"canGoForward" : @(_webView.canGoForward),
}];
@@ -139,19 +135,20 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
#pragma mark - UIWebViewDelegate methods
- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
// We have this check to filter out iframe requests and whatnot
BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
if (isTopFrame) {
NSMutableDictionary *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"url": (request.URL).absoluteString,
@"navigationType": @(navigationType)
}];
[_eventDispatcher sendInputEventWithName:@"loadingStart" body:event];
if (_onLoadingStart) {
// We have this check to filter out iframe requests and whatnot
BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
if (isTopFrame) {
NSMutableDictionary *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"url": (request.URL).absoluteString,
@"navigationType": @(navigationType)
}];
_onLoadingStart(event);
}
}
// JS Navigation handler
@@ -160,21 +157,24 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
// NSURLErrorCancelled is reported when a page has a redirect OR if you load
// a new URL in the WebView before the previous one came back. We can just
// ignore these since they aren't real errors.
// http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
return;
}
if (_onLoadingError) {
NSMutableDictionary *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"domain": error.domain,
@"code": @(error.code),
@"description": error.localizedDescription,
}];
[_eventDispatcher sendInputEventWithName:@"loadingError" body:event];
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
// NSURLErrorCancelled is reported when a page has a redirect OR if you load
// a new URL in the WebView before the previous one came back. We can just
// ignore these since they aren't real errors.
// http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
return;
}
NSMutableDictionary *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"domain": error.domain,
@"code": @(error.code),
@"description": error.localizedDescription,
}];
_onLoadingError(event);
}
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
@@ -184,8 +184,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
}
// we only need the final 'finishLoad' call so only fire the event when we're actually done loading.
if (!webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) {
[_eventDispatcher sendInputEventWithName:@"loadingFinish" body:[self baseEvent]];
if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) {
_onLoadingFinish([self baseEvent]);
}
}

View File

@@ -20,7 +20,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTWebView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
return [RCTWebView new];
}
RCT_REMAP_VIEW_PROPERTY(url, URL, NSURL);
@@ -31,15 +31,9 @@ RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL);
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString);
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets);
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL);
- (NSArray *)customDirectEventTypes
{
return @[
@"loadingStart",
@"loadingFinish",
@"loadingError",
];
}
RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock);
- (NSDictionary *)constantsToExport
{
@@ -80,7 +74,6 @@ RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag)
}];
}
RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {

View File

@@ -11,7 +11,6 @@
#import "RCTViewControllerProtocol.h"
@class RCTEventDispatcher;
@class RCTNavItem;
@class RCTWrapperViewController;
@@ -24,11 +23,8 @@ didMoveToNavigationController:(UINavigationController *)navigationController;
@interface RCTWrapperViewController : UIViewController <RCTViewControllerProtocol>
- (instancetype)initWithContentView:(UIView *)contentView
eventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNavItem:(RCTNavItem *)navItem
eventDispatcher:(RCTEventDispatcher *)eventDispatcher;
- (instancetype)initWithContentView:(UIView *)contentView NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNavItem:(RCTNavItem *)navItem;
@property (nonatomic, weak) id<RCTWrapperViewControllerNavigationListener> navigationListener;
@property (nonatomic, strong) RCTNavItem *navItem;

View File

@@ -21,7 +21,6 @@
{
UIView *_wrapperView;
UIView *_contentView;
RCTEventDispatcher *_eventDispatcher;
CGFloat _previousTopLayout;
CGFloat _previousBottomLayout;
}
@@ -30,23 +29,19 @@
@synthesize currentBottomLayoutGuide = _currentBottomLayoutGuide;
- (instancetype)initWithContentView:(UIView *)contentView
eventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
RCTAssertParam(contentView);
RCTAssertParam(eventDispatcher);
if ((self = [super initWithNibName:nil bundle:nil])) {
_contentView = contentView;
_eventDispatcher = eventDispatcher;
self.automaticallyAdjustsScrollViewInsets = NO;
}
return self;
}
- (instancetype)initWithNavItem:(RCTNavItem *)navItem
eventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [self initWithContentView:navItem eventDispatcher:eventDispatcher])) {
if ((self = [self initWithContentView:navItem])) {
_navItem = navItem;
}
return self;
@@ -101,14 +96,8 @@ static UIView *RCTFindNavBarShadowViewInView(UIView *view)
UINavigationItem *item = self.navigationItem;
item.title = _navItem.title;
item.backBarButtonItem = _navItem.backButtonItem;
if ((item.leftBarButtonItem = _navItem.leftButtonItem)) {
item.leftBarButtonItem.target = self;
item.leftBarButtonItem.action = @selector(handleNavLeftButtonTapped);
}
if ((item.rightBarButtonItem = _navItem.rightButtonItem)) {
item.rightBarButtonItem.target = self;
item.rightBarButtonItem.action = @selector(handleNavRightButtonTapped);
}
item.leftBarButtonItem = _navItem.leftButtonItem;
item.rightBarButtonItem = _navItem.rightButtonItem;
}
}
@@ -122,18 +111,6 @@ static UIView *RCTFindNavBarShadowViewInView(UIView *view)
self.view = _wrapperView;
}
- (void)handleNavLeftButtonTapped
{
[_eventDispatcher sendInputEventWithName:@"navLeftButtonTap"
body:@{@"target":_navItem.reactTag}];
}
- (void)handleNavRightButtonTapped
{
[_eventDispatcher sendInputEventWithName:@"navRightButtonTap"
body:@{@"target":_navItem.reactTag}];
}
- (void)didMoveToParentViewController:(UIViewController *)parent
{
// There's no clear setter for navigation controllers, but did move to parent

View File

@@ -23,7 +23,7 @@
- (void)setReactTag:(NSNumber *)reactTag
{
objc_setAssociatedObject(self, @selector(reactTag), reactTag, OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, @selector(reactTag), reactTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isReactRootView