mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-23 03:50:11 +08:00
Added mechanism for directly mapping JS event handlers to blocks
Summary: Currently, the system for mapping JS event handlers to blocks is quite clean on the JS side, but is clunky on the native side. The event property is passed as a boolean, which can then be checked by the native side, and if true, the native side is supposed to send an event via the event dispatcher. This diff adds the facility to declare the property as a block instead. This means that the event side can simply call the block, and it will automatically send the event. Because the blocks for bubbling and direct events are named differently, we can also use this to generate the event registration data and get rid of the arrays of event names. The name of the event is inferred from the property name, which means that the property for an event called "load" must be called `onLoad` or the mapping won't work. This can be optionally remapped to a different property name on the view itself if necessary, e.g. RCT_REMAP_VIEW_PROPERTY(onLoad, loadEventBlock, RCTDirectEventBlock) If you don't want to use this mechanism then for now it is still possible to declare the property as a BOOL instead and use the old mechanism (this approach is now deprecated however, and may eventually be removed altogether).
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -107,6 +107,8 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
|
||||
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS, {
|
||||
nativeOnly: { onChange: true },
|
||||
});
|
||||
|
||||
module.exports = SliderIOS;
|
||||
|
||||
@@ -108,6 +108,8 @@ var styles = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS);
|
||||
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS, {
|
||||
nativeOnly: { onChange: true }
|
||||
});
|
||||
|
||||
module.exports = SwitchIOS;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
14
React/Views/RCTDatePicker.h
Normal file
14
React/Views/RCTDatePicker.h
Normal 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
|
||||
41
React/Views/RCTDatePicker.m
Normal file
41
React/Views/RCTDatePicker.m
Normal 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
|
||||
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTComponent.h"
|
||||
|
||||
@interface RCTSlider : UISlider
|
||||
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
|
||||
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user