mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-24 04:16:00 +08:00
Initial commit
This commit is contained in:
36
ReactKit/Base/RCTAssert.h
Normal file
36
ReactKit/Base/RCTAssert.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#define RCTErrorDomain @"RCTErrorDomain"
|
||||
|
||||
#define RCTAssert(condition, message, ...) _RCTAssert(condition, message, ##__VA_ARGS__)
|
||||
#define RCTCAssert(condition, message, ...) _RCTCAssert(condition, message, ##__VA_ARGS__)
|
||||
|
||||
typedef void (^RCTAssertFunction)(BOOL condition, NSString *message, ...);
|
||||
|
||||
extern RCTAssertFunction RCTInjectedAssertFunction;
|
||||
extern RCTAssertFunction RCTInjectedCAssertFunction;
|
||||
|
||||
void RCTInjectAssertFunctions(RCTAssertFunction assertFunction, RCTAssertFunction cAssertFunction);
|
||||
|
||||
#define _RCTAssert(condition, message, ...) \
|
||||
do { \
|
||||
if (RCTInjectedAssertFunction) { \
|
||||
RCTInjectedAssertFunction(condition, message, ##__VA_ARGS__); \
|
||||
} else { \
|
||||
NSAssert(condition, message, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define _RCTCAssert(condition, message, ...) \
|
||||
do { \
|
||||
if (RCTInjectedCAssertFunction) { \
|
||||
RCTInjectedCAssertFunction(condition, message, ##__VA_ARGS__); \
|
||||
} else { \
|
||||
NSCAssert(condition, message, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define RCTAssertMainThread() RCTAssert([NSThread isMainThread], @"This method must be called on the main thread");
|
||||
#define RCTCAssertMainThread() RCTCAssert([NSThread isMainThread], @"This function must be called on the main thread");
|
||||
12
ReactKit/Base/RCTAssert.m
Normal file
12
ReactKit/Base/RCTAssert.m
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTAssert.h"
|
||||
|
||||
RCTAssertFunction RCTInjectedAssertFunction = nil;
|
||||
RCTAssertFunction RCTInjectedCAssertFunction = nil;
|
||||
|
||||
void RCTInjectAssertFunctions(RCTAssertFunction assertFunction, RCTAssertFunction cAssertFunction)
|
||||
{
|
||||
RCTInjectedAssertFunction = assertFunction;
|
||||
RCTInjectedCAssertFunction = cAssertFunction;
|
||||
}
|
||||
13
ReactKit/Base/RCTAutoInsetsProtocol.h
Normal file
13
ReactKit/Base/RCTAutoInsetsProtocol.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
* Defines a View that wants to support auto insets adjustment
|
||||
*/
|
||||
@protocol RCTAutoInsetsProtocol
|
||||
|
||||
@property (nonatomic, assign, readwrite) UIEdgeInsets contentInset;
|
||||
@property (nonatomic, assign, readwrite) BOOL automaticallyAdjustContentInsets;
|
||||
|
||||
@end
|
||||
85
ReactKit/Base/RCTBridge.h
Normal file
85
ReactKit/Base/RCTBridge.h
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTExport.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
|
||||
@protocol RCTNativeModule;
|
||||
|
||||
@class RCTUIManager;
|
||||
@class RCTJavaScriptEventDispatcher;
|
||||
|
||||
/**
|
||||
* Functions are the one thing that aren't automatically converted to OBJC
|
||||
* blocks, according to this revert: http://trac.webkit.org/changeset/144489
|
||||
* They must be expressed as `JSValue`s.
|
||||
*
|
||||
* But storing callbacks causes reference cycles!
|
||||
* http://stackoverflow.com/questions/19202248/how-can-i-use-jsmanagedvalue-to-avoid-a-reference-cycle-without-the-jsvalue-gett
|
||||
* We'll live with the leak for now, but need to clean this up asap:
|
||||
* Passing a reference to the `context` to the bridge would make it easy to
|
||||
* execute JS. We can add `JSManagedValue`s to protect against this. The same
|
||||
* needs to be done in `RCTTiming` and friends.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Must be kept in sync with `MessageQueue.js`.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
|
||||
RCTBridgeFieldRequestModuleIDs = 0,
|
||||
RCTBridgeFieldMethodIDs,
|
||||
RCTBridgeFieldParamss,
|
||||
RCTBridgeFieldResponseCBIDs,
|
||||
RCTBridgeFieldResponseReturnValues,
|
||||
RCTBridgeFieldFlushDateMillis
|
||||
};
|
||||
|
||||
/**
|
||||
* Utilities for constructing common response objects. When sending a
|
||||
* systemError back to JS, it's important to describe whether or not it was a
|
||||
* system error, or API usage error. System errors should never happen and are
|
||||
* therefore logged using `RCTLogError()`. API usage errors are expected if the
|
||||
* API is misused and will therefore not be logged using `RCTLogError()`. The JS
|
||||
* application code is expected to handle them. Regardless of type, each error
|
||||
* should be logged at most once.
|
||||
*/
|
||||
static inline NSDictionary *RCTSystemErrorObject(NSString *msg)
|
||||
{
|
||||
return @{@"systemError": msg ?: @""};
|
||||
}
|
||||
|
||||
static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
|
||||
{
|
||||
return @{@"apiError": msg ?: @""};
|
||||
}
|
||||
|
||||
/**
|
||||
* Async batched bridge used to communicate with `RCTJavaScriptAppEngine`.
|
||||
*/
|
||||
@interface RCTBridge : NSObject <RCTInvalidating>
|
||||
|
||||
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
|
||||
shadowQueue:(dispatch_queue_t)shadowQueue
|
||||
javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig;
|
||||
|
||||
- (void)enqueueJSCall:(NSUInteger)moduleID methodID:(NSUInteger)methodID args:(NSArray *)args;
|
||||
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
- (void)enqueueUpdateTimers;
|
||||
|
||||
@property (nonatomic, readonly) RCTUIManager *uiManager;
|
||||
@property (nonatomic, readonly) RCTJavaScriptEventDispatcher *eventDispatcher;
|
||||
|
||||
// For use in implementing delegates, which may need to queue responses.
|
||||
- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)callbackID;
|
||||
|
||||
/**
|
||||
* Global logging function will print to both xcode and js debugger consoles.
|
||||
*
|
||||
* NOTE: Use via RCTLog* macros defined in RCTLog.h
|
||||
* TODO (#5906496): should log function be exposed here, or could it be a module?
|
||||
*/
|
||||
+ (void)log:(NSArray *)objects level:(NSString *)level;
|
||||
|
||||
+ (BOOL)hasValidJSExecutor;
|
||||
|
||||
@end
|
||||
515
ReactKit/Base/RCTBridge.m
Normal file
515
ReactKit/Base/RCTBridge.m
Normal file
@@ -0,0 +1,515 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTBridge.h"
|
||||
|
||||
#import <objc/message.h>
|
||||
|
||||
#import "RCTModuleMethod.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTJavaScriptEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTModuleIDs.h"
|
||||
#import "RCTTiming.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
NSString *RCTModuleName(Class moduleClass)
|
||||
{
|
||||
if ([moduleClass respondsToSelector:@selector(moduleName)]) {
|
||||
|
||||
return [moduleClass moduleName];
|
||||
|
||||
} else {
|
||||
|
||||
// Default implementation, works in most cases
|
||||
NSString *className = NSStringFromClass(moduleClass);
|
||||
|
||||
// TODO: be more consistent with naming so that this check isn't needed
|
||||
if ([moduleClass conformsToProtocol:@protocol(RCTNativeViewModule)]) {
|
||||
if ([className hasPrefix:@"RCTUI"]) {
|
||||
className = [className substringFromIndex:@"RCT".length];
|
||||
}
|
||||
if ([className hasSuffix:@"Manager"]) {
|
||||
className = [className substringToIndex:className.length - @"Manager".length];
|
||||
}
|
||||
}
|
||||
return className;
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary *RCTNativeModuleClasses(void)
|
||||
{
|
||||
static NSMutableDictionary *modules;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
modules = [NSMutableDictionary dictionary];
|
||||
|
||||
unsigned int classCount;
|
||||
Class *classes = objc_copyClassList(&classCount);
|
||||
for (unsigned int i = 0; i < classCount; i++) {
|
||||
|
||||
Class cls = classes[i];
|
||||
|
||||
if (!class_getSuperclass(cls)) {
|
||||
// Class has no superclass - it's probably something weird
|
||||
continue;
|
||||
}
|
||||
|
||||
if (![cls conformsToProtocol:@protocol(RCTNativeModule)]) {
|
||||
// Not an RCTNativeModule
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get module name
|
||||
NSString *moduleName = RCTModuleName(cls);
|
||||
|
||||
// Check module name is unique
|
||||
id existingClass = modules[moduleName];
|
||||
RCTCAssert(existingClass == Nil, @"Attempted to register RCTNativeModule class %@ for the name '%@', but name was already registered by class %@", cls, moduleName, existingClass);
|
||||
modules[moduleName] = cls;
|
||||
}
|
||||
|
||||
free(classes);
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
@implementation RCTBridge
|
||||
{
|
||||
NSMutableDictionary *_moduleInstances;
|
||||
NSDictionary *_javaScriptModulesConfig;
|
||||
dispatch_queue_t _shadowQueue;
|
||||
RCTTiming *_timing;
|
||||
id<RCTJavaScriptExecutor> _javaScriptExecutor;
|
||||
}
|
||||
|
||||
static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
|
||||
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
|
||||
shadowQueue:(dispatch_queue_t)shadowQueue
|
||||
javaScriptModulesConfig:(NSDictionary *)javaScriptModulesConfig
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_javaScriptExecutor = javaScriptExecutor;
|
||||
_latestJSExecutor = _javaScriptExecutor;
|
||||
_shadowQueue = shadowQueue;
|
||||
_eventDispatcher = [[RCTJavaScriptEventDispatcher alloc] initWithBridge:self];
|
||||
|
||||
_moduleInstances = [[NSMutableDictionary alloc] init];
|
||||
|
||||
// TODO (#5906496): Remove special case
|
||||
_timing = [[RCTTiming alloc] initWithBridge:self];
|
||||
_javaScriptModulesConfig = javaScriptModulesConfig;
|
||||
_moduleInstances[RCTModuleName([RCTTiming class])] = _timing;
|
||||
|
||||
// TODO (#5906496): Remove special case
|
||||
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
|
||||
[RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
|
||||
if ([moduleClass conformsToProtocol:@protocol(RCTNativeViewModule)]) {
|
||||
viewManagers[moduleName] = [[moduleClass alloc] init];
|
||||
}
|
||||
}];
|
||||
_uiManager = [[RCTUIManager alloc] initWithShadowQueue:_shadowQueue viewManagers:viewManagers];
|
||||
_uiManager.eventDispatcher = _eventDispatcher;
|
||||
_moduleInstances[RCTModuleName([RCTUIManager class])] = _uiManager;
|
||||
[_moduleInstances addEntriesFromDictionary:viewManagers];
|
||||
|
||||
// Register remaining modules
|
||||
[RCTNativeModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
|
||||
if (_moduleInstances[moduleName] == nil) {
|
||||
_moduleInstances[moduleName] = [[moduleClass alloc] init];
|
||||
}
|
||||
}];
|
||||
|
||||
[self doneRegisteringModules];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
RCTAssert(!self.valid, @"must call -invalidate before -dealloc; TODO: why not call it here then?");
|
||||
}
|
||||
|
||||
#pragma mark - RCTInvalidating
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _javaScriptExecutor != nil;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
if (_latestJSExecutor == _javaScriptExecutor) {
|
||||
_latestJSExecutor = nil;
|
||||
}
|
||||
_javaScriptExecutor = nil;
|
||||
|
||||
dispatch_sync(_shadowQueue, ^{
|
||||
// Make sure all dispatchers have been executed before
|
||||
// freeing up memory from _asyncHookMapByModuleID
|
||||
});
|
||||
|
||||
for (id target in _moduleInstances.objectEnumerator) {
|
||||
if ([target respondsToSelector:@selector(invalidate)]) {
|
||||
[(id<RCTInvalidating>)target invalidate];
|
||||
}
|
||||
}
|
||||
[_moduleInstances removeAllObjects];
|
||||
|
||||
_timing = nil;
|
||||
}
|
||||
|
||||
/**
|
||||
* - TODO (#5906496): When we build a `MessageQueue.m`, handling all the requests could
|
||||
* cause both a queue of "responses". We would flush them here. However, we
|
||||
* currently just expect each objc block to handle its own response sending
|
||||
* using a `RCTResponseSenderBlock`.
|
||||
*/
|
||||
|
||||
#pragma mark - RCTBridge methods
|
||||
|
||||
/**
|
||||
* Like JS::call, for objective-c.
|
||||
*/
|
||||
- (void)enqueueJSCall:(NSUInteger)moduleID methodID:(NSUInteger)methodID args:(NSArray *)args
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
[self _invokeRemoteJSModule:moduleID methodID:methodID args:args];
|
||||
}
|
||||
|
||||
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
|
||||
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
|
||||
if (scriptLoadError) {
|
||||
onComplete(scriptLoadError);
|
||||
return;
|
||||
}
|
||||
|
||||
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
|
||||
method:@"flushedQueue"
|
||||
arguments:@[]
|
||||
callback:^(id objcValue, NSError *error) {
|
||||
[self _handleBuffer:objcValue];
|
||||
onComplete(error);
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)enqueueUpdateTimers
|
||||
{
|
||||
[_timing enqueueUpdateTimers];
|
||||
}
|
||||
|
||||
#pragma mark - Payload Generation
|
||||
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
|
||||
{
|
||||
NSTimeInterval startJS = RCTTGetAbsoluteTime();
|
||||
|
||||
RCTJavaScriptCallback processResponse = ^(id objcValue, NSError *error) {
|
||||
NSTimeInterval startNative = RCTTGetAbsoluteTime();
|
||||
[self _handleBuffer:objcValue];
|
||||
|
||||
NSTimeInterval end = RCTTGetAbsoluteTime();
|
||||
NSTimeInterval timeJS = startNative - startJS;
|
||||
NSTimeInterval timeNative = end - startNative;
|
||||
|
||||
// TODO: surface this performance information somewhere
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"PERF" object:nil userInfo:@{@"JS": @(timeJS * 1000000), @"Native": @(timeNative * 1000000)}];
|
||||
};
|
||||
|
||||
[_javaScriptExecutor executeJSCall:module
|
||||
method:method
|
||||
arguments:args
|
||||
callback:processResponse];
|
||||
}
|
||||
|
||||
- (void)_invokeRemoteJSModule:(NSUInteger)moduleID methodID:(NSUInteger)methodID args:(NSArray *)args
|
||||
{
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[@(moduleID), @(methodID), args]];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO (#5906496): Have responses piggy backed on a round trip with ObjC->JS requests.
|
||||
*/
|
||||
- (void)_sendResponseToJavaScriptCallbackID:(NSInteger)cbID args:(NSArray *)args
|
||||
{
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"invokeCallbackAndReturnFlushedQueue"
|
||||
arguments:@[@(cbID), args]];
|
||||
}
|
||||
|
||||
#pragma mark - Payload Processing
|
||||
|
||||
- (void)_handleBuffer:(id)buffer
|
||||
{
|
||||
if (buffer == nil || buffer == (id)kCFNull) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![buffer isKindOfClass:[NSArray class]]) {
|
||||
RCTLogMustFix(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class]));
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *requestsArray = (NSArray *)buffer;
|
||||
NSUInteger bufferRowCount = [requestsArray count];
|
||||
NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1;
|
||||
if (bufferRowCount != expectedFieldsCount) {
|
||||
RCTLogMustFix(@"Must pass all fields to buffer - expected %zd, saw %zd", expectedFieldsCount, bufferRowCount);
|
||||
return;
|
||||
}
|
||||
|
||||
for (NSUInteger fieldIndex = RCTBridgeFieldRequestModuleIDs; fieldIndex <= RCTBridgeFieldParamss; fieldIndex++) {
|
||||
id field = [requestsArray objectAtIndex:fieldIndex];
|
||||
if (![field isKindOfClass:[NSArray class]]) {
|
||||
RCTLogMustFix(@"Field at index %zd in buffer must be an instance of NSArray, got %@", fieldIndex, NSStringFromClass([field class]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSArray *moduleIDs = [requestsArray objectAtIndex:RCTBridgeFieldRequestModuleIDs];
|
||||
NSArray *methodIDs = [requestsArray objectAtIndex:RCTBridgeFieldMethodIDs];
|
||||
NSArray *paramss = [requestsArray objectAtIndex:RCTBridgeFieldParamss];
|
||||
|
||||
NSUInteger numRequests = [moduleIDs count];
|
||||
BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramss count];
|
||||
if (!allSame) {
|
||||
RCTLogMustFix(@"Invalid data message - all must be length: %zd", numRequests);
|
||||
return;
|
||||
}
|
||||
|
||||
for (NSUInteger i = 0; i < numRequests; i++) {
|
||||
@autoreleasepool {
|
||||
[self _handleRequestNumber:i
|
||||
moduleID:[moduleIDs objectAtIndex:i]
|
||||
methodID:[methodIDs objectAtIndex:i]
|
||||
params:[paramss objectAtIndex:i]];
|
||||
}
|
||||
}
|
||||
|
||||
// Update modules
|
||||
for (id target in _moduleInstances.objectEnumerator) {
|
||||
if ([target respondsToSelector:@selector(batchDidComplete)]) {
|
||||
dispatch_async(_shadowQueue, ^{
|
||||
[target batchDidComplete];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_handleRequestNumber:(NSUInteger)i moduleID:(id)moduleID methodID:(id)methodID params:(id)params
|
||||
{
|
||||
if (![moduleID isKindOfClass:[NSNumber class]] || ![methodID isKindOfClass:[NSNumber class]] || ![params isKindOfClass:[NSArray class]]) {
|
||||
RCTLogMustFix(@"Invalid module/method/params tuple for request #%zd", i);
|
||||
return;
|
||||
}
|
||||
[self _dispatchUsingAsyncHookMapWithModuleID:[moduleID integerValue]
|
||||
methodID:[methodID integerValue]
|
||||
params:params];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a callback that reports values back to the JS thread.
|
||||
* TODO (#5906496): These responses should go into their own queue `MessageQueue.m` that
|
||||
* mirrors the JS queue and protocol. For now, we speak the "language" of the JS
|
||||
* queue by packing it into an array that matches the wire protocol.
|
||||
*/
|
||||
- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)cbID
|
||||
{
|
||||
if (!cbID) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return ^(NSArray *args) {
|
||||
[self _sendResponseToJavaScriptCallbackID:cbID args:args];
|
||||
};
|
||||
}
|
||||
|
||||
+ (NSInvocation *)invocationForAdditionalArguments:(NSUInteger)argCount
|
||||
{
|
||||
static NSMutableDictionary *invocations;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
invocations = [NSMutableDictionary dictionary];
|
||||
});
|
||||
|
||||
id key = @(argCount);
|
||||
NSInvocation *invocation = invocations[key];
|
||||
if (invocation == nil) {
|
||||
NSString *objCTypes = [@"v@:" stringByPaddingToLength:3 + argCount withString:@"@" startingAtIndex:0];
|
||||
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:objCTypes.UTF8String];
|
||||
invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
|
||||
invocations[key] = invocation;
|
||||
}
|
||||
|
||||
return invocation;
|
||||
}
|
||||
|
||||
- (BOOL)_dispatchUsingAsyncHookMapWithModuleID:(NSInteger)moduleID
|
||||
methodID:(NSInteger)methodID
|
||||
params:(NSArray *)params
|
||||
{
|
||||
if (moduleID < 0 || moduleID >= RCTExportedMethodsByModule().count) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSString *moduleName = RCTExportedModuleNameAtSortedIndex(moduleID);
|
||||
NSArray *methods = RCTExportedMethodsByModule()[moduleName];
|
||||
if (methodID < 0 || methodID >= methods.count) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
RCTModuleMethod *method = methods[methodID];
|
||||
NSUInteger methodArity = method.arity;
|
||||
if (params.count != methodArity) {
|
||||
RCTLogMustFix(
|
||||
@"Expected %tu arguments but got %tu invoking %@.%@",
|
||||
methodArity,
|
||||
params.count,
|
||||
moduleName,
|
||||
method.JSMethodName
|
||||
);
|
||||
return NO;
|
||||
}
|
||||
|
||||
__weak RCTBridge *weakSelf = self;
|
||||
dispatch_async(_shadowQueue, ^{
|
||||
__strong RCTBridge *strongSelf = weakSelf;
|
||||
|
||||
if (!strongSelf.isValid) {
|
||||
// strongSelf has been invalidated since the dispatch_async call and this
|
||||
// invocation should not continue.
|
||||
return;
|
||||
}
|
||||
|
||||
NSInvocation *invocation = [RCTBridge invocationForAdditionalArguments:methodArity];
|
||||
|
||||
// TODO: we should just store module instances by index, since that's how we look them up anyway
|
||||
id target = strongSelf->_moduleInstances[moduleName];
|
||||
RCTAssert(target != nil, @"No module found for name '%@'", moduleName);
|
||||
|
||||
[invocation setArgument:&target atIndex:0];
|
||||
|
||||
SEL selector = method.selector;
|
||||
[invocation setArgument:&selector atIndex:1];
|
||||
|
||||
// Retain used blocks until after invocation completes.
|
||||
NSMutableArray *blocks = [NSMutableArray array];
|
||||
|
||||
[params enumerateObjectsUsingBlock:^(id param, NSUInteger idx, BOOL *stop) {
|
||||
if ([param isEqual:[NSNull null]]) {
|
||||
param = nil;
|
||||
} else if ([method.blockArgumentIndexes containsIndex:idx]) {
|
||||
id block = [strongSelf createResponseSenderBlock:[param integerValue]];
|
||||
[blocks addObject:block];
|
||||
param = block;
|
||||
}
|
||||
|
||||
[invocation setArgument:¶m atIndex:idx + 2];
|
||||
}];
|
||||
|
||||
@try {
|
||||
[invocation invoke];
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogMustFix(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, exception);
|
||||
}
|
||||
@finally {
|
||||
// Force `blocks` to remain alive until here.
|
||||
blocks = nil;
|
||||
}
|
||||
});
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)doneRegisteringModules
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
RCTAssert(_javaScriptModulesConfig != nil, @"JS module config not loaded in APP");
|
||||
|
||||
NSMutableDictionary *objectsToInject = [NSMutableDictionary dictionary];
|
||||
|
||||
// Dictionary of { moduleName0: { moduleID: 0, methods: { methodName0: { methodID: 0, type: remote }, methodName1: { ... }, ... }, ... }
|
||||
NSUInteger moduleCount = RCTExportedMethodsByModule().count;
|
||||
NSMutableDictionary *moduleConfigs = [NSMutableDictionary dictionaryWithCapacity:RCTExportedMethodsByModule().count];
|
||||
for (NSUInteger i = 0; i < moduleCount; i++) {
|
||||
NSString *moduleName = RCTExportedModuleNameAtSortedIndex(i);
|
||||
NSArray *rawMethods = RCTExportedMethodsByModule()[moduleName];
|
||||
NSMutableDictionary *methods = [NSMutableDictionary dictionaryWithCapacity:rawMethods.count];
|
||||
[rawMethods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *stop) {
|
||||
methods[method.JSMethodName] = @{
|
||||
@"methodID": @(methodID),
|
||||
@"type": @"remote",
|
||||
};
|
||||
}];
|
||||
|
||||
NSMutableDictionary *moduleConfig = [NSMutableDictionary dictionary];
|
||||
moduleConfig[@"moduleID"] = @(i);
|
||||
moduleConfig[@"methods"] = methods;
|
||||
|
||||
id target = [_moduleInstances objectForKey:moduleName];
|
||||
if ([target respondsToSelector:@selector(constantsToExport)] && ![target conformsToProtocol:@protocol(RCTNativeViewModule)]) {
|
||||
// TODO: find a more elegant way to handle RCTNativeViewModule constants as a special case
|
||||
moduleConfig[@"constants"] = [target constantsToExport];
|
||||
}
|
||||
moduleConfigs[moduleName] = moduleConfig;
|
||||
}
|
||||
NSDictionary *batchedBridgeConfig = @{
|
||||
@"remoteModuleConfig": moduleConfigs,
|
||||
@"localModulesConfig": _javaScriptModulesConfig
|
||||
};
|
||||
|
||||
NSString *configJSON = RCTJSONStringify(batchedBridgeConfig, NULL);
|
||||
objectsToInject[@"__fbBatchedBridgeConfig"] = configJSON;
|
||||
|
||||
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
||||
[objectsToInject enumerateKeysAndObjectsUsingBlock:^(NSString *objectName, NSString *script, BOOL *stop) {
|
||||
[_javaScriptExecutor injectJSONText:script asGlobalObjectNamed:objectName callback:^(id err) {
|
||||
dispatch_semaphore_signal(semaphore);
|
||||
}];
|
||||
}];
|
||||
|
||||
for (NSUInteger i = 0, count = objectsToInject.count; i < count; i++) {
|
||||
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
|
||||
RCTLogMustFix(@"JavaScriptExecutor take too long to inject JSON object");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)hasValidJSExecutor
|
||||
{
|
||||
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);
|
||||
}
|
||||
|
||||
+ (void)log:(NSArray *)objects level:(NSString *)level
|
||||
{
|
||||
if (!_latestJSExecutor || ![_latestJSExecutor isValid]) {
|
||||
RCTLogError(@"%@", RCTLogFormatString(@"ERROR: No valid JS executor to log %@.", objects));
|
||||
return;
|
||||
}
|
||||
NSMutableArray *args = [NSMutableArray arrayWithObject:level];
|
||||
|
||||
// TODO (#5906496): Find out and document why we skip the first object
|
||||
for (id ob in [objects subarrayWithRange:(NSRange){1, [objects count] - 1}]) {
|
||||
if ([NSJSONSerialization isValidJSONObject:@[ob]]) {
|
||||
[args addObject:ob];
|
||||
} else {
|
||||
[args addObject:[ob description]];
|
||||
}
|
||||
}
|
||||
// Note the js executor could get invalidated while we're trying to call this...need to watch out for that.
|
||||
[_latestJSExecutor executeJSCall:@"RCTLog"
|
||||
method:@"logIfNoNativeHook"
|
||||
arguments:args
|
||||
callback:^(id objcValue, NSError *error) {}];
|
||||
}
|
||||
|
||||
@end
|
||||
59
ReactKit/Base/RCTConvert.h
Normal file
59
ReactKit/Base/RCTConvert.h
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "Layout.h"
|
||||
|
||||
@interface RCTConvert : NSObject
|
||||
|
||||
+ (BOOL)BOOL:(id)json;
|
||||
+ (double)double:(id)json;
|
||||
+ (float)float:(id)json;
|
||||
+ (int)int:(id)json;
|
||||
|
||||
+ (NSString *)NSString:(id)json;
|
||||
+ (NSNumber *)NSNumber:(id)json;
|
||||
+ (NSInteger)NSInteger:(id)json;
|
||||
+ (NSUInteger)NSUInteger:(id)json;
|
||||
|
||||
+ (NSURL *)NSURL:(id)json;
|
||||
+ (NSURLRequest *)NSURLRequest:(id)json;
|
||||
|
||||
+ (NSDate *)NSDate:(id)json;
|
||||
+ (NSTimeZone *)NSTimeZone:(id)json;
|
||||
+ (NSTimeInterval)NSTimeInterval:(id)json;
|
||||
|
||||
+ (NSTextAlignment)NSTextAlignment:(id)json;
|
||||
+ (NSWritingDirection)NSWritingDirection:(id)json;
|
||||
+ (UITextAutocapitalizationType)UITextAutocapitalizationType:(id)json;
|
||||
+ (UIKeyboardType)UIKeyboardType:(id)json;
|
||||
|
||||
+ (CGFloat)CGFloat:(id)json;
|
||||
+ (CGPoint)CGPoint:(id)json;
|
||||
+ (CGSize)CGSize:(id)json;
|
||||
+ (CGRect)CGRect:(id)json;
|
||||
+ (UIEdgeInsets)UIEdgeInsets:(id)json;
|
||||
|
||||
+ (CATransform3D)CATransform3D:(id)json;
|
||||
+ (CGAffineTransform)CGAffineTransform:(id)json;
|
||||
|
||||
+ (UIColor *)UIColor:(id)json;
|
||||
+ (CGColorRef)CGColor:(id)json;
|
||||
|
||||
+ (UIImage *)UIImage:(id)json;
|
||||
+ (CGImageRef)CGImage:(id)json;
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withSize:(id)json;
|
||||
+ (UIFont *)UIFont:(UIFont *)font withWeight:(id)json;
|
||||
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json;
|
||||
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json size:(id)json weight:(id)json;
|
||||
|
||||
+ (BOOL)css_overflow:(id)json;
|
||||
+ (css_flex_direction_t)css_flex_direction_t:(id)json;
|
||||
+ (css_justify_t)css_justify_t:(id)json;
|
||||
+ (css_align_t)css_align_t:(id)json;
|
||||
+ (css_position_type_t)css_position_type_t:(id)json;
|
||||
+ (css_wrap_type_t)css_wrap_type_t:(id)json;
|
||||
|
||||
@end
|
||||
574
ReactKit/Base/RCTConvert.m
Normal file
574
ReactKit/Base/RCTConvert.m
Normal file
@@ -0,0 +1,574 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTConvert.h"
|
||||
|
||||
#import "RCTLog.h"
|
||||
|
||||
CGFloat const RCTDefaultFontSize = 14;
|
||||
NSString *const RCTDefaultFontName = @"HelveticaNeue";
|
||||
NSString *const RCTDefaultFontWeight = @"normal";
|
||||
NSString *const RCTBoldFontWeight = @"bold";
|
||||
|
||||
#define RCT_CONVERTER_CUSTOM(type, name, code) \
|
||||
+ (type)name:(id)json \
|
||||
{ \
|
||||
@try { \
|
||||
return code; \
|
||||
} \
|
||||
@catch (__unused NSException *e) { \
|
||||
RCTLogMustFix(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \
|
||||
json, [json class], #type); \
|
||||
json = nil; \
|
||||
return code; \
|
||||
} \
|
||||
}
|
||||
|
||||
#define RCT_CONVERTER(type, name, getter) \
|
||||
RCT_CONVERTER_CUSTOM(type, name, [json getter])
|
||||
|
||||
#define RCT_ENUM_CONVERTER(type, values, default, getter) \
|
||||
+ (type)type:(id)json \
|
||||
{ \
|
||||
static NSDictionary *mapping; \
|
||||
static dispatch_once_t onceToken; \
|
||||
dispatch_once(&onceToken, ^{ \
|
||||
mapping = values; \
|
||||
}); \
|
||||
if (!json) { \
|
||||
return default; \
|
||||
} \
|
||||
if ([json isKindOfClass:[NSNumber class]]) { \
|
||||
if ([[mapping allValues] containsObject:json]) { \
|
||||
return [json getter]; \
|
||||
} \
|
||||
RCTLogMustFix(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allValues]); \
|
||||
return default; \
|
||||
} \
|
||||
if (![json isKindOfClass:[NSString class]]) { \
|
||||
RCTLogMustFix(@"Expected NSNumber or NSString for %s, received %@: %@", #type, [json class], json); \
|
||||
} \
|
||||
id value = mapping[json]; \
|
||||
if(!value && [json description].length > 0) { \
|
||||
RCTLogMustFix(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allKeys]); \
|
||||
} \
|
||||
return value ? [value getter] : default; \
|
||||
}
|
||||
|
||||
#define RCT_STRUCT_CONVERTER(type, values) \
|
||||
+ (type)type:(id)json \
|
||||
{ \
|
||||
@try { \
|
||||
static NSArray *fields; \
|
||||
static NSUInteger count; \
|
||||
static dispatch_once_t onceToken; \
|
||||
dispatch_once(&onceToken, ^{ \
|
||||
fields = values; \
|
||||
count = [fields count]; \
|
||||
}); \
|
||||
type result; \
|
||||
if ([json isKindOfClass:[NSArray class]]) { \
|
||||
if ([json count] != count) { \
|
||||
RCTLogMustFix(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
|
||||
} else { \
|
||||
for (NSUInteger i = 0; i < count; i++) { \
|
||||
((CGFloat *)&result)[i] = [json[i] doubleValue]; \
|
||||
} \
|
||||
} \
|
||||
} else { \
|
||||
if (![json isKindOfClass:[NSDictionary class]]) { \
|
||||
RCTLogMustFix(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
|
||||
} else { \
|
||||
for (NSUInteger i = 0; i < count; i++) { \
|
||||
((CGFloat *)&result)[i] = [json[fields[i]] doubleValue]; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
return result; \
|
||||
} \
|
||||
@catch (__unused NSException *e) { \
|
||||
RCTLogMustFix(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
|
||||
type result; \
|
||||
return result; \
|
||||
} \
|
||||
}
|
||||
|
||||
@implementation RCTConvert
|
||||
|
||||
RCT_CONVERTER(BOOL, BOOL, boolValue)
|
||||
RCT_CONVERTER(double, double, doubleValue)
|
||||
RCT_CONVERTER(float, float, floatValue)
|
||||
RCT_CONVERTER(int, int, intValue)
|
||||
|
||||
RCT_CONVERTER(NSString *, NSString, description)
|
||||
RCT_CONVERTER_CUSTOM(NSNumber *, NSNumber, @([json doubleValue]))
|
||||
RCT_CONVERTER(NSInteger, NSInteger, integerValue)
|
||||
RCT_CONVERTER_CUSTOM(NSUInteger, NSUInteger, [json unsignedIntegerValue])
|
||||
|
||||
+ (NSURL *)NSURL:(id)json
|
||||
{
|
||||
if (![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogMustFix(@"Expected NSString for NSURL, received %@: %@", [json class], json);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *path = json;
|
||||
if ([path isAbsolutePath])
|
||||
{
|
||||
return [NSURL fileURLWithPath:path];
|
||||
}
|
||||
else if ([path length])
|
||||
{
|
||||
return [NSURL URLWithString:path relativeToURL:[[NSBundle mainBundle] resourceURL]];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSURLRequest *)NSURLRequest:(id)json
|
||||
{
|
||||
return [NSURLRequest requestWithURL:[self NSURL:json]];
|
||||
}
|
||||
|
||||
RCT_CONVERTER_CUSTOM(NSDate *, NSDate, [NSDate dateWithTimeIntervalSince1970:[json doubleValue]])
|
||||
RCT_CONVERTER_CUSTOM(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[json doubleValue]])
|
||||
RCT_CONVERTER(NSTimeInterval, NSTimeInterval, doubleValue)
|
||||
|
||||
/**
|
||||
* NOTE: We don't deliberately don't support NSTextAlignmentJustified in the
|
||||
* X-platform RCTText implementation because it isn't available on Android.
|
||||
* We may wish to support this for iOS-specific controls such as UILabel.
|
||||
*/
|
||||
RCT_ENUM_CONVERTER(NSTextAlignment, (@{
|
||||
@"auto": @(NSTextAlignmentNatural),
|
||||
@"left": @(NSTextAlignmentLeft),
|
||||
@"center": @(NSTextAlignmentCenter),
|
||||
@"right": @(NSTextAlignmentRight),
|
||||
/* @"justify": @(NSTextAlignmentJustify), */
|
||||
}), NSTextAlignmentNatural, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(NSWritingDirection, (@{
|
||||
@"auto": @(NSWritingDirectionNatural),
|
||||
@"ltr": @(NSWritingDirectionLeftToRight),
|
||||
@"rtl": @(NSWritingDirectionRightToLeft),
|
||||
}), NSWritingDirectionNatural, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(UITextAutocapitalizationType, (@{
|
||||
@"none": @(UITextAutocapitalizationTypeNone),
|
||||
@"words": @(UITextAutocapitalizationTypeWords),
|
||||
@"sentences": @(UITextAutocapitalizationTypeSentences),
|
||||
@"all": @(UITextAutocapitalizationTypeAllCharacters)
|
||||
}), UITextAutocapitalizationTypeSentences, integerValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(UIKeyboardType, (@{
|
||||
@"numeric": @(UIKeyboardTypeDecimalPad),
|
||||
@"default": @(UIKeyboardTypeDefault),
|
||||
}), UIKeyboardTypeDefault, integerValue)
|
||||
|
||||
RCT_CONVERTER(CGFloat, CGFloat, doubleValue)
|
||||
RCT_STRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]))
|
||||
RCT_STRUCT_CONVERTER(CGSize, (@[@"w", @"h"]))
|
||||
RCT_STRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"w", @"h"]))
|
||||
RCT_STRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]))
|
||||
|
||||
RCT_STRUCT_CONVERTER(CATransform3D, (@[
|
||||
@"m11", @"m12", @"m13", @"m14",
|
||||
@"m21", @"m22", @"m23", @"m24",
|
||||
@"m31", @"m32", @"m33", @"m34",
|
||||
@"m41", @"m42", @"m43", @"m44"
|
||||
]))
|
||||
|
||||
RCT_STRUCT_CONVERTER(CGAffineTransform, (@[@"a", @"b", @"c", @"d", @"tx", @"ty"]))
|
||||
|
||||
+ (UIColor *)UIColor:(id)json
|
||||
{
|
||||
// Check color cache
|
||||
static NSMutableDictionary *colorCache = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
colorCache = [[NSMutableDictionary alloc] init];
|
||||
});
|
||||
UIColor *color = colorCache[json];
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
|
||||
if ([json isKindOfClass:[NSString class]]) {
|
||||
|
||||
// Check named colors
|
||||
static NSDictionary *namedColors = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
namedColors = @{
|
||||
|
||||
// CSS colors
|
||||
@"aliceblue": @"#f0f8ff",
|
||||
@"antiquewhite": @"#faebd7",
|
||||
@"aqua": @"#00ffff",
|
||||
@"aquamarine": @"#7fffd4",
|
||||
@"azure": @"#f0ffff",
|
||||
@"beige": @"#f5f5dc",
|
||||
@"bisque": @"#ffe4c4",
|
||||
@"black": @"#000000",
|
||||
@"blanchedalmond": @"#ffebcd",
|
||||
@"blue": @"#0000ff",
|
||||
@"blueviolet": @"#8a2be2",
|
||||
@"brown": @"#a52a2a",
|
||||
@"burlywood": @"#deb887",
|
||||
@"cadetblue": @"#5f9ea0",
|
||||
@"chartreuse": @"#7fff00",
|
||||
@"chocolate": @"#d2691e",
|
||||
@"coral": @"#ff7f50",
|
||||
@"cornflowerblue": @"#6495ed",
|
||||
@"cornsilk": @"#fff8dc",
|
||||
@"crimson": @"#dc143c",
|
||||
@"cyan": @"#00ffff",
|
||||
@"darkblue": @"#00008b",
|
||||
@"darkcyan": @"#008b8b",
|
||||
@"darkgoldenrod": @"#b8860b",
|
||||
@"darkgray": @"#a9a9a9",
|
||||
@"darkgrey": @"#a9a9a9",
|
||||
@"darkgreen": @"#006400",
|
||||
@"darkkhaki": @"#bdb76b",
|
||||
@"darkmagenta": @"#8b008b",
|
||||
@"darkolivegreen": @"#556b2f",
|
||||
@"darkorange": @"#ff8c00",
|
||||
@"darkorchid": @"#9932cc",
|
||||
@"darkred": @"#8b0000",
|
||||
@"darksalmon": @"#e9967a",
|
||||
@"darkseagreen": @"#8fbc8f",
|
||||
@"darkslateblue": @"#483d8b",
|
||||
@"darkslategray": @"#2f4f4f",
|
||||
@"darkslategrey": @"#2f4f4f",
|
||||
@"darkturquoise": @"#00ced1",
|
||||
@"darkviolet": @"#9400d3",
|
||||
@"deeppink": @"#ff1493",
|
||||
@"deepskyblue": @"#00bfff",
|
||||
@"dimgray": @"#696969",
|
||||
@"dimgrey": @"#696969",
|
||||
@"dodgerblue": @"#1e90ff",
|
||||
@"firebrick": @"#b22222",
|
||||
@"floralwhite": @"#fffaf0",
|
||||
@"forestgreen": @"#228b22",
|
||||
@"fuchsia": @"#ff00ff",
|
||||
@"gainsboro": @"#dcdcdc",
|
||||
@"ghostwhite": @"#f8f8ff",
|
||||
@"gold": @"#ffd700",
|
||||
@"goldenrod": @"#daa520",
|
||||
@"gray": @"#808080",
|
||||
@"grey": @"#808080",
|
||||
@"green": @"#008000",
|
||||
@"greenyellow": @"#adff2f",
|
||||
@"honeydew": @"#f0fff0",
|
||||
@"hotpink": @"#ff69b4",
|
||||
@"indianred": @"#cd5c5c",
|
||||
@"indigo": @"#4b0082",
|
||||
@"ivory": @"#fffff0",
|
||||
@"khaki": @"#f0e68c",
|
||||
@"lavender": @"#e6e6fa",
|
||||
@"lavenderblush": @"#fff0f5",
|
||||
@"lawngreen": @"#7cfc00",
|
||||
@"lemonchiffon": @"#fffacd",
|
||||
@"lightblue": @"#add8e6",
|
||||
@"lightcoral": @"#f08080",
|
||||
@"lightcyan": @"#e0ffff",
|
||||
@"lightgoldenrodyellow": @"#fafad2",
|
||||
@"lightgray": @"#d3d3d3",
|
||||
@"lightgrey": @"#d3d3d3",
|
||||
@"lightgreen": @"#90ee90",
|
||||
@"lightpink": @"#ffb6c1",
|
||||
@"lightsalmon": @"#ffa07a",
|
||||
@"lightseagreen": @"#20b2aa",
|
||||
@"lightskyblue": @"#87cefa",
|
||||
@"lightslategray": @"#778899",
|
||||
@"lightslategrey": @"#778899",
|
||||
@"lightsteelblue": @"#b0c4de",
|
||||
@"lightyellow": @"#ffffe0",
|
||||
@"lime": @"#00ff00",
|
||||
@"limegreen": @"#32cd32",
|
||||
@"linen": @"#faf0e6",
|
||||
@"magenta": @"#ff00ff",
|
||||
@"maroon": @"#800000",
|
||||
@"mediumaquamarine": @"#66cdaa",
|
||||
@"mediumblue": @"#0000cd",
|
||||
@"mediumorchid": @"#ba55d3",
|
||||
@"mediumpurple": @"#9370db",
|
||||
@"mediumseagreen": @"#3cb371",
|
||||
@"mediumslateblue": @"#7b68ee",
|
||||
@"mediumspringgreen": @"#00fa9a",
|
||||
@"mediumturquoise": @"#48d1cc",
|
||||
@"mediumvioletred": @"#c71585",
|
||||
@"midnightblue": @"#191970",
|
||||
@"mintcream": @"#f5fffa",
|
||||
@"mistyrose": @"#ffe4e1",
|
||||
@"moccasin": @"#ffe4b5",
|
||||
@"navajowhite": @"#ffdead",
|
||||
@"navy": @"#000080",
|
||||
@"oldlace": @"#fdf5e6",
|
||||
@"olive": @"#808000",
|
||||
@"olivedrab": @"#6b8e23",
|
||||
@"orange": @"#ffa500",
|
||||
@"orangered": @"#ff4500",
|
||||
@"orchid": @"#da70d6",
|
||||
@"palegoldenrod": @"#eee8aa",
|
||||
@"palegreen": @"#98fb98",
|
||||
@"paleturquoise": @"#afeeee",
|
||||
@"palevioletred": @"#db7093",
|
||||
@"papayawhip": @"#ffefd5",
|
||||
@"peachpuff": @"#ffdab9",
|
||||
@"peru": @"#cd853f",
|
||||
@"pink": @"#ffc0cb",
|
||||
@"plum": @"#dda0dd",
|
||||
@"powderblue": @"#b0e0e6",
|
||||
@"purple": @"#800080",
|
||||
@"rebeccapurple": @"#663399",
|
||||
@"red": @"#ff0000",
|
||||
@"rosybrown": @"#bc8f8f",
|
||||
@"royalblue": @"#4169e1",
|
||||
@"saddlebrown": @"#8b4513",
|
||||
@"salmon": @"#fa8072",
|
||||
@"sandybrown": @"#f4a460",
|
||||
@"seagreen": @"#2e8b57",
|
||||
@"seashell": @"#fff5ee",
|
||||
@"sienna": @"#a0522d",
|
||||
@"silver": @"#c0c0c0",
|
||||
@"skyblue": @"#87ceeb",
|
||||
@"slateblue": @"#6a5acd",
|
||||
@"slategray": @"#708090",
|
||||
@"slategrey": @"#708090",
|
||||
@"snow": @"#fffafa",
|
||||
@"springgreen": @"#00ff7f",
|
||||
@"steelblue": @"#4682b4",
|
||||
@"tan": @"#d2b48c",
|
||||
@"teal": @"#008080",
|
||||
@"thistle": @"#d8bfd8",
|
||||
@"tomato": @"#ff6347",
|
||||
@"turquoise": @"#40e0d0",
|
||||
@"violet": @"#ee82ee",
|
||||
@"wheat": @"#f5deb3",
|
||||
@"white": @"#ffffff",
|
||||
@"whitesmoke": @"#f5f5f5",
|
||||
@"yellow": @"#ffff00",
|
||||
@"yellowgreen": @"#9acd32",
|
||||
|
||||
// Nonstandard color extensions
|
||||
@"transparent": @"rgba(0,0,0,0)",
|
||||
@"clear": @"rgba(0,0,0,0)",
|
||||
};
|
||||
});
|
||||
NSString *colorString = namedColors[json];
|
||||
if (!colorString) {
|
||||
colorString = json;
|
||||
}
|
||||
|
||||
// Parse color
|
||||
NSUInteger red = -1;
|
||||
NSUInteger green = -1;
|
||||
NSUInteger blue = -1;
|
||||
CGFloat alpha = 1.0;
|
||||
if ([colorString hasPrefix:@"#"]) {
|
||||
sscanf([colorString UTF8String], "#%02tX%02tX%02tX", &red, &green, &blue);
|
||||
} else if ([colorString hasPrefix:@"rgba("]) {
|
||||
double tmpAlpha;
|
||||
sscanf([colorString UTF8String], "rgba(%zd,%zd,%zd,%lf)", &red, &green, &blue, &tmpAlpha);
|
||||
alpha = tmpAlpha > 0.99 ? 1.0 : tmpAlpha;
|
||||
} else if ([colorString hasPrefix:@"rgb("]) {
|
||||
sscanf([colorString UTF8String], "rgb(%zd,%zd,%zd)", &red, &green, &blue);
|
||||
} else {
|
||||
RCTLogMustFix(@"Unrecognized color format '%@', must be one of #hex|rgba|rgb", colorString);
|
||||
}
|
||||
if (red == -1 || green == -1 || blue == -1 || alpha > 1.0 || alpha < 0.0) {
|
||||
RCTLogMustFix(@"Invalid color string '%@'", colorString);
|
||||
} else {
|
||||
color = [UIColor colorWithRed:red / 255.0 green:green / 255.0 blue:blue / 255.0 alpha:alpha];
|
||||
}
|
||||
|
||||
} else if ([json isKindOfClass:[NSArray class]]) {
|
||||
|
||||
if ([json count] < 3 || [json count] > 4) {
|
||||
|
||||
RCTLogMustFix(@"Expected array with count 3 or 4, but count is %zd: %@", [json count], json);
|
||||
|
||||
} else {
|
||||
|
||||
// Color array
|
||||
color = [UIColor colorWithRed:[json[0] doubleValue]
|
||||
green:[json[1] doubleValue]
|
||||
blue:[json[2] doubleValue]
|
||||
alpha:[json count] > 3 ? [json[3] doubleValue] : 1];
|
||||
}
|
||||
|
||||
} else if ([json isKindOfClass:[NSDictionary class]]) {
|
||||
|
||||
// Color dictionary
|
||||
color = [UIColor colorWithRed:[json[@"r"] doubleValue]
|
||||
green:[json[@"g"] doubleValue]
|
||||
blue:[json[@"b"] doubleValue]
|
||||
alpha:[json[@"a"] ?: @1 doubleValue]];
|
||||
|
||||
} else if (json && ![json isKindOfClass:[NSNull class]]) {
|
||||
|
||||
RCTLogMustFix(@"Expected NSArray, NSDictionary or NSString for UIColor, received %@: %@", [json class], json);
|
||||
}
|
||||
|
||||
// Default color
|
||||
if (!color) {
|
||||
color = [UIColor whiteColor];
|
||||
}
|
||||
|
||||
// Cache and return
|
||||
if (json) {
|
||||
colorCache[json] = color;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
+ (CGColorRef)CGColor:(id)json
|
||||
{
|
||||
return [self UIColor:json].CGColor;
|
||||
}
|
||||
|
||||
+ (UIImage *)UIImage:(id)json
|
||||
{
|
||||
if (![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogMustFix(@"Expected NSString for UIImage, received %@: %@", [json class], json);
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ([json length] == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
UIImage *image = nil;
|
||||
NSString *path = json;
|
||||
if ([path isAbsolutePath]) {
|
||||
image = [UIImage imageWithContentsOfFile:path];
|
||||
} else {
|
||||
image = [UIImage imageNamed:path];
|
||||
if (!image) {
|
||||
image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:path ofType:nil]];
|
||||
}
|
||||
}
|
||||
if (!image) {
|
||||
RCTLogWarn(@"No image was found at path %@", json);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
+ (CGImageRef)CGImage:(id)json
|
||||
{
|
||||
return [self UIImage:json].CGImage;
|
||||
}
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withSize:(id)json
|
||||
{
|
||||
return [self UIFont:font withFamily:nil size:json weight:nil];
|
||||
}
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withWeight:(id)json
|
||||
{
|
||||
return [self UIFont:font withFamily:nil size:nil weight:json];
|
||||
}
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json
|
||||
{
|
||||
return [self UIFont:font withFamily:json size:nil weight:nil];
|
||||
}
|
||||
|
||||
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)family size:(id)size weight:(id)weight
|
||||
{
|
||||
// Create descriptor
|
||||
UIFontDescriptor *fontDescriptor = font.fontDescriptor ?: [UIFontDescriptor fontDescriptorWithName:RCTDefaultFontName size:RCTDefaultFontSize];
|
||||
|
||||
// Get font size
|
||||
CGFloat fontSize = [self CGFloat:size];
|
||||
if (fontSize && !isnan(fontSize)) {
|
||||
fontDescriptor = [fontDescriptor fontDescriptorWithSize:fontSize];
|
||||
}
|
||||
|
||||
// Get font family
|
||||
NSString *familyName = [RCTConvert NSString:family];
|
||||
if (familyName) {
|
||||
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
|
||||
UIFont *font = [UIFont fontWithName:familyName size:fontDescriptor.pointSize];
|
||||
if (font) {
|
||||
// It's actually a font name, not a font family name,
|
||||
// but we'll do what was meant, not what was said.
|
||||
familyName = font.familyName;
|
||||
fontDescriptor = font.fontDescriptor;
|
||||
} else {
|
||||
// Not a valid font or family
|
||||
RCTLogError(@"Unrecognized font family '%@'", familyName);
|
||||
}
|
||||
} else {
|
||||
// Set font family
|
||||
fontDescriptor = [fontDescriptor fontDescriptorWithFamily:familyName];
|
||||
}
|
||||
}
|
||||
|
||||
// Get font weight
|
||||
NSString *fontWeight = [RCTConvert NSString:weight];
|
||||
if (fontWeight) {
|
||||
|
||||
static NSSet *values;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
values = [NSSet setWithObjects:@"bold", @"normal", nil];
|
||||
});
|
||||
|
||||
if (fontWeight && ![values containsObject:fontWeight]) {
|
||||
RCTLogError(@"Unrecognized font weight '%@', must be one of %@", fontWeight, values);
|
||||
}
|
||||
|
||||
UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits;
|
||||
if ([fontWeight isEqualToString:RCTBoldFontWeight]) {
|
||||
symbolicTraits |= UIFontDescriptorTraitBold;
|
||||
} else {
|
||||
symbolicTraits &= ~UIFontDescriptorTraitBold;
|
||||
}
|
||||
fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits];
|
||||
}
|
||||
|
||||
// TODO: font style
|
||||
|
||||
// Create font
|
||||
return [UIFont fontWithDescriptor:fontDescriptor size:fontDescriptor.pointSize];
|
||||
}
|
||||
|
||||
typedef BOOL css_overflow;
|
||||
|
||||
RCT_ENUM_CONVERTER(css_overflow, (@{
|
||||
@"hidden": @NO,
|
||||
@"visible": @YES
|
||||
}), YES, boolValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(css_flex_direction_t, (@{
|
||||
@"row": @(CSS_FLEX_DIRECTION_ROW),
|
||||
@"column": @(CSS_FLEX_DIRECTION_COLUMN)
|
||||
}), CSS_FLEX_DIRECTION_COLUMN, intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(css_justify_t, (@{
|
||||
@"flex-start": @(CSS_JUSTIFY_FLEX_START),
|
||||
@"flex-end": @(CSS_JUSTIFY_FLEX_END),
|
||||
@"center": @(CSS_JUSTIFY_CENTER),
|
||||
@"space-between": @(CSS_JUSTIFY_SPACE_BETWEEN),
|
||||
@"space-around": @(CSS_JUSTIFY_SPACE_AROUND)
|
||||
}), CSS_JUSTIFY_FLEX_START, intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(css_align_t, (@{
|
||||
@"flex-start": @(CSS_ALIGN_FLEX_START),
|
||||
@"flex-end": @(CSS_ALIGN_FLEX_END),
|
||||
@"center": @(CSS_ALIGN_CENTER),
|
||||
@"auto": @(CSS_ALIGN_AUTO),
|
||||
@"stretch": @(CSS_ALIGN_STRETCH)
|
||||
}), CSS_ALIGN_FLEX_START, intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(css_position_type_t, (@{
|
||||
@"absolute": @(CSS_POSITION_ABSOLUTE),
|
||||
@"relative": @(CSS_POSITION_RELATIVE)
|
||||
}), CSS_POSITION_RELATIVE, intValue)
|
||||
|
||||
RCT_ENUM_CONVERTER(css_wrap_type_t, (@{
|
||||
@"wrap": @(CSS_WRAP),
|
||||
@"nowrap": @(CSS_NOWRAP)
|
||||
}), CSS_NOWRAP, intValue)
|
||||
|
||||
@end
|
||||
52
ReactKit/Base/RCTEventExtractor.h
Normal file
52
ReactKit/Base/RCTEventExtractor.h
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTModuleIDs.h"
|
||||
|
||||
/**
|
||||
* Simple utility to help extract arguments to JS invocations of React event
|
||||
* emitter (IOS version).
|
||||
*/
|
||||
@interface RCTEventExtractor : NSObject
|
||||
|
||||
+ (NSArray *)eventArgs:(NSNumber *)tag type:(RCTEventType)type nativeEventObj:(NSDictionary *)nativeEventObj;
|
||||
|
||||
/**
|
||||
* Constructs information about touch events to send across the serialized
|
||||
* boundary. This data should be compliant with W3C `Touch` objects. This data
|
||||
* alone isn't sufficient to construct W3C `Event` objects. To construct that,
|
||||
* there must be a simple receiver on the other side of the bridge that
|
||||
* organizes the touch objects into `Event`s.
|
||||
*
|
||||
* We send the data as an array of `Touch`es, the type of action
|
||||
* (start/end/move/cancel) and the indices that represent "changed" `Touch`es
|
||||
* from that array.
|
||||
*/
|
||||
+ (NSArray *)touchEventArgsForOrderedTouches:(NSArray *)orderedTouches
|
||||
orderedStartTags:(NSArray *)orderedStartTags
|
||||
orderedTouchIDs:(NSArray *)orderedTouchIDs
|
||||
changedIndices:(NSArray *)changedIndices
|
||||
type:(RCTEventType)type
|
||||
view:(UIView *)view;
|
||||
|
||||
+ (NSDictionary *)scrollEventObject:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
|
||||
|
||||
/**
|
||||
* Useful when having to simply communicate the fact that *something* scrolled.
|
||||
* When JavaScript infers gestures based on the event stream, any type of
|
||||
* scroll that occurs in the native platform will cause ongoing gestures to
|
||||
* cancel. Scroll/table views already send scroll events appropriately, but
|
||||
* this method is useful for other views that don't actually scroll, but should
|
||||
* interrupt JavaScript gestures as scrolls do.
|
||||
*/
|
||||
+ (NSDictionary *)fakeScrollEventObjectFor:(NSNumber *)reactTag;
|
||||
|
||||
/**
|
||||
* Finds the React target of a touch. This must be done when the touch starts,
|
||||
* else `UIKit` gesture recognizers may destroy the touch's target.
|
||||
*/
|
||||
+ (NSNumber *)touchStartTarget:(UITouch *)touch inView:(UIView *)view;
|
||||
|
||||
@end
|
||||
|
||||
200
ReactKit/Base/RCTEventExtractor.m
Normal file
200
ReactKit/Base/RCTEventExtractor.m
Normal file
@@ -0,0 +1,200 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTEventExtractor.h"
|
||||
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "RCTViewNodeProtocol.h"
|
||||
|
||||
@implementation RCTEventExtractor
|
||||
|
||||
// TODO (#5906496): an array lookup would be better than a switch statement here
|
||||
|
||||
/**
|
||||
* Keep in sync with `IOSEventConstants.js`.
|
||||
* TODO (#5906496): do this sync programmatically instead of manually
|
||||
*/
|
||||
+ (NSString *)topLevelTypeForEventType:(RCTEventType)eventType
|
||||
{
|
||||
switch(eventType) {
|
||||
case RCTEventTap:
|
||||
return @"topTap";
|
||||
case RCTEventVisibleCellsChange:
|
||||
return @"topVisibleCellsChange";
|
||||
case RCTEventNavigateBack:
|
||||
return @"topNavigateBack";
|
||||
case RCTEventNavRightButtonTap:
|
||||
return @"topNavRightButtonTap";
|
||||
case RCTEventChange:
|
||||
return @"topChange";
|
||||
case RCTEventTextFieldDidFocus:
|
||||
return @"topFocus";
|
||||
case RCTEventTextFieldWillBlur:
|
||||
return @"topBlur";
|
||||
case RCTEventTextFieldSubmitEditing:
|
||||
return @"topSubmitEditing";
|
||||
case RCTEventTextFieldEndEditing:
|
||||
return @"topEndEditing";
|
||||
case RCTEventTextInput:
|
||||
return @"topTextInput";
|
||||
case RCTEventLongPress:
|
||||
return @"topLongPress"; // Not yet supported
|
||||
case RCTEventTouchCancel:
|
||||
return @"topTouchCancel";
|
||||
case RCTEventTouchEnd:
|
||||
return @"topTouchEnd";
|
||||
case RCTEventTouchMove:
|
||||
return @"topTouchMove";
|
||||
case RCTEventTouchStart:
|
||||
return @"topTouchStart";
|
||||
case RCTEventScrollBeginDrag:
|
||||
return @"topScrollBeginDrag";
|
||||
case RCTEventScroll:
|
||||
return @"topScroll";
|
||||
case RCTEventScrollEndDrag:
|
||||
return @"topScrollEndDrag";
|
||||
case RCTEventScrollAnimationEnd:
|
||||
return @"topScrollAnimationEnd";
|
||||
case RCTEventSelectionChange:
|
||||
return @"topSelectionChange";
|
||||
case RCTEventMomentumScrollBegin:
|
||||
return @"topMomentumScrollBegin";
|
||||
case RCTEventMomentumScrollEnd:
|
||||
return @"topMomentumScrollEnd";
|
||||
case RCTEventPullToRefresh:
|
||||
return @"topPullToRefresh";
|
||||
case RCTEventLoadingStart:
|
||||
return @"topLoadingStart";
|
||||
case RCTEventLoadingFinish:
|
||||
return @"topLoadingFinish";
|
||||
case RCTEventLoadingError:
|
||||
return @"topLoadingError";
|
||||
case RCTEventNavigationProgress:
|
||||
return @"topNavigationProgress";
|
||||
default :
|
||||
RCTLogError(@"Unrecognized event type: %tu", eventType);
|
||||
return @"unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO (#5906496): Cache created string messages for each event type/tag target (for
|
||||
* events that have no data) to save allocations.
|
||||
*/
|
||||
+ (NSArray *)eventArgs:(NSNumber *)reactTag
|
||||
type:(RCTEventType)type
|
||||
nativeEventObj:(NSDictionary *)nativeEventObj
|
||||
{
|
||||
NSString *topLevelType = [RCTEventExtractor topLevelTypeForEventType:type];
|
||||
return @[reactTag ?: @0, topLevelType, nativeEventObj];
|
||||
}
|
||||
|
||||
+ (NSArray *)touchEventArgsForOrderedTouches:(NSArray *)orderedTouches
|
||||
orderedStartTags:(NSArray *)orderedStartTags
|
||||
orderedTouchIDs:(NSArray *)orderedTouchIDs
|
||||
changedIndices:(NSArray *)changedIndices
|
||||
type:(RCTEventType)type
|
||||
view:(UIView *)view
|
||||
{
|
||||
if (!orderedTouches || !orderedTouches.count) {
|
||||
RCTLogError(@"No touches in touchEventArgsForOrderedTouches");
|
||||
return nil;
|
||||
}
|
||||
NSMutableArray *touchObjects = [[NSMutableArray alloc] init];
|
||||
for (NSInteger i = 0; i < orderedTouches.count; i++) {
|
||||
NSDictionary *touchObj =
|
||||
[RCTEventExtractor touchObj:orderedTouches[i]
|
||||
withTargetTag:orderedStartTags[i]
|
||||
withTouchID:orderedTouchIDs[i]
|
||||
inView:view];
|
||||
[touchObjects addObject:touchObj];
|
||||
}
|
||||
NSString *topLevelType = [RCTEventExtractor topLevelTypeForEventType:type];
|
||||
return @[topLevelType, touchObjects, changedIndices];
|
||||
}
|
||||
|
||||
+ (NSNumber *)touchStartTarget:(UITouch *)touch inView:(UIView *)view
|
||||
{
|
||||
UIView <RCTViewNodeProtocol> *closestReactAncestor = [RCTUIManager closestReactAncestorThatRespondsToTouch:touch];
|
||||
return [closestReactAncestor reactTag];
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an object that contains all of the important touch data. This
|
||||
* should contain a superset of a W3C `Touch` object. The `Event` objects that
|
||||
* reference these touches can only be constructed from a collection of touch
|
||||
* objects that are recieved across the serialized bridge.
|
||||
*
|
||||
* Must accept the `targetReactTag` because targets are reset to `nil` by
|
||||
* `UIKit` gesture system. We had to pre-recorded at the time of touch start.
|
||||
*/
|
||||
+ (NSDictionary *)touchObj:(UITouch *)touch
|
||||
withTargetTag:(NSNumber *)targetReactTag
|
||||
withTouchID:(NSNumber *)touchID
|
||||
inView:(UIView *)view
|
||||
{
|
||||
CGPoint location = [touch locationInView:view];
|
||||
CGPoint locInView = [touch locationInView:touch.view];
|
||||
double timeStamp = touch.timestamp * 1000.0; // convert to ms
|
||||
return @{
|
||||
@"pageX": @(location.x),
|
||||
@"pageY": @(location.y),
|
||||
@"locationX": @(locInView.x),
|
||||
@"locationY": @(locInView.y),
|
||||
@"target": targetReactTag,
|
||||
@"identifier": touchID,
|
||||
@"timeStamp": @(timeStamp),
|
||||
@"touches": [NSNull null], // We hijack this touchObj to serve both as an event
|
||||
@"changedTouches": [NSNull null], // and as a Touch object, so making this JIT friendly.
|
||||
};
|
||||
}
|
||||
|
||||
// TODO (#5906496): shouldn't some of these strings be constants?
|
||||
|
||||
+ (NSDictionary *)makeScrollEventObject:(UIScrollView *)uiScrollView reactTag:(NSNumber *)reactTag;
|
||||
{
|
||||
return @{
|
||||
@"contentOffset": @{
|
||||
@"x": @(uiScrollView.contentOffset.x),
|
||||
@"y": @(uiScrollView.contentOffset.y)
|
||||
},
|
||||
@"contentSize": @{
|
||||
@"width": @(uiScrollView.contentSize.width),
|
||||
@"height": @(uiScrollView.contentSize.height)
|
||||
},
|
||||
@"layoutMeasurement": @{
|
||||
@"width": @(uiScrollView.frame.size.width),
|
||||
@"height": @(uiScrollView.frame.size.height)
|
||||
},
|
||||
@"zoomScale": @(uiScrollView.zoomScale),
|
||||
@"target": reactTag,
|
||||
};
|
||||
}
|
||||
|
||||
+ (NSDictionary *)scrollEventObject:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
|
||||
{
|
||||
return [RCTEventExtractor makeScrollEventObject:scrollView reactTag:reactTag];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)fakeScrollEventObjectFor:(NSNumber *)reactTag
|
||||
{
|
||||
return @{
|
||||
@"contentOffset": @{
|
||||
@"x": @0,
|
||||
@"y": @0
|
||||
},
|
||||
@"contentSize": @{
|
||||
@"width": @0,
|
||||
@"height": @0
|
||||
},
|
||||
@"layoutMeasurement": @{
|
||||
@"width": @0,
|
||||
@"height": @0
|
||||
},
|
||||
@"zoomScale": @1,
|
||||
@"target": reactTag
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
175
ReactKit/Base/RCTExport.h
Normal file
175
ReactKit/Base/RCTExport.h
Normal file
@@ -0,0 +1,175 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTLog.h"
|
||||
|
||||
@class RCTSparseArray;
|
||||
@class RCTUIManager;
|
||||
|
||||
typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *viewRegistry);
|
||||
|
||||
@class RCTJavaScriptEventDispatcher;
|
||||
@class RCTShadowView;
|
||||
|
||||
/* ------------------------------------------------------------------- */
|
||||
typedef struct {
|
||||
const char *func;
|
||||
const char *js_name;
|
||||
} RCTExportEntry;
|
||||
|
||||
#define _RCTExportSegmentName "__DATA"
|
||||
#define _RCTExportSectionName "RCTExport"
|
||||
|
||||
extern NSString *RCTExportedModuleNameAtSortedIndex(NSUInteger index);
|
||||
extern NSDictionary *RCTExportedMethodsByModule(void);
|
||||
|
||||
extern BOOL RCTSetProperty(id target, NSString *keypath, id value);
|
||||
extern BOOL RCTCallSetter(id target, SEL setter, id value);
|
||||
/* ------------------------------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* The type of a block that is capable of sending a response to a bridged
|
||||
* operation. Use this for returning callback methods to JS.
|
||||
*/
|
||||
typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
||||
|
||||
|
||||
/**
|
||||
* Provides minimal interface needed to register a bridge module
|
||||
*/
|
||||
@protocol RCTNativeModule <NSObject>
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Place this macro inside the method body of any method you want
|
||||
* to expose to JS. The optional js_name argument will be used as
|
||||
* the JS function name. If omitted, the JS function name will match
|
||||
* the Objective-C method selector name, up to the first colon.
|
||||
*/
|
||||
#define RCT_EXPORT(js_name) __attribute__((used, section(_RCTExportSegmentName "," \
|
||||
_RCTExportSectionName))) static const RCTExportEntry __rct_export_entry__ = { __func__, #js_name }
|
||||
|
||||
/**
|
||||
* The module name exposed to JS. If omitted, this will be inferred
|
||||
* automatically by using the native module's class name.
|
||||
*/
|
||||
+ (NSString *)moduleName;
|
||||
|
||||
/**
|
||||
* Injects constants into JS. These constants are made accessible via
|
||||
* NativeModules.moduleName.X.
|
||||
*/
|
||||
- (NSDictionary *)constantsToExport;
|
||||
|
||||
/**
|
||||
* Notifies the module that a batch of JS method invocations has just completed.
|
||||
*/
|
||||
- (void)batchDidComplete;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* Provides minimal interface needed to register a UIViewManager module
|
||||
*/
|
||||
@protocol RCTNativeViewModule <RCTNativeModule>
|
||||
|
||||
/**
|
||||
* This method instantiates a native view to be managed by the module.
|
||||
*/
|
||||
- (UIView *)viewWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* This method instantiates a shadow view to be managed by the module. If omitted,
|
||||
* an ordinary RCTShadowView instance will be created.
|
||||
*/
|
||||
- (RCTShadowView *)shadowView;
|
||||
|
||||
/**
|
||||
* Informal protocol for setting view and shadowView properties.
|
||||
* Implement methods matching these patterns to set any properties that
|
||||
* require special treatment (e.g. where the type or name cannot be inferred).
|
||||
*
|
||||
* - (void)set_<propertyName>:(id)property
|
||||
* forView:(UIView *)view
|
||||
* withDefaultView:(UIView *)defaultView;
|
||||
*
|
||||
* - (void)set_<propertyName>:(id)property
|
||||
* forShadowView:(RCTShadowView *)view
|
||||
* withDefaultView:(RCTShadowView *)defaultView;
|
||||
*
|
||||
* For simple cases, use the macros below:
|
||||
*/
|
||||
|
||||
/**
|
||||
* This handles the simple case, where JS and native property names match
|
||||
* And the type can be automatically inferred.
|
||||
*/
|
||||
#define RCT_EXPORT_VIEW_PROPERTY(name) \
|
||||
RCT_REMAP_VIEW_PROPERTY(name, name)
|
||||
|
||||
/**
|
||||
* This macro maps a named property on the module to an arbitrary key path
|
||||
* within the view.
|
||||
*/
|
||||
#define RCT_REMAP_VIEW_PROPERTY(name, keypath) \
|
||||
- (void)set_##name:(id)json forView:(id)view withDefaultView:(id)defaultView { \
|
||||
if (json) { \
|
||||
if(!RCTSetProperty(view, @#keypath, json)) { \
|
||||
RCTLogMustFix(@"%@ does not have setter for `%s` property", [view class], #name); \
|
||||
} \
|
||||
} else { \
|
||||
[view setValue:[defaultView valueForKeyPath:@#keypath] forKeyPath:@#keypath]; \
|
||||
} \
|
||||
}
|
||||
|
||||
/**
|
||||
* These are useful in cases where the module's superclass handles a
|
||||
* property, but you wish to "unhandle" it, so it will be ignored.
|
||||
*/
|
||||
#define RCT_IGNORE_VIEW_PROPERTY(name) \
|
||||
- (void)set_##name:(id)value forView:(id)view withDefaultView:(id)defaultView {}
|
||||
|
||||
#define RCT_IGNORE_SHADOW_PROPERTY(name) \
|
||||
- (void)set_##name:(id)value forShadowView:(id)view withDefaultView:(id)defaultView {}
|
||||
|
||||
/**
|
||||
* Returns a dictionary of config data passed to JS that defines eligible events
|
||||
* that can be placed on native views. This should return bubbling
|
||||
* directly-dispatched event types and specify what names should be used to
|
||||
* subscribe to either form (bubbling/capturing).
|
||||
*
|
||||
* Returned dictionary should be of the form: @{
|
||||
* @"onTwirl": {
|
||||
* @"phasedRegistrationNames": @{
|
||||
* @"bubbled": @"onTwirl",
|
||||
* @"captured": @"onTwirlCaptured"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
- (NSDictionary *)customBubblingEventTypes;
|
||||
|
||||
/**
|
||||
* Returns a dictionary of config data passed to JS that defines eligible events
|
||||
* that can be placed on native views. This should return non-bubbling
|
||||
* directly-dispatched event types.
|
||||
*
|
||||
* Returned dictionary should be of the form: @{
|
||||
* @"onTwirl": {
|
||||
* @"registrationName": @"onTwirl"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
- (NSDictionary *)customDirectEventTypes;
|
||||
|
||||
/**
|
||||
* To deprecate, hopefully
|
||||
*/
|
||||
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry;
|
||||
|
||||
@end
|
||||
362
ReactKit/Base/RCTExport.m
Normal file
362
ReactKit/Base/RCTExport.m
Normal file
@@ -0,0 +1,362 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTExport.h"
|
||||
|
||||
#import <dlfcn.h>
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import <mach-o/getsect.h>
|
||||
#import <mach-o/dyld.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTModuleMethod.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static NSDictionary *_methodsByModule;
|
||||
|
||||
@interface _RCTExportLoader : NSObject
|
||||
|
||||
@end
|
||||
|
||||
@implementation _RCTExportLoader
|
||||
|
||||
+ (NSString *)methodNameForSelector:(SEL)selector
|
||||
{
|
||||
NSString *methodName = NSStringFromSelector(selector);
|
||||
NSRange colonRange = [methodName rangeOfString:@":"];
|
||||
if (colonRange.location != NSNotFound) {
|
||||
methodName = [methodName substringToIndex:colonRange.location];
|
||||
}
|
||||
return methodName;
|
||||
}
|
||||
|
||||
+ (NSIndexSet *)blockArgumentIndexesForMethod:(Method)method
|
||||
{
|
||||
unsigned int argumentCount = method_getNumberOfArguments(method);
|
||||
|
||||
NSMutableIndexSet *blockArgumentIndexes = [NSMutableIndexSet indexSet];
|
||||
static const char *blockType = @encode(typeof(^{}));
|
||||
for (unsigned int i = 2; i < argumentCount; i++) {
|
||||
char *type = method_copyArgumentType(method, i);
|
||||
if (!strcmp(type, blockType)) {
|
||||
[blockArgumentIndexes addIndex:i - 2];
|
||||
}
|
||||
free(type);
|
||||
}
|
||||
return [blockArgumentIndexes copy];
|
||||
}
|
||||
|
||||
+ (void)load
|
||||
{
|
||||
static uint32_t _exportsLoaded = 0;
|
||||
if (OSAtomicTestAndSetBarrier(1, &_exportsLoaded)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
typedef uint64_t RCTExportValue;
|
||||
typedef struct section_64 RCTExportSection;
|
||||
#define RCTGetSectByNameFromHeader getsectbynamefromheader_64
|
||||
#else
|
||||
typedef uint32_t RCTExportValue;
|
||||
typedef struct section RCTExportSection;
|
||||
#define RCTGetSectByNameFromHeader getsectbynamefromheader
|
||||
#endif
|
||||
|
||||
Dl_info info;
|
||||
dladdr(&RCTExportedMethodsByModule, &info);
|
||||
|
||||
const RCTExportValue mach_header = (RCTExportValue)info.dli_fbase;
|
||||
const RCTExportSection *section = RCTGetSectByNameFromHeader((void *)mach_header, _RCTExportSegmentName, _RCTExportSectionName);
|
||||
|
||||
if (section == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary *methodsByModule = [NSMutableDictionary dictionary];
|
||||
NSCharacterSet *plusMinusCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"+-"];
|
||||
|
||||
for (RCTExportValue addr = section->offset;
|
||||
addr < section->offset + section->size;
|
||||
addr += sizeof(RCTExportEntry)) {
|
||||
|
||||
RCTExportEntry *entry = (RCTExportEntry *)(mach_header + addr);
|
||||
|
||||
NSScanner *scanner = [NSScanner scannerWithString:@(entry->func)];
|
||||
|
||||
NSString *plusMinus;
|
||||
if (![scanner scanCharactersFromSet:plusMinusCharacterSet intoString:&plusMinus]) continue;
|
||||
if (![scanner scanString:@"[" intoString:NULL]) continue;
|
||||
|
||||
NSString *className;
|
||||
if (![scanner scanUpToString:@" " intoString:&className]) continue;
|
||||
[scanner scanString:@" " intoString:NULL];
|
||||
|
||||
NSString *selectorName;
|
||||
if (![scanner scanUpToString:@"]" intoString:&selectorName]) continue;
|
||||
|
||||
Class class = NSClassFromString(className);
|
||||
if (class == Nil) continue;
|
||||
|
||||
SEL selector = NSSelectorFromString(selectorName);
|
||||
Method method = ([plusMinus characterAtIndex:0] == '+' ? class_getClassMethod : class_getInstanceMethod)(class, selector);
|
||||
if (method == nil) continue;
|
||||
|
||||
NSString *JSMethodName = strlen(entry->js_name) ? @(entry->js_name) : [self methodNameForSelector:selector];
|
||||
RCTModuleMethod *moduleMethod =
|
||||
[[RCTModuleMethod alloc] initWithSelector:selector
|
||||
JSMethodName:JSMethodName
|
||||
arity:method_getNumberOfArguments(method) - 2
|
||||
blockArgumentIndexes:[self blockArgumentIndexesForMethod:method]];
|
||||
|
||||
// TODO: store these by class name, not module name, then we don't need to call moduleName here
|
||||
NSString *moduleName = [class respondsToSelector:@selector(moduleName)] ? [class moduleName] : className;
|
||||
NSArray *moduleMap = methodsByModule[moduleName];
|
||||
methodsByModule[moduleName] = (moduleMap != nil) ? [moduleMap arrayByAddingObject:moduleMethod] : @[moduleMethod];
|
||||
}
|
||||
|
||||
_methodsByModule = [methodsByModule copy];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSDictionary *RCTExportedMethodsByModule(void)
|
||||
{
|
||||
return _methodsByModule;
|
||||
}
|
||||
|
||||
NSString *RCTExportedModuleNameAtSortedIndex(NSUInteger index)
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
static NSArray *sortedModuleNames;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sortedModuleNames = [RCTExportedMethodsByModule().allKeys sortedArrayUsingSelector:@selector(compare:)];
|
||||
});
|
||||
return sortedModuleNames[index];
|
||||
}
|
||||
|
||||
static NSString *RCTGuessTypeEncoding(id target, NSString *key, id value, NSString *encoding)
|
||||
{
|
||||
// TODO (#5906496): handle more cases
|
||||
if ([key rangeOfString:@"color" options:NSCaseInsensitiveSearch].location != NSNotFound) {
|
||||
if ([target isKindOfClass:[CALayer class]]) {
|
||||
return @(@encode(CGColorRef));
|
||||
} else {
|
||||
return @"@\"UIColor\"";
|
||||
}
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
static NSDictionary *RCTConvertValue(id value, NSString *encoding)
|
||||
{
|
||||
static NSDictionary *converters = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
|
||||
id (^numberConvert)(id) = ^(id val){
|
||||
return [RCTConvert NSNumber:val];
|
||||
};
|
||||
|
||||
id (^boolConvert)(id) = ^(id val){
|
||||
return @([RCTConvert BOOL:val]);
|
||||
};
|
||||
|
||||
// TODO (#5906496): add the rest of RCTConvert here
|
||||
converters =
|
||||
@{
|
||||
@(@encode(char)): boolConvert,
|
||||
@(@encode(int)): numberConvert,
|
||||
@(@encode(short)): numberConvert,
|
||||
@(@encode(long)): numberConvert,
|
||||
@(@encode(long long)): numberConvert,
|
||||
@(@encode(unsigned char)): numberConvert,
|
||||
@(@encode(unsigned int)): numberConvert,
|
||||
@(@encode(unsigned short)): numberConvert,
|
||||
@(@encode(unsigned long)): numberConvert,
|
||||
@(@encode(unsigned long long)): numberConvert,
|
||||
@(@encode(float)): numberConvert,
|
||||
@(@encode(double)): numberConvert,
|
||||
@(@encode(bool)): boolConvert,
|
||||
@(@encode(UIEdgeInsets)): ^(id val) {
|
||||
return [NSValue valueWithUIEdgeInsets:[RCTConvert UIEdgeInsets:val]];
|
||||
},
|
||||
@(@encode(CGPoint)): ^(id val) {
|
||||
return [NSValue valueWithCGPoint:[RCTConvert CGPoint:val]];
|
||||
},
|
||||
@(@encode(CGSize)): ^(id val) {
|
||||
return [NSValue valueWithCGSize:[RCTConvert CGSize:val]];
|
||||
},
|
||||
@(@encode(CGRect)): ^(id val) {
|
||||
return [NSValue valueWithCGRect:[RCTConvert CGRect:val]];
|
||||
},
|
||||
@(@encode(CGColorRef)): ^(id val) {
|
||||
return (id)[RCTConvert CGColor:val];
|
||||
},
|
||||
@(@encode(CGAffineTransform)): ^(id val) {
|
||||
return [NSValue valueWithCGAffineTransform:[RCTConvert CGAffineTransform:val]];
|
||||
},
|
||||
@(@encode(CATransform3D)): ^(id val) {
|
||||
return [NSValue valueWithCATransform3D:[RCTConvert CATransform3D:val]];
|
||||
},
|
||||
@"@\"NSString\"": ^(id val) {
|
||||
return [RCTConvert NSString:val];
|
||||
},
|
||||
@"@\"NSURL\"": ^(id val) {
|
||||
return [RCTConvert NSURL:val];
|
||||
},
|
||||
@"@\"UIColor\"": ^(id val) {
|
||||
return [RCTConvert UIColor:val];
|
||||
},
|
||||
@"@\"UIImage\"": ^(id val) {
|
||||
return [RCTConvert UIImage:val];
|
||||
},
|
||||
@"@\"NSDate\"": ^(id val) {
|
||||
return [RCTConvert NSDate:val];
|
||||
},
|
||||
@"@\"NSTimeZone\"": ^(id val) {
|
||||
return [RCTConvert NSTimeZone:val];
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Handle null values
|
||||
if (value == [NSNull null] && ![encoding isEqualToString:@"@\"NSNull\""]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Convert value
|
||||
id (^converter)(id) = converters[encoding];
|
||||
return converter ? converter(value) : value;
|
||||
}
|
||||
|
||||
BOOL RCTSetProperty(id target, NSString *keypath, id value)
|
||||
{
|
||||
// Split keypath
|
||||
NSArray *parts = [keypath componentsSeparatedByString:@"."];
|
||||
NSString *key = [parts lastObject];
|
||||
for (NSUInteger i = 0; i < parts.count - 1; i++) {
|
||||
target = [target valueForKey:parts[i]];
|
||||
if (!target) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
// Check target class for property definition
|
||||
NSString *encoding = nil;
|
||||
objc_property_t property = class_getProperty([target class], [key UTF8String]);
|
||||
if (property) {
|
||||
|
||||
// Get type info
|
||||
char *typeEncoding = property_copyAttributeValue(property, "T");
|
||||
encoding = @(typeEncoding);
|
||||
free(typeEncoding);
|
||||
|
||||
} else {
|
||||
|
||||
// Check if setter exists
|
||||
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
|
||||
[[key substringToIndex:1] uppercaseString],
|
||||
[key substringFromIndex:1]]);
|
||||
|
||||
if (![target respondsToSelector:setter]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Get type of first method argument
|
||||
Method method = class_getInstanceMethod([target class], setter);
|
||||
char *typeEncoding = method_copyArgumentType(method, 2);
|
||||
if (typeEncoding) {
|
||||
encoding = @(typeEncoding);
|
||||
free(typeEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
if (encoding.length == 0 || [encoding isEqualToString:@(@encode(id))]) {
|
||||
// Not enough info about the type encoding to be useful, so
|
||||
// try to guess the type from the value and property name
|
||||
encoding = RCTGuessTypeEncoding(target, key, value, encoding);
|
||||
}
|
||||
|
||||
// Special case for numeric encodings, which may be enums
|
||||
if ([value isKindOfClass:[NSString class]] &&
|
||||
[@"iIsSlLqQ" containsString:[encoding substringToIndex:1]]) {
|
||||
|
||||
/**
|
||||
* NOTE: the property names below may seem weird, but it's
|
||||
* because they are tested as case-sensitive suffixes, so
|
||||
* "apitalizationType" will match any of the following
|
||||
*
|
||||
* - capitalizationType
|
||||
* - autocapitalizationType
|
||||
* - autoCapitalizationType
|
||||
* - titleCapitalizationType
|
||||
* - etc.
|
||||
*/
|
||||
static NSDictionary *converters = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
converters =
|
||||
@{
|
||||
@"apitalizationType": ^(id val) {
|
||||
return [RCTConvert UITextAutocapitalizationType:val];
|
||||
},
|
||||
@"eyboardType": ^(id val) {
|
||||
return [RCTConvert UIKeyboardType:val];
|
||||
},
|
||||
@"extAlignment": ^(id val) {
|
||||
return [RCTConvert NSTextAlignment:val];
|
||||
},
|
||||
};
|
||||
});
|
||||
for (NSString *subkey in converters) {
|
||||
if ([key hasSuffix:subkey]) {
|
||||
NSInteger (^converter)(NSString *) = converters[subkey];
|
||||
value = @(converter(value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Another nasty special case
|
||||
if ([target isKindOfClass:[UITextField class]]) {
|
||||
static NSDictionary *specialCases = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
specialCases = @{
|
||||
@"autocapitalizationType": ^(UITextField *f, NSInteger v){ f.autocapitalizationType = v; },
|
||||
@"autocorrectionType": ^(UITextField *f, NSInteger v){ f.autocorrectionType = v; },
|
||||
@"spellCheckingType": ^(UITextField *f, NSInteger v){ f.spellCheckingType = v; },
|
||||
@"keyboardType": ^(UITextField *f, NSInteger v){ f.keyboardType = v; },
|
||||
@"keyboardAppearance": ^(UITextField *f, NSInteger v){ f.keyboardAppearance = v; },
|
||||
@"returnKeyType": ^(UITextField *f, NSInteger v){ f.returnKeyType = v; },
|
||||
@"enablesReturnKeyAutomatically": ^(UITextField *f, NSInteger v){ f.enablesReturnKeyAutomatically = !!v; },
|
||||
@"secureTextEntry": ^(UITextField *f, NSInteger v){ f.secureTextEntry = !!v; }};
|
||||
});
|
||||
|
||||
void (^block)(UITextField *f, NSInteger v) = specialCases[key];
|
||||
if (block)
|
||||
{
|
||||
block(target, [value integerValue]);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Set converted value
|
||||
[target setValue:RCTConvertValue(value, encoding) forKey:key];
|
||||
return YES;
|
||||
}
|
||||
|
||||
BOOL RCTCallSetter(id target, SEL setter, id value)
|
||||
{
|
||||
// Get property name
|
||||
NSString *propertyName = NSStringFromSelector(setter);
|
||||
RCTCAssert([propertyName hasPrefix:@"set"] && [propertyName hasSuffix:@":"],
|
||||
@"%@ is not a valid setter name", propertyName);
|
||||
propertyName = [[[propertyName substringWithRange:(NSRange){3,1}] lowercaseString] stringByAppendingString:[propertyName substringWithRange:(NSRange){4,propertyName.length - 5}]];
|
||||
|
||||
// Set property
|
||||
return RCTSetProperty(target, propertyName, value);
|
||||
}
|
||||
18
ReactKit/Base/RCTImageDownloader.h
Normal file
18
ReactKit/Base/RCTImageDownloader.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error);
|
||||
|
||||
@interface RCTImageDownloader : NSObject
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
- (id)downloadImageForURL:(NSURL *)url
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
block:(RCTImageDownloadBlock)block;
|
||||
|
||||
- (void)cancelDownload:(id)downloadToken;
|
||||
|
||||
@end
|
||||
67
ReactKit/Base/RCTImageDownloader.m
Normal file
67
ReactKit/Base/RCTImageDownloader.m
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTImageDownloader.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
// TODO: something a bit more sophisticated
|
||||
|
||||
@implementation RCTImageDownloader
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
static RCTImageDownloader *sharedInstance;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[self alloc] init];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (id)downloadImageForURL:(NSURL *)url
|
||||
size:(CGSize)size
|
||||
scale:(CGFloat)scale
|
||||
block:(RCTImageDownloadBlock)block
|
||||
{
|
||||
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
|
||||
UIImage *image = [UIImage imageWithData:data scale:scale];
|
||||
|
||||
// TODO: cache compressed image
|
||||
|
||||
CGSize imageSize = size;
|
||||
if (CGSizeEqualToSize(imageSize, CGSizeZero)) {
|
||||
imageSize = image.size;
|
||||
}
|
||||
|
||||
CGFloat imageScale = scale;
|
||||
if (imageScale == 0 || imageScale > image.scale) {
|
||||
imageScale = image.scale;
|
||||
}
|
||||
|
||||
if (image) {
|
||||
// Decompress on background thread
|
||||
UIGraphicsBeginImageContextWithOptions(imageSize, NO, imageScale);
|
||||
[image drawInRect:(CGRect){{0, 0}, imageSize}];
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
// TODO: cache decompressed images at each requested size
|
||||
}
|
||||
|
||||
// Dispatch back to main thread
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
block(image, error);
|
||||
});
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
return task;
|
||||
}
|
||||
|
||||
- (void)cancelDownload:(id)downloadToken
|
||||
{
|
||||
[(NSURLSessionDataTask *)downloadToken cancel];
|
||||
}
|
||||
|
||||
@end
|
||||
16
ReactKit/Base/RCTInvalidating.h
Normal file
16
ReactKit/Base/RCTInvalidating.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// TODO (#5906496): is there a reason for this protocol? It seems to be
|
||||
// used in a number of places where it isn't really required - only the
|
||||
// RCTBridge actually ever calls casts to it - in all other
|
||||
// cases it is simply a way of adding some method definitions to classes
|
||||
|
||||
@protocol RCTInvalidating <NSObject>
|
||||
|
||||
@property (nonatomic, assign, readonly, getter = isValid) BOOL valid;
|
||||
|
||||
- (void)invalidate;
|
||||
|
||||
@end
|
||||
25
ReactKit/Base/RCTJavaScriptAppEngine.h
Executable file
25
ReactKit/Base/RCTJavaScriptAppEngine.h
Executable file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTJavaScriptExecutor.h"
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
/**
|
||||
* Class that allows easy embedding, loading, life-cycle management of a
|
||||
* JavaScript application inside of a native application.
|
||||
* TODO: Before loading new application source, publish global notification in
|
||||
* JavaScript so that applications can clean up resources. (launch blocker).
|
||||
* TODO: Incremental module loading. (low pri).
|
||||
*/
|
||||
@interface RCTJavaScriptAppEngine : NSObject <RCTInvalidating>
|
||||
|
||||
@property (nonatomic, readonly, strong) RCTBridge *bridge;
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
||||
- (void)loadBundleAtURL:(NSURL *)moduleURL useCache:(BOOL)useCache onComplete:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
+ (void)resetCacheForBundleAtURL:(NSURL *)moduleURL;
|
||||
|
||||
@end
|
||||
266
ReactKit/Base/RCTJavaScriptAppEngine.m
Executable file
266
ReactKit/Base/RCTJavaScriptAppEngine.m
Executable file
@@ -0,0 +1,266 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTJavaScriptAppEngine.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#define JS_SERVER_NOT_AVAILABLE @"Could not connect to development server. Ensure node server is running - run 'npm start' from ReactKit root"
|
||||
|
||||
#define CACHE_DIR @"RCTJSBundleCache"
|
||||
|
||||
#pragma mark - Application Engine
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Add window resize rotation events matching the DOM API.
|
||||
* - Device pixel ration hooks.
|
||||
* - Source maps.
|
||||
*/
|
||||
@implementation RCTJavaScriptAppEngine
|
||||
{
|
||||
BOOL _isPaused; // Pauses drawing/updating of the JSView
|
||||
BOOL _pauseOnEnterBackground;
|
||||
CADisplayLink *_displayLink;
|
||||
NSTimer *_runTimer;
|
||||
NSDictionary *_loadedResource;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
RCT_NOT_DESIGNATED_INITIALIZER();
|
||||
}
|
||||
|
||||
/**
|
||||
* `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore
|
||||
* engine in its own dedicated thread.
|
||||
*
|
||||
* TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one
|
||||
* additional GCD dispatch per frame and likely makes it so that other UIThread
|
||||
* operations don't delay the dispatch (so we can begin working in JS much
|
||||
* faster.) Event handling must still be sent via a GCD dispatch, of course.
|
||||
*
|
||||
* We must add the display link to two runloops in order to get setTimeouts to
|
||||
* fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`)
|
||||
* TODO: We can invent a `requestAnimationFrame` and
|
||||
* `requestAvailableAnimationFrame` to control if callbacks can be fired during
|
||||
* an animation.
|
||||
* http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink
|
||||
*
|
||||
*/
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
if ((self = [super init])) {
|
||||
_bridge = bridge;
|
||||
_isPaused = NO;
|
||||
self.pauseOnEnterBackground = YES;
|
||||
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(run:)];
|
||||
if (_displayLink) {
|
||||
[_displayLink setFrameInterval:1];
|
||||
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
|
||||
} else {
|
||||
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
|
||||
_runTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0) target:self selector:@selector(run:) userInfo:nil repeats:YES];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Wait until operations on `javaScriptQueue` are complete.
|
||||
*/
|
||||
- (void)dealloc
|
||||
{
|
||||
RCTAssert(!self.valid, @"-invalidate must be called before -dealloc");
|
||||
}
|
||||
|
||||
#pragma mark - RCTInvalidating
|
||||
|
||||
- (BOOL)isValid
|
||||
{
|
||||
return _displayLink != nil;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[_bridge invalidate];
|
||||
_bridge = nil;
|
||||
|
||||
[_displayLink invalidate];
|
||||
_displayLink = nil;
|
||||
|
||||
// Remove from notification center
|
||||
self.pauseOnEnterBackground = NO;
|
||||
}
|
||||
|
||||
#pragma mark - Run loop
|
||||
|
||||
- (void)run:(CADisplayLink *)sender
|
||||
{
|
||||
if (!_isPaused) {
|
||||
RCTAssertMainThread();
|
||||
[_bridge enqueueUpdateTimers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)pauseRunLoop
|
||||
{
|
||||
if (!_isPaused) {
|
||||
[_displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
|
||||
_isPaused = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resumeRunLoop
|
||||
{
|
||||
if (_isPaused) {
|
||||
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
|
||||
_isPaused = NO;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See warnings from lint: UIApplicationDidBecomeActive fires in a critical
|
||||
* foreground path, and prevents the app from prioritizing the foreground
|
||||
* processing. Consider using
|
||||
* FBApplicationDidFinishEnteringForegroundAndIsNowIdleNotification.
|
||||
*/
|
||||
- (void)setPauseOnEnterBackground:(BOOL)pauses
|
||||
{
|
||||
NSArray *pauseN = @[
|
||||
UIApplicationWillResignActiveNotification,
|
||||
UIApplicationDidEnterBackgroundNotification,
|
||||
UIApplicationWillTerminateNotification
|
||||
];
|
||||
NSArray *resumeN =
|
||||
@[UIApplicationWillEnterForegroundNotification, UIApplicationDidBecomeActiveNotification];
|
||||
|
||||
if (pauses) {
|
||||
[self observeKeyPaths:pauseN selector:@selector(pauseRunLoop)];
|
||||
[self observeKeyPaths:resumeN selector:@selector(resumeRunLoop)];
|
||||
}
|
||||
else {
|
||||
[self removeObserverForKeyPaths:pauseN];
|
||||
[self removeObserverForKeyPaths:resumeN];
|
||||
}
|
||||
_pauseOnEnterBackground = pauses;
|
||||
}
|
||||
|
||||
- (void)removeObserverForKeyPaths:(NSArray*)keyPaths
|
||||
{
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
for (NSString *name in keyPaths) {
|
||||
[nc removeObserver:self name:name object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)observeKeyPaths:(NSArray*)keyPaths selector:(SEL)selector
|
||||
{
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
for (NSString *name in keyPaths) {
|
||||
[nc addObserver:self selector:selector name:name object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Module and script loading
|
||||
|
||||
+ (void)resetCacheForBundleAtURL:(NSURL *)moduleURL
|
||||
{
|
||||
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
||||
NSString *fileDir = [rootPath stringByAppendingPathComponent:CACHE_DIR];
|
||||
NSString *filePath = [fileDir stringByAppendingPathComponent:RCTMD5Hash(moduleURL.absoluteString)];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:filePath error:NULL];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: All loading of script via network or disk should be done in a separate
|
||||
* thread, not the JS thread, and not the main UI thread (launch blocker).
|
||||
*/
|
||||
- (void)loadBundleAtURL:(NSURL *)moduleURL useCache:(BOOL)useCache onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
NSString *cachedFilePath;
|
||||
if (useCache) {
|
||||
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
|
||||
NSString *fileDir = [rootPath stringByAppendingPathComponent:CACHE_DIR];
|
||||
cachedFilePath = [fileDir stringByAppendingPathComponent:RCTMD5Hash(moduleURL.absoluteString)];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:cachedFilePath]) {
|
||||
NSError *error;
|
||||
NSString *rawText = [NSString stringWithContentsOfFile:cachedFilePath encoding:NSUTF8StringEncoding error:&error];
|
||||
if (rawText.length == 0 || error != nil) {
|
||||
if (onComplete) onComplete(error);
|
||||
} else {
|
||||
[self _enqueueLoadBundleResource:rawText url:moduleURL onComplete:onComplete];
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:moduleURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
if (error != nil) {
|
||||
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: JS_SERVER_NOT_AVAILABLE,
|
||||
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
||||
NSUnderlyingErrorKey: error,
|
||||
};
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:error.code
|
||||
userInfo:userInfo];
|
||||
}
|
||||
if (onComplete) onComplete(error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||
if (response.textEncodingName != nil) {
|
||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
|
||||
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
|
||||
NSDictionary *userInfo;
|
||||
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
|
||||
if ([errorDetails isKindOfClass:[NSDictionary class]]) {
|
||||
userInfo = @{
|
||||
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
|
||||
@"stack": @[@{
|
||||
@"methodName": errorDetails[@"description"] ?: @"",
|
||||
@"file": errorDetails[@"filename"] ?: @"",
|
||||
@"lineNumber": errorDetails[@"lineNumber"] ?: @0
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
userInfo = @{NSLocalizedDescriptionKey: rawText};
|
||||
}
|
||||
NSError *serverError = [NSError errorWithDomain:@"JSServer"
|
||||
code:[(NSHTTPURLResponse *)response statusCode]
|
||||
userInfo:userInfo];
|
||||
if (onComplete) onComplete(serverError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (useCache) {
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:cachedFilePath.stringByDeletingLastPathComponent withIntermediateDirectories:YES attributes:nil error:NULL];
|
||||
[rawText writeToFile:cachedFilePath atomically:YES encoding:encoding error:NULL];
|
||||
}
|
||||
|
||||
[self _enqueueLoadBundleResource:rawText url:moduleURL onComplete:onComplete];
|
||||
}];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
- (void)_enqueueLoadBundleResource:(NSString *)rawText url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete
|
||||
{
|
||||
[_bridge enqueueApplicationScript:rawText url:url onComplete:onComplete];
|
||||
}
|
||||
|
||||
@end
|
||||
14
ReactKit/Base/RCTJavaScriptEventDispatcher.h
Normal file
14
ReactKit/Base/RCTJavaScriptEventDispatcher.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
@interface RCTJavaScriptEventDispatcher : NSObject
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
||||
- (void)sendDeviceEventWithArgs:(NSArray *)args;
|
||||
- (void)sendEventWithArgs:(NSArray *)args;
|
||||
- (void)sendTouchesWithArgs:(NSArray *)args;
|
||||
|
||||
@end
|
||||
51
ReactKit/Base/RCTJavaScriptEventDispatcher.m
Normal file
51
ReactKit/Base/RCTJavaScriptEventDispatcher.m
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTJavaScriptEventDispatcher.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTModuleIDs.h"
|
||||
|
||||
@implementation RCTJavaScriptEventDispatcher
|
||||
{
|
||||
RCTBridge *_bridge;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_bridge = bridge;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)sendDeviceEventWithArgs:(NSArray *)args
|
||||
{
|
||||
if (!args) {
|
||||
return;
|
||||
}
|
||||
[_bridge enqueueJSCall:RCTModuleIDDeviceEventEmitter
|
||||
methodID:RCTDeviceEventEmitterEmit
|
||||
args:args];
|
||||
}
|
||||
|
||||
- (void)sendEventWithArgs:(NSArray *)args
|
||||
{
|
||||
if (!args) {
|
||||
return;
|
||||
}
|
||||
[_bridge enqueueJSCall:RCTModuleIDReactIOSEventEmitter
|
||||
methodID:RCTEventEmitterReceiveEvent
|
||||
args:args];
|
||||
}
|
||||
|
||||
- (void)sendTouchesWithArgs:(NSArray *)args
|
||||
{
|
||||
if (!args) {
|
||||
return;
|
||||
}
|
||||
[_bridge enqueueJSCall:RCTModuleIDReactIOSEventEmitter
|
||||
methodID:RCTEventEmitterReceiveTouches
|
||||
args:args];
|
||||
}
|
||||
|
||||
@end
|
||||
35
ReactKit/Base/RCTJavaScriptExecutor.h
Normal file
35
ReactKit/Base/RCTJavaScriptExecutor.h
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
|
||||
#import "RCTInvalidating.h"
|
||||
|
||||
typedef void (^RCTJavaScriptCompleteBlock)(NSError *error);
|
||||
typedef void (^RCTJavaScriptCallback)(id objcValue, NSError *error);
|
||||
|
||||
/**
|
||||
* Abstracts away a JavaScript execution context - we may be running code in a
|
||||
* web view (for debugging purposes), or may be running code in a `JSContext`.
|
||||
*/
|
||||
@protocol RCTJavaScriptExecutor <RCTInvalidating>
|
||||
|
||||
/**
|
||||
* Executes given method with arguments on JS thread and calls the given callback
|
||||
* with JSValue and JSContext as a result of the JS module call.
|
||||
*/
|
||||
- (void)executeJSCall:(NSString *)name
|
||||
method:(NSString *)method
|
||||
arguments:(NSArray *)arguments
|
||||
callback:(RCTJavaScriptCallback)onComplete;
|
||||
|
||||
/**
|
||||
* Runs an application script, and notifies of the script load being complete via `onComplete`.
|
||||
*/
|
||||
- (void)executeApplicationScript:(NSString *)script
|
||||
sourceURL:(NSURL *)url
|
||||
onComplete:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
|
||||
- (void)injectJSONText:(NSString *)script
|
||||
asGlobalObjectNamed:(NSString *)objectName
|
||||
callback:(RCTJavaScriptCompleteBlock)onComplete;
|
||||
@end
|
||||
28
ReactKit/Base/RCTKeyCommands.h
Normal file
28
ReactKit/Base/RCTKeyCommands.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface RCTKeyCommands : NSObject
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
* Register a keyboard command.
|
||||
*/
|
||||
- (void)registerKeyCommandWithInput:(NSString *)input
|
||||
modifierFlags:(UIKeyModifierFlags)flags
|
||||
action:(void (^)(UIKeyCommand *command))block;
|
||||
|
||||
/**
|
||||
* Unregister a keyboard command.
|
||||
*/
|
||||
- (void)unregisterKeyCommandWithInput:(NSString *)input
|
||||
modifierFlags:(UIKeyModifierFlags)flags;
|
||||
|
||||
/**
|
||||
* Check if a command is registered.
|
||||
*/
|
||||
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
|
||||
modifierFlags:(UIKeyModifierFlags)flags;
|
||||
|
||||
@end
|
||||
116
ReactKit/Base/RCTKeyCommands.m
Normal file
116
ReactKit/Base/RCTKeyCommands.m
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTKeyCommands.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTKeyCommands ()
|
||||
|
||||
@property (nonatomic, strong) NSMutableDictionary *commandBindings;
|
||||
|
||||
- (void)RCT_handleKeyCommand:(UIKeyCommand *)key;
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIApplication (RCTKeyCommands)
|
||||
|
||||
- (NSArray *)RCT_keyCommands
|
||||
{
|
||||
NSDictionary *commandBindings = [RCTKeyCommands sharedInstance].commandBindings;
|
||||
return [[self RCT_keyCommands] arrayByAddingObjectsFromArray:[commandBindings allKeys]];
|
||||
}
|
||||
|
||||
- (BOOL)RCT_sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event
|
||||
{
|
||||
if (action == @selector(RCT_handleKeyCommand:)) {
|
||||
[[RCTKeyCommands sharedInstance] RCT_handleKeyCommand:sender];
|
||||
return YES;
|
||||
}
|
||||
return [self RCT_sendAction:action to:target from:sender forEvent:event];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTKeyCommands
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
//swizzle UIApplication
|
||||
RCTSwapInstanceMethods([UIApplication class], @selector(keyCommands), @selector(RCT_keyCommands));
|
||||
RCTSwapInstanceMethods([UIApplication class], @selector(sendAction:to:from:forEvent:), @selector(RCT_sendAction:to:from:forEvent:));
|
||||
}
|
||||
|
||||
static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static RCTKeyCommands *sharedInstance;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[self alloc] init];
|
||||
});
|
||||
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_commandBindings = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)RCT_handleKeyCommand:(UIKeyCommand *)key
|
||||
{
|
||||
// NOTE: We should just be able to do commandBindings[key] here, but curiously, the
|
||||
// lookup seems to return nil sometimes, even if the key is found in the dictionary.
|
||||
// To fix this, we use a linear search, since there won't be many keys anyway
|
||||
|
||||
[_commandBindings enumerateKeysAndObjectsUsingBlock:^(UIKeyCommand *k, void (^block)(UIKeyCommand *), BOOL *stop) {
|
||||
if ([key.input isEqualToString:k.input] && key.modifierFlags == k.modifierFlags) {
|
||||
block(key);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)registerKeyCommandWithInput:(NSString *)input
|
||||
modifierFlags:(UIKeyModifierFlags)flags
|
||||
action:(void (^)(UIKeyCommand *))block
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
|
||||
modifierFlags:flags
|
||||
action:@selector(RCT_handleKeyCommand:)];
|
||||
_commandBindings[command] = block;
|
||||
}
|
||||
|
||||
- (void)unregisterKeyCommandWithInput:(NSString *)input modifierFlags:(UIKeyModifierFlags)flags
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
for (UIKeyCommand *key in [_commandBindings allKeys]) {
|
||||
if ([key.input isEqualToString:input] && key.modifierFlags == flags) {
|
||||
[_commandBindings removeObjectForKey:key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
|
||||
modifierFlags:(UIKeyModifierFlags)flags
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
for (UIKeyCommand *key in [_commandBindings allKeys]) {
|
||||
if ([key.input isEqualToString:input] && key.modifierFlags == flags) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
127
ReactKit/Base/RCTLog.h
Normal file
127
ReactKit/Base/RCTLog.h
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTRedBox.h"
|
||||
|
||||
#define RCTLOG_INFO 1
|
||||
#define RCTLOG_WARN 2
|
||||
#define RCTLOG_ERROR 3
|
||||
#define RCTLOG_MUSTFIX 4
|
||||
|
||||
// If set to e.g. `RCTLOG_ERROR`, will assert after logging the first error.
|
||||
#if DEBUG
|
||||
#define RCTLOG_FATAL_LEVEL RCTLOG_MUSTFIX
|
||||
#define RCTLOG_REDBOX_LEVEL RCTLOG_ERROR
|
||||
#else
|
||||
#define RCTLOG_FATAL_LEVEL (RCTLOG_MUSTFIX + 1)
|
||||
#define RCTLOG_REDBOX_LEVEL (RCTLOG_MUSTFIX + 1)
|
||||
#endif
|
||||
|
||||
// If defined, only log messages that match this regex will fatal
|
||||
#define RCTLOG_FATAL_REGEX nil
|
||||
|
||||
#define _RCTLog(__RCTLog__level, ...) do { \
|
||||
NSString *__RCTLog__levelStr; \
|
||||
switch(__RCTLog__level) { \
|
||||
case RCTLOG_INFO: __RCTLog__levelStr = @"info"; break; \
|
||||
case RCTLOG_WARN: __RCTLog__levelStr = @"warn"; break; \
|
||||
case RCTLOG_ERROR: __RCTLog__levelStr = @"error"; break; \
|
||||
case RCTLOG_MUSTFIX: __RCTLog__levelStr = @"mustfix"; break; \
|
||||
} \
|
||||
NSString *__RCTLog__msg = _RCTLogObjects(RCTLogFormat(__VA_ARGS__), __RCTLog__levelStr); \
|
||||
if (__RCTLog__level >= RCTLOG_FATAL_LEVEL) { \
|
||||
BOOL __RCTLog__fail = YES; \
|
||||
if (RCTLOG_FATAL_REGEX) { \
|
||||
NSError *__RCTLog__e; \
|
||||
NSRegularExpression *__RCTLog__regex = [NSRegularExpression regularExpressionWithPattern:RCTLOG_FATAL_REGEX options:0 error:&__RCTLog__e]; \
|
||||
__RCTLog__fail = [__RCTLog__regex numberOfMatchesInString:__RCTLog__msg options:0 range:NSMakeRange(0, [__RCTLog__msg length])] > 0; \
|
||||
} \
|
||||
RCTCAssert(!__RCTLog__fail, @"RCTLOG_FATAL_LEVEL %@: %@", __RCTLog__levelStr, __RCTLog__msg); \
|
||||
} \
|
||||
if (__RCTLog__level >= RCTLOG_REDBOX_LEVEL) { \
|
||||
RCTRedBox *__RCTLog__redBox = [RCTRedBox sharedInstance]; \
|
||||
[__RCTLog__redBox showErrorMessage:__RCTLog__msg]; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define RCTLog(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__)
|
||||
#define RCTLogInfo(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__)
|
||||
#define RCTLogWarn(...) _RCTLog(RCTLOG_WARN, __VA_ARGS__)
|
||||
#define RCTLogError(...) _RCTLog(RCTLOG_ERROR, __VA_ARGS__)
|
||||
#define RCTLogMustFix(...) _RCTLog(RCTLOG_MUSTFIX, __VA_ARGS__)
|
||||
|
||||
#define RCTLogFormat(...) _RCTLogFormat(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__)
|
||||
#define RCTLogFormatString(...) _RCTLogFormatString(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__)
|
||||
|
||||
static inline NSString *_RCTLogPreamble(const char *file, int lineNumber, const char *funcName) {
|
||||
NSString *threadName = [[NSThread currentThread] name];
|
||||
NSString *fileName=[[NSString stringWithUTF8String:file] lastPathComponent];
|
||||
if (!threadName || threadName.length <= 0) {
|
||||
threadName = [NSString stringWithFormat:@"%p", [NSThread currentThread]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"[RCTLog][tid:%@][%@:%d]>", threadName, fileName, lineNumber];
|
||||
}
|
||||
|
||||
// Returns array of objects. First arg is a simple string to print, remaining args are objects to pass through to the debugger so they are
|
||||
// inspectable in the console.
|
||||
static inline NSArray *_RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5);
|
||||
static inline NSArray *_RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
NSString *preamble = _RCTLogPreamble(file, lineNumber, funcName);
|
||||
|
||||
// Pull out NSObjects so we can pass them through as inspectable objects to the js debugger
|
||||
NSArray *formatParts = [format componentsSeparatedByString:@"%"];
|
||||
NSMutableArray *objects = [NSMutableArray arrayWithObject:preamble];
|
||||
BOOL valid = YES;
|
||||
for (int i = 0; i < formatParts.count; i++) {
|
||||
if (i == 0) { // first part is always a string
|
||||
[objects addObject:formatParts[i]];
|
||||
} else {
|
||||
if (valid && [formatParts[i] length] && [formatParts[i] characterAtIndex:0] == '@') {
|
||||
id obj = va_arg(args, id);
|
||||
if (obj) {
|
||||
[objects addObject:obj];
|
||||
} else {
|
||||
[objects addObject:@"null"];
|
||||
}
|
||||
[objects addObject:[formatParts[i] substringFromIndex:1]]; // remove formatting char
|
||||
} else {
|
||||
// We could determine the type (double, int?) of the va_arg by parsing the formatPart, but for now we just bail.
|
||||
valid = NO;
|
||||
[objects addObject:[NSString stringWithFormat:@"unknown object for %%%@", formatParts[i]]];
|
||||
}
|
||||
}
|
||||
}
|
||||
va_end(args);
|
||||
va_start(args, format);
|
||||
NSString *strOut = [preamble stringByAppendingString:[[NSString alloc] initWithFormat:format arguments:args]];
|
||||
va_end(args);
|
||||
NSMutableArray *objectsOut = [NSMutableArray arrayWithObject:strOut];
|
||||
[objectsOut addObjectsFromArray:objects];
|
||||
return objectsOut;
|
||||
}
|
||||
|
||||
static inline NSString *_RCTLogFormatString(const char *, int, const char *, NSString *, ...) NS_FORMAT_FUNCTION(4,5);
|
||||
static inline NSString *_RCTLogFormatString(const char *file, int lineNumber, const char *funcName, NSString *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start (args, format);
|
||||
NSString *body = [[NSString alloc] initWithFormat:format arguments:args];
|
||||
va_end (args);
|
||||
return [NSString stringWithFormat:@"%@ %@", _RCTLogPreamble(file, lineNumber, funcName), body];
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
NSString *_RCTLogObjects(NSArray *objects, NSString *level);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
typedef void (^RCTLogFunction)(NSString *format, NSString *str);
|
||||
void RCTInjectLogFunction(RCTLogFunction func);
|
||||
33
ReactKit/Base/RCTLog.m
Normal file
33
ReactKit/Base/RCTLog.m
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTLog.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
|
||||
static RCTLogFunction injectedLogFunction;
|
||||
|
||||
void RCTInjectLogFunction(RCTLogFunction func) {
|
||||
injectedLogFunction = func;
|
||||
}
|
||||
|
||||
// TODO (#5906496): // kinda ugly that this is tied to RCTBridge
|
||||
NSString *_RCTLogObjects(NSArray *objects, NSString *level)
|
||||
{
|
||||
NSString *str = objects[0];
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
if ([RCTBridge hasValidJSExecutor]) {
|
||||
fprintf(stderr, "%s\n", [str UTF8String]); // don't print timestamps and other junk
|
||||
[RCTBridge log:objects level:level];
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
// Print normal errors with timestamps when not in simulator.
|
||||
// Non errors are already compiled out above, so log as error here.
|
||||
if (injectedLogFunction) {
|
||||
injectedLogFunction(@">\n %@", str);
|
||||
} else {
|
||||
NSLog(@">\n %@", str);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
102
ReactKit/Base/RCTModuleIDs.h
Normal file
102
ReactKit/Base/RCTModuleIDs.h
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* All of this will be replaced with an auto-generated bridge.
|
||||
*/
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RCTJSModuleIDs) {
|
||||
RCTModuleIDReactIOSEventEmitter,
|
||||
RCTModuleIDJSTimers, // JS timer tracking module
|
||||
RCTModuleIDReactIOS,
|
||||
RCTModuleIDBundler,
|
||||
RCTModuleIDDimensions,
|
||||
RCTModuleIDDeviceEventEmitter,
|
||||
RCTModuleIDNativeAppEventEmitter,
|
||||
RCTModuleIDRenderingPerf,
|
||||
};
|
||||
|
||||
/**
|
||||
* JS module `RCTIOSEventEmitter`.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, RCTEventEmitterRemoteMethodIDs) {
|
||||
RCTEventEmitterReceiveEvent = 0,
|
||||
RCTEventEmitterReceiveTouches
|
||||
};
|
||||
|
||||
/**
|
||||
* `RCTEventEmitter`: Encoding of parameters.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, RCTEventType) {
|
||||
RCTEventTap = 1,
|
||||
RCTEventVisibleCellsChange,
|
||||
RCTEventNavigateBack,
|
||||
RCTEventNavRightButtonTap,
|
||||
RCTEventChange,
|
||||
RCTEventTextFieldDidFocus,
|
||||
RCTEventTextFieldWillBlur,
|
||||
RCTEventTextFieldSubmitEditing,
|
||||
RCTEventTextFieldEndEditing,
|
||||
RCTEventTextInput,
|
||||
RCTEventLongPress,
|
||||
RCTEventTouchStart,
|
||||
RCTEventTouchMove,
|
||||
RCTEventTouchCancel,
|
||||
RCTEventTouchEnd,
|
||||
RCTEventScrollBeginDrag,
|
||||
RCTEventScroll,
|
||||
RCTEventScrollEndDrag,
|
||||
RCTEventSelectionChange,
|
||||
RCTEventMomentumScrollBegin,
|
||||
RCTEventMomentumScrollEnd,
|
||||
RCTEventPullToRefresh,
|
||||
RCTEventScrollAnimationEnd,
|
||||
RCTEventLoadingStart,
|
||||
RCTEventLoadingFinish,
|
||||
RCTEventLoadingError,
|
||||
RCTEventNavigationProgress,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RCTKeyCode) {
|
||||
RCTKeyCodeBackspace = 8,
|
||||
RCTKeyCodeReturn = 13,
|
||||
};
|
||||
|
||||
/**
|
||||
* JS timer tracking module.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, RCTJSTimersMethodIDs) {
|
||||
RCTJSTimersCallTimers = 0
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RCTReactIOSMethodIDs) {
|
||||
RCTReactIOSUnmountComponentAtNodeAndRemoveContainer = 0,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RCTBundlerMethodIDs) {
|
||||
RCTBundlerRunApplication = 0
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RCTDimensionsMethodIDs) {
|
||||
RCTDimensionsSet = 0
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RCTRenderingPerfMethodIDs) {
|
||||
RCTRenderingPerfToggle = 0,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RCTDeviceEventEmitterMethodIDs) {
|
||||
RCTDeviceEventEmitterEmit = 0
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, RCTNativeAppEventEmitterMethodIDs) {
|
||||
RCTNativeAppEventEmitterEmit = 0
|
||||
};
|
||||
|
||||
@interface RCTModuleIDs : NSObject
|
||||
|
||||
+ (NSDictionary *)config;
|
||||
|
||||
@end
|
||||
|
||||
102
ReactKit/Base/RCTModuleIDs.m
Normal file
102
ReactKit/Base/RCTModuleIDs.m
Normal file
@@ -0,0 +1,102 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTModuleIDs.h"
|
||||
|
||||
@implementation RCTModuleIDs
|
||||
|
||||
/**
|
||||
* Configures invocations from IOS -> JS. Simply passes the name of the key in
|
||||
* the configuration object `require('ReactIOSEventEmitter')`.
|
||||
*/
|
||||
+ (NSDictionary *)config
|
||||
{
|
||||
return @{
|
||||
@"Dimensions": @{
|
||||
@"moduleID": @(RCTModuleIDDimensions),
|
||||
@"methods": @{
|
||||
@"set": @{
|
||||
@"methodID": @(RCTDimensionsSet),
|
||||
@"type": @"local"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@"RCTRenderingPerf": @{
|
||||
@"moduleID": @(RCTModuleIDRenderingPerf),
|
||||
@"methods": @{
|
||||
@"toggle": @{
|
||||
@"methodID": @(RCTRenderingPerfToggle),
|
||||
@"type": @"local"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@"RCTDeviceEventEmitter": @{
|
||||
@"moduleID": @(RCTModuleIDDeviceEventEmitter),
|
||||
@"methods": @{
|
||||
@"emit": @{
|
||||
@"methodID": @(RCTDeviceEventEmitterEmit),
|
||||
@"type": @"local"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@"RCTEventEmitter": @{
|
||||
@"moduleID": @(RCTModuleIDReactIOSEventEmitter),
|
||||
@"methods": @{
|
||||
@"receiveEvent": @{
|
||||
@"methodID": @(RCTEventEmitterReceiveEvent),
|
||||
@"type": @"local"
|
||||
},
|
||||
@"receiveTouches": @{
|
||||
@"methodID": @(RCTEventEmitterReceiveTouches),
|
||||
@"type": @"local"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@"RCTNativeAppEventEmitter": @{
|
||||
@"moduleID": @(RCTModuleIDNativeAppEventEmitter),
|
||||
@"methods": @{
|
||||
@"emit": @{
|
||||
@"methodID": @(RCTDeviceEventEmitterEmit),
|
||||
@"type": @"local"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@"RCTJSTimers": @{
|
||||
@"moduleID": @(RCTModuleIDJSTimers),
|
||||
@"methods": @{
|
||||
// Last argument is the callback.
|
||||
@"callTimers": @{
|
||||
@"methodID": @(RCTJSTimersCallTimers),
|
||||
@"type": @"local"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@"ReactIOS": @{
|
||||
@"moduleID": @(RCTModuleIDReactIOS),
|
||||
@"methods": @{
|
||||
@"unmountComponentAtNodeAndRemoveContainer": @{
|
||||
@"methodID": @(RCTReactIOSUnmountComponentAtNodeAndRemoveContainer),
|
||||
@"type": @"local"
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@"Bundler": @{
|
||||
@"moduleID": @(RCTModuleIDBundler),
|
||||
@"methods": @{
|
||||
@"runApplication": @{
|
||||
@"methodID": @(RCTBundlerRunApplication),
|
||||
@"type": @"local"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
19
ReactKit/Base/RCTModuleMethod.h
Normal file
19
ReactKit/Base/RCTModuleMethod.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@protocol RCTNativeModule;
|
||||
|
||||
@interface RCTModuleMethod : NSObject
|
||||
|
||||
- (instancetype)initWithSelector:(SEL)selector
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
arity:(NSUInteger)arity
|
||||
blockArgumentIndexes:(NSIndexSet *)blockArgumentIndexes;
|
||||
|
||||
@property (readonly, nonatomic, assign) SEL selector;
|
||||
@property (readonly, nonatomic, copy) NSString *JSMethodName;
|
||||
@property (readonly, nonatomic, assign) NSUInteger arity;
|
||||
@property (readonly, nonatomic, copy) NSIndexSet *blockArgumentIndexes;
|
||||
|
||||
@end
|
||||
35
ReactKit/Base/RCTModuleMethod.m
Normal file
35
ReactKit/Base/RCTModuleMethod.m
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTModuleMethod.h"
|
||||
|
||||
@implementation RCTModuleMethod
|
||||
|
||||
- (instancetype)initWithSelector:(SEL)selector
|
||||
JSMethodName:(NSString *)JSMethodName
|
||||
arity:(NSUInteger)arity
|
||||
blockArgumentIndexes:(NSIndexSet *)blockArgumentIndexes
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_selector = selector;
|
||||
_JSMethodName = [JSMethodName copy];
|
||||
_arity = arity;
|
||||
_blockArgumentIndexes = [blockArgumentIndexes copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
NSString *blocks = @"no block args";
|
||||
if (self.blockArgumentIndexes.count > 0) {
|
||||
NSMutableString *indexString = [NSMutableString string];
|
||||
[self.blockArgumentIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
|
||||
[indexString appendFormat:@", %tu", idx];
|
||||
}];
|
||||
blocks = [NSString stringWithFormat:@"block args at %@", [indexString substringFromIndex:2]];
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"<%@: %p; exports -%@ as %@; %@>", NSStringFromClass(self.class), self, NSStringFromSelector(self.selector), self.JSMethodName, blocks];
|
||||
}
|
||||
|
||||
@end
|
||||
33
ReactKit/Base/RCTMultiTouchGestureRecognizer.h
Normal file
33
ReactKit/Base/RCTMultiTouchGestureRecognizer.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
// Copyright 2013-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class RCTMultiTouchGestureRecognizer;
|
||||
|
||||
@protocol RCTMultiTouchGestureRecognizerListener <NSObject>
|
||||
|
||||
- (void)handleTouchesStarted:(NSSet *)startedTouches
|
||||
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
|
||||
withEvent:(UIEvent *)event;
|
||||
|
||||
- (void)handleTouchesMoved:(NSSet *)movedTouches
|
||||
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
|
||||
withEvent:(UIEvent *)event;
|
||||
|
||||
- (void)handleTouchesEnded:(NSSet *)endedTouches
|
||||
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
|
||||
withEvent:(UIEvent *)event;
|
||||
|
||||
- (void)handleTouchesCancelled:(NSSet *)cancelledTouches
|
||||
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
|
||||
withEvent:(UIEvent *)event;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTMultiTouchGestureRecognizer : UIGestureRecognizer
|
||||
|
||||
@property (nonatomic, weak) id<RCTMultiTouchGestureRecognizerListener> touchEventDelegate;
|
||||
|
||||
@end
|
||||
103
ReactKit/Base/RCTMultiTouchGestureRecognizer.m
Normal file
103
ReactKit/Base/RCTMultiTouchGestureRecognizer.m
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
// Copyright 2013-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTMultiTouchGestureRecognizer.h"
|
||||
|
||||
#import <UIKit/UIGestureRecognizerSubclass.h>
|
||||
|
||||
#import "RCTLog.h"
|
||||
|
||||
@implementation RCTMultiTouchGestureRecognizer
|
||||
|
||||
- (void)touchesBegan:(NSSet *)startedTouches withEvent:(UIEvent *)event {
|
||||
[super touchesBegan:startedTouches withEvent:event];
|
||||
if (!self.touchEventDelegate) {
|
||||
RCTLogError(@"No Touch Delegate for Simple Gesture Recognizer");
|
||||
return;
|
||||
}
|
||||
self.state = UIGestureRecognizerStateBegan;
|
||||
[self.touchEventDelegate handleTouchesStarted:startedTouches
|
||||
forMultiGestureRecognizer:self
|
||||
withEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesMoved:(NSSet *)movedTouches withEvent:(UIEvent *)event {
|
||||
[super touchesMoved:movedTouches withEvent:event];
|
||||
if (self.state == UIGestureRecognizerStateFailed) {
|
||||
return;
|
||||
}
|
||||
[self.touchEventDelegate handleTouchesMoved:movedTouches
|
||||
forMultiGestureRecognizer:self
|
||||
withEvent:event];
|
||||
}
|
||||
|
||||
- (void)touchesEnded:(NSSet *)endedTouches withEvent:(UIEvent *)event {
|
||||
[super touchesEnded:endedTouches withEvent:event];
|
||||
[self.touchEventDelegate handleTouchesEnded:endedTouches
|
||||
forMultiGestureRecognizer:self
|
||||
withEvent:event];
|
||||
// These may be a different set than the total set of touches.
|
||||
NSSet *touches = [event touchesForGestureRecognizer:self];
|
||||
|
||||
BOOL hasEnded = [self _allTouchesAreCanceledOrEnded:touches];
|
||||
|
||||
if (hasEnded) {
|
||||
self.state = UIGestureRecognizerStateEnded;
|
||||
} else if ([self _anyTouchesChanged:touches]) {
|
||||
self.state = UIGestureRecognizerStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)touchesCancelled:(NSSet *)cancelledTouches withEvent:(UIEvent *)event {
|
||||
[super touchesCancelled:cancelledTouches withEvent:event];
|
||||
[self.touchEventDelegate handleTouchesCancelled:cancelledTouches
|
||||
forMultiGestureRecognizer:self
|
||||
withEvent:event];
|
||||
// These may be a different set than the total set of touches.
|
||||
NSSet *touches = [event touchesForGestureRecognizer:self];
|
||||
|
||||
BOOL hasCanceled = [self _allTouchesAreCanceledOrEnded:touches];
|
||||
|
||||
if (hasCanceled) {
|
||||
self.state = UIGestureRecognizerStateFailed;
|
||||
} else if ([self _anyTouchesChanged:touches]) {
|
||||
self.state = UIGestureRecognizerStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
- (BOOL)_allTouchesAreCanceledOrEnded:(NSSet *)touches
|
||||
{
|
||||
for (UITouch *touch in touches) {
|
||||
if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
|
||||
return NO;
|
||||
} else if (touch.phase == UITouchPhaseStationary) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)_anyTouchesChanged:(NSSet *)touches
|
||||
{
|
||||
for (UITouch *touch in touches) {
|
||||
if (touch.phase == UITouchPhaseBegan || touch.phase == UITouchPhaseMoved) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
16
ReactKit/Base/RCTRedBox.h
Normal file
16
ReactKit/Base/RCTRedBox.h
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface RCTRedBox : NSObject
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message;
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details;
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack;
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack;
|
||||
|
||||
- (void)dismiss;
|
||||
|
||||
@end
|
||||
294
ReactKit/Base/RCTRedBox.m
Normal file
294
ReactKit/Base/RCTRedBox.m
Normal file
@@ -0,0 +1,294 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTRedBox.h"
|
||||
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@interface RCTRedBoxWindow : UIWindow <UITableViewDelegate, UITableViewDataSource>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTRedBoxWindow
|
||||
{
|
||||
UIView *_rootView;
|
||||
UITableView *_stackTraceTableView;
|
||||
|
||||
NSString *_lastErrorMessage;
|
||||
NSArray *_lastStackTrace;
|
||||
|
||||
UITableViewCell *_cachedMessageCell;
|
||||
}
|
||||
|
||||
- (id)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if ((self = [super initWithFrame:frame])) {
|
||||
|
||||
self.windowLevel = UIWindowLevelStatusBar + 5;
|
||||
self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1];
|
||||
self.hidden = YES;
|
||||
|
||||
UIViewController *rootController = [[UIViewController alloc] init];
|
||||
self.rootViewController = rootController;
|
||||
_rootView = rootController.view;
|
||||
_rootView.backgroundColor = [UIColor clearColor];
|
||||
|
||||
const CGFloat buttonHeight = 60;
|
||||
|
||||
CGRect detailsFrame = _rootView.bounds;
|
||||
detailsFrame.size.height -= buttonHeight;
|
||||
|
||||
_stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain];
|
||||
_stackTraceTableView.delegate = self;
|
||||
_stackTraceTableView.dataSource = self;
|
||||
_stackTraceTableView.backgroundColor = [UIColor clearColor];
|
||||
_stackTraceTableView.separatorColor = [[UIColor whiteColor] colorWithAlphaComponent:0.3];
|
||||
_stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
[_rootView addSubview:_stackTraceTableView];
|
||||
|
||||
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
dismissButton.titleLabel.font = [UIFont systemFontOfSize:14];
|
||||
[dismissButton setTitle:@"Dismiss (ESC)" forState:UIControlStateNormal];
|
||||
[dismissButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5] forState:UIControlStateNormal];
|
||||
[dismissButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
|
||||
[dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||||
reloadButton.titleLabel.font = [UIFont systemFontOfSize:14];
|
||||
[reloadButton setTitle:@"Reload JS (\u2318R)" forState:UIControlStateNormal];
|
||||
[reloadButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5] forState:UIControlStateNormal];
|
||||
[reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
|
||||
[reloadButton addTarget:self action:@selector(reload) forControlEvents:UIControlEventTouchUpInside];
|
||||
|
||||
CGFloat buttonWidth = self.bounds.size.width / 2;
|
||||
dismissButton.frame = CGRectMake(0, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
|
||||
reloadButton.frame = CGRectMake(buttonWidth, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
|
||||
[_rootView addSubview:dismissButton];
|
||||
[_rootView addSubview:reloadButton];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)openStackFrameInEditor:(NSDictionary *)stackFrame
|
||||
{
|
||||
NSData *stackFrameJSON = [RCTJSONStringify(stackFrame, nil) dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSString *postLength = [NSString stringWithFormat:@"%lu", (unsigned long)[stackFrameJSON length]];
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
|
||||
request.URL = [NSURL URLWithString:@"http://localhost:8081/open-stack-frame"];
|
||||
request.HTTPMethod = @"POST";
|
||||
request.HTTPBody = stackFrameJSON;
|
||||
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:nil];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow
|
||||
{
|
||||
_lastStackTrace = stack;
|
||||
_lastErrorMessage = message;
|
||||
_cachedMessageCell = [self reuseCell:nil forErrorMessage:message];
|
||||
[_stackTraceTableView reloadData];
|
||||
[_stackTraceTableView setNeedsLayout];
|
||||
|
||||
if (self.hidden && shouldShow) {
|
||||
[_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
|
||||
atScrollPosition:UITableViewScrollPositionTop
|
||||
animated:NO];
|
||||
[self makeKeyAndVisible];
|
||||
[self becomeFirstResponder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dismiss
|
||||
{
|
||||
self.hidden = YES;
|
||||
}
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
[RCTRootView reloadAll];
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
#pragma mark - TableView
|
||||
|
||||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
|
||||
{
|
||||
return section == 0 ? 1 : [_lastStackTrace count];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([indexPath section] == 0) {
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"msg-cell"];
|
||||
return [self reuseCell:cell forErrorMessage:_lastErrorMessage];
|
||||
}
|
||||
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
|
||||
NSUInteger index = [indexPath row];
|
||||
NSDictionary *stackFrame = _lastStackTrace[index];
|
||||
return [self reuseCell:cell forStackFrame:stackFrame];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString *)message
|
||||
{
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"msg-cell"];
|
||||
cell.textLabel.textColor = [UIColor whiteColor];
|
||||
cell.textLabel.font = [UIFont boldSystemFontOfSize:16];
|
||||
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
cell.textLabel.numberOfLines = 0;
|
||||
cell.detailTextLabel.textColor = [UIColor whiteColor];
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
cell.selectionStyle = UITableViewCellSelectionStyleNone;
|
||||
}
|
||||
|
||||
cell.textLabel.text = message;
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(NSDictionary *)stackFrame
|
||||
{
|
||||
if (!cell) {
|
||||
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"];
|
||||
cell.textLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9];
|
||||
cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14];
|
||||
cell.detailTextLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7];
|
||||
cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11];
|
||||
cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle;
|
||||
cell.backgroundColor = [UIColor clearColor];
|
||||
cell.selectedBackgroundView = [[UIView alloc] init];
|
||||
cell.selectedBackgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.2];
|
||||
}
|
||||
|
||||
cell.textLabel.text = stackFrame[@"methodName"];
|
||||
cell.detailTextLabel.text = cell.detailTextLabel.text = [NSString stringWithFormat:@"%@:%@", [stackFrame[@"file"] lastPathComponent], stackFrame[@"lineNumber"]];
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([indexPath section] == 0) {
|
||||
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
|
||||
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
|
||||
|
||||
NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16],
|
||||
NSParagraphStyleAttributeName: paragraphStyle};
|
||||
CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
|
||||
return ceil(boundingRect.size.height) + 40;
|
||||
} else {
|
||||
return 44;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
|
||||
{
|
||||
if ([indexPath section] == 1) {
|
||||
NSUInteger row = [indexPath row];
|
||||
NSDictionary *stackFrame = _lastStackTrace[row];
|
||||
[self openStackFrameInEditor:stackFrame];
|
||||
}
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
}
|
||||
|
||||
#pragma mark - Key commands
|
||||
|
||||
- (NSArray *)keyCommands
|
||||
{
|
||||
// NOTE: We could use RCTKeyCommands for this, but since
|
||||
// we control this window, we can use the standard, non-hacky
|
||||
// mechanism instead
|
||||
|
||||
return @[
|
||||
|
||||
// Dismiss red box
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape
|
||||
modifierFlags:0
|
||||
action:@selector(dismiss)],
|
||||
|
||||
// Reload
|
||||
[UIKeyCommand keyCommandWithInput:@"r"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:@selector(reload)]
|
||||
];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTRedBox
|
||||
{
|
||||
RCTRedBoxWindow *_window;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance
|
||||
{
|
||||
static RCTRedBox *_sharedInstance;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_sharedInstance = [[RCTRedBox alloc] init];
|
||||
});
|
||||
return _sharedInstance;
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message
|
||||
{
|
||||
[self showErrorMessage:message withStack:nil showIfHidden:YES];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details
|
||||
{
|
||||
NSString *combinedMessage = message;
|
||||
if (details) {
|
||||
combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details];
|
||||
}
|
||||
[self showErrorMessage:combinedMessage];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack showIfHidden:YES];
|
||||
}
|
||||
|
||||
- (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack
|
||||
{
|
||||
[self showErrorMessage:message withStack:stack showIfHidden:NO];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
|
||||
dispatch_block_t block = ^{
|
||||
if (!_window) {
|
||||
_window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||
}
|
||||
[_window showErrorMessage:message withStack:stack showIfHidden:shouldShow];
|
||||
};
|
||||
if ([NSThread isMainThread]) {
|
||||
block();
|
||||
} else {
|
||||
dispatch_async(dispatch_get_main_queue(), block);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
- (void)dismiss
|
||||
{
|
||||
[_window dismiss];
|
||||
}
|
||||
|
||||
@end
|
||||
20
ReactKit/Base/RCTRootView.h
Normal file
20
ReactKit/Base/RCTRootView.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@protocol RCTJavaScriptExecutor;
|
||||
|
||||
@interface RCTRootView : UIView
|
||||
|
||||
@property (nonatomic, strong) NSURL *scriptURL;
|
||||
@property (nonatomic, copy) NSString *moduleName;
|
||||
@property (nonatomic, copy) NSDictionary *initialProperties;
|
||||
@property (nonatomic, strong) id<RCTJavaScriptExecutor> executor;
|
||||
|
||||
/**
|
||||
* Reload this root view, or all root views, respectively.
|
||||
*/
|
||||
- (void)reload;
|
||||
+ (void)reloadAll;
|
||||
|
||||
@end
|
||||
190
ReactKit/Base/RCTRootView.m
Normal file
190
ReactKit/Base/RCTRootView.m
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTRootView.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTContextExecutor.h"
|
||||
#import "RCTJavaScriptAppEngine.h"
|
||||
#import "RCTJavaScriptEventDispatcher.h"
|
||||
#import "RCTModuleIDs.h"
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTShadowView.h"
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTTouchHandler.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "RCTViewManager.h"
|
||||
#import "UIView+ReactKit.h"
|
||||
#import "RCTKeyCommands.h"
|
||||
|
||||
NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification";
|
||||
|
||||
@implementation RCTRootView
|
||||
{
|
||||
dispatch_queue_t _shadowQueue;
|
||||
RCTBridge *_bridge;
|
||||
RCTJavaScriptAppEngine *_appEngine;
|
||||
RCTTouchHandler *_touchHandler;
|
||||
}
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
|
||||
// Register Cmd-R as a global refresh key
|
||||
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:^(UIKeyCommand *command) {
|
||||
[self reloadAll];
|
||||
}];
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super initWithCoder:aDecoder];
|
||||
if (!self) return nil;
|
||||
|
||||
[self setUp];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (!self) return nil;
|
||||
|
||||
[self setUp];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
// TODO: does it make sense to do this here? What if there's more than one host view?
|
||||
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
// Every root view that is created must have a unique react tag.
|
||||
// Numbering of these tags goes from 1, 11, 21, 31, etc
|
||||
static NSInteger rootViewTag = 1;
|
||||
self.reactTag = @(rootViewTag);
|
||||
rootViewTag += 10;
|
||||
|
||||
// Add reload observer
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(reload)
|
||||
name:RCTRootViewReloadNotification
|
||||
object:nil];
|
||||
self.backgroundColor = [UIColor whiteColor];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
}
|
||||
|
||||
- (void)bundleFinishedLoading:(NSError *)error
|
||||
{
|
||||
if (error != nil) {
|
||||
[[RCTRedBox sharedInstance] showErrorMessage:error.localizedDescription withDetails:error.localizedFailureReason];
|
||||
} else {
|
||||
|
||||
[_bridge.uiManager registerRootView:self];
|
||||
|
||||
NSString *moduleName = _moduleName ?: @"";
|
||||
NSDictionary *appParameters = @{
|
||||
@"rootTag": self.reactTag ?: @0,
|
||||
@"initialProps": self.initialProperties ?: @{},
|
||||
};
|
||||
[_appEngine.bridge enqueueJSCall:RCTModuleIDBundler
|
||||
methodID:RCTBundlerRunApplication
|
||||
args:@[moduleName, appParameters]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)loadBundle
|
||||
{
|
||||
// Clear view
|
||||
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||
|
||||
if (!_scriptURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
RCTJavaScriptCompleteBlock callback = ^(NSError *error) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf bundleFinishedLoading:error];
|
||||
});
|
||||
};
|
||||
|
||||
[_executor invalidate];
|
||||
[_appEngine invalidate];
|
||||
[_bridge invalidate];
|
||||
|
||||
_executor = [[RCTContextExecutor alloc] init];
|
||||
_bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor
|
||||
shadowQueue:_shadowQueue
|
||||
javaScriptModulesConfig:[RCTModuleIDs config]];
|
||||
|
||||
_appEngine = [[RCTJavaScriptAppEngine alloc] initWithBridge:_bridge];
|
||||
_touchHandler = [[RCTTouchHandler alloc] initWithEventDispatcher:_bridge.eventDispatcher rootView:self];
|
||||
|
||||
[_appEngine loadBundleAtURL:_scriptURL useCache:NO onComplete:callback];
|
||||
}
|
||||
|
||||
- (void)setScriptURL:(NSURL *)scriptURL
|
||||
{
|
||||
if ([_scriptURL isEqual:scriptURL]) {
|
||||
return;
|
||||
}
|
||||
|
||||
_scriptURL = scriptURL;
|
||||
[self loadBundle];
|
||||
}
|
||||
|
||||
- (void)setExecutor:(id<RCTJavaScriptExecutor>)executor
|
||||
{
|
||||
RCTAssert(!_bridge, @"You may only change the Javascript Executor prior to loading a script bundle.");
|
||||
_executor = executor;
|
||||
}
|
||||
|
||||
- (BOOL)isReactRootView
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)reload
|
||||
{
|
||||
[RCTJavaScriptAppEngine resetCacheForBundleAtURL:_scriptURL];
|
||||
[self loadBundle];
|
||||
}
|
||||
|
||||
+ (void)reloadAll
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRootViewReloadNotification object:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Key commands
|
||||
|
||||
- (NSArray *)keyCommands
|
||||
{
|
||||
return @[
|
||||
|
||||
// Reload
|
||||
[UIKeyCommand keyCommandWithInput:@"r"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:@selector(reload)]
|
||||
];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
24
ReactKit/Base/RCTScrollDispatcher.h
Normal file
24
ReactKit/Base/RCTScrollDispatcher.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class RCTJavaScriptEventDispatcher;
|
||||
|
||||
/**
|
||||
* Handles throttling of scroll events that are dispatched to JavaScript.
|
||||
*/
|
||||
@interface RCTScrollDispatcher : NSObject
|
||||
|
||||
@property (nonatomic, readwrite, assign) NSInteger throttleScrollCallbackMS;
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTJavaScriptEventDispatcher *)dispatcher;
|
||||
|
||||
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
|
||||
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag;
|
||||
|
||||
@end
|
||||
|
||||
131
ReactKit/Base/RCTScrollDispatcher.m
Normal file
131
ReactKit/Base/RCTScrollDispatcher.m
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTScrollDispatcher.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTEventExtractor.h"
|
||||
#import "RCTJavaScriptEventDispatcher.h"
|
||||
#import "UIView+ReactKit.h"
|
||||
|
||||
@implementation RCTScrollDispatcher
|
||||
{
|
||||
RCTJavaScriptEventDispatcher *_eventDispatcher;
|
||||
NSTimeInterval _lastScrollDispatchTime;
|
||||
BOOL _allowNextScrollNoMatterWhat;
|
||||
NSMutableDictionary *_cachedChildFrames;
|
||||
CGPoint _lastContentOffset;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTJavaScriptEventDispatcher *)dispatcher
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_eventDispatcher = dispatcher;
|
||||
_throttleScrollCallbackMS = 0;
|
||||
_lastScrollDispatchTime = CACurrentMediaTime();
|
||||
_cachedChildFrames = [NSMutableDictionary new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray *)_getUpdatedChildFrames:(UIScrollView *)scrollView forUpdateKey:(NSString *)updateKey
|
||||
{
|
||||
NSArray *children = [scrollView.subviews[0] reactSubviews];
|
||||
NSMutableArray *updatedChildFrames = [NSMutableArray new];
|
||||
NSMutableArray *cachedFrames = _cachedChildFrames[updateKey];
|
||||
if (!cachedFrames) {
|
||||
cachedFrames = [[NSMutableArray alloc] initWithCapacity:children.count];
|
||||
_cachedChildFrames[updateKey] = cachedFrames;
|
||||
}
|
||||
for (int ii = 0; ii < children.count; ii++) {
|
||||
CGRect newFrame = [children[ii] frame];
|
||||
if (cachedFrames.count <= ii || !CGRectEqualToRect(newFrame, [cachedFrames[ii] CGRectValue])) {
|
||||
[updatedChildFrames addObject:
|
||||
@{
|
||||
@"index": @(ii),
|
||||
@"x": @(newFrame.origin.x),
|
||||
@"y": @(newFrame.origin.y),
|
||||
@"width": @(newFrame.size.width),
|
||||
@"height": @(newFrame.size.height),
|
||||
}];
|
||||
NSValue *frameObj = [NSValue valueWithCGRect:newFrame];
|
||||
if (cachedFrames.count <= ii) {
|
||||
[cachedFrames addObject:frameObj];
|
||||
} else {
|
||||
cachedFrames[ii] = frameObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
return updatedChildFrames;
|
||||
}
|
||||
|
||||
- (void)_dispatchScroll:(UIScrollView *)scrollView forUpdateKey:(NSString *)updateKey reactTag:(NSNumber *)reactTag
|
||||
{
|
||||
NSTimeInterval now = CACurrentMediaTime();
|
||||
NSTimeInterval dt = now - _lastScrollDispatchTime;
|
||||
NSMutableDictionary *mutableNativeObj = [[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag] mutableCopy];
|
||||
if (updateKey) {
|
||||
NSArray *updatedChildFrames = [self _getUpdatedChildFrames:scrollView forUpdateKey:updateKey];
|
||||
if (updatedChildFrames.count > 0) {
|
||||
mutableNativeObj[@"updatedChildFrames"] = updatedChildFrames;
|
||||
}
|
||||
}
|
||||
mutableNativeObj[@"velocity"] = @{
|
||||
@"x": @((scrollView.contentOffset.x - _lastContentOffset.x) / dt),
|
||||
@"y": @((scrollView.contentOffset.y - _lastContentOffset.y) / dt),
|
||||
};
|
||||
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
|
||||
type:RCTEventScroll
|
||||
nativeEventObj:mutableNativeObj]];
|
||||
_lastScrollDispatchTime = now;
|
||||
_lastContentOffset = scrollView.contentOffset;
|
||||
_allowNextScrollNoMatterWhat = NO;
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
|
||||
{
|
||||
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
|
||||
type:RCTEventScrollAnimationEnd
|
||||
nativeEventObj:[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag]]];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidScroll:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
|
||||
{
|
||||
NSTimeInterval now = CACurrentMediaTime();
|
||||
NSTimeInterval throttleScrollCallbackSeconds = _throttleScrollCallbackMS / 1000.0f;
|
||||
if (_allowNextScrollNoMatterWhat ||
|
||||
(_throttleScrollCallbackMS != 0 && throttleScrollCallbackSeconds < (now - _lastScrollDispatchTime))) {
|
||||
[self _dispatchScroll:scrollView forUpdateKey:@"didScroll" reactTag:reactTag];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
|
||||
{
|
||||
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
|
||||
type:RCTEventMomentumScrollBegin
|
||||
nativeEventObj:[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag]]];
|
||||
}
|
||||
|
||||
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
|
||||
{
|
||||
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
|
||||
type:RCTEventMomentumScrollEnd
|
||||
nativeEventObj:[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag]]];
|
||||
}
|
||||
|
||||
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
|
||||
{
|
||||
_allowNextScrollNoMatterWhat = YES;
|
||||
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
|
||||
type:RCTEventScrollBeginDrag
|
||||
nativeEventObj:[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag]]];
|
||||
}
|
||||
|
||||
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView reactTag:(NSNumber *)reactTag
|
||||
{
|
||||
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:reactTag
|
||||
type:RCTEventScrollEndDrag
|
||||
nativeEventObj:[RCTEventExtractor scrollEventObject:scrollView reactTag:reactTag]]];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
18
ReactKit/Base/RCTScrollableProtocol.h
Normal file
18
ReactKit/Base/RCTScrollableProtocol.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
/**
|
||||
* Contains any methods related to scrolling. Any `RCTView` that has scrolling
|
||||
* features should implement these methods.
|
||||
*/
|
||||
@protocol RCTScrollableProtocol
|
||||
|
||||
@property (nonatomic, readwrite, weak) NSObject<UIScrollViewDelegate> *nativeMainScrollDelegate;
|
||||
@property (nonatomic, readonly) CGSize contentSize;
|
||||
|
||||
- (void)scrollToOffset:(CGPoint)offset;
|
||||
- (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated;
|
||||
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated;
|
||||
|
||||
@end
|
||||
30
ReactKit/Base/RCTSparseArray.h
Normal file
30
ReactKit/Base/RCTSparseArray.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface RCTSparseArray : NSObject <NSCopying>
|
||||
|
||||
- (instancetype)init;
|
||||
- (instancetype)initWithCapacity:(NSUInteger)capacity;
|
||||
- (instancetype)initWithSparseArray:(RCTSparseArray *)sparseArray;
|
||||
|
||||
+ (instancetype)sparseArray;
|
||||
+ (instancetype)sparseArrayWithCapacity:(NSUInteger)capacity;
|
||||
+ (instancetype)sparseArrayWithSparseArray:(RCTSparseArray *)sparseArray;
|
||||
|
||||
// Use nil object to remove at idx.
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx;
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)idx;
|
||||
|
||||
// Use nil obj to remove at key.
|
||||
- (void)setObject:(id)obj forKeyedSubscript:(NSNumber *)key;
|
||||
- (id)objectForKeyedSubscript:(NSNumber *)key;
|
||||
|
||||
@property (readonly, nonatomic) NSUInteger count;
|
||||
@property (readonly, nonatomic, copy) NSArray *allIndexes;
|
||||
@property (readonly, nonatomic, copy) NSArray *allObjects;
|
||||
|
||||
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSNumber *idx, BOOL *stop))block;
|
||||
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id obj, NSNumber *idx, BOOL *stop))block;
|
||||
|
||||
@end
|
||||
116
ReactKit/Base/RCTSparseArray.m
Normal file
116
ReactKit/Base/RCTSparseArray.m
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTSparseArray.h"
|
||||
|
||||
@implementation RCTSparseArray
|
||||
{
|
||||
NSMutableDictionary *_storage;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
return [self initWithCapacity:0];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCapacity:(NSUInteger)capacity
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_storage = [NSMutableDictionary dictionaryWithCapacity:capacity];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithSparseArray:(RCTSparseArray *)sparseArray
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_storage = [sparseArray->_storage copy];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithStorage:(NSDictionary *)storage
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
_storage = [storage copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (instancetype)sparseArray
|
||||
{
|
||||
return [[self alloc] init];
|
||||
}
|
||||
|
||||
+ (instancetype)sparseArrayWithCapacity:(NSUInteger)capacity
|
||||
{
|
||||
return [[self alloc] initWithCapacity:capacity];
|
||||
}
|
||||
|
||||
+ (instancetype)sparseArrayWithSparseArray:(RCTSparseArray *)sparseArray
|
||||
{
|
||||
return [[self alloc] initWithSparseArray:sparseArray];
|
||||
}
|
||||
|
||||
- (id)objectAtIndexedSubscript:(NSUInteger)idx
|
||||
{
|
||||
return _storage[@(idx)];
|
||||
}
|
||||
|
||||
- (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
|
||||
{
|
||||
self[@(idx)] = obj;
|
||||
}
|
||||
|
||||
- (id)objectForKeyedSubscript:(NSNumber *)key
|
||||
{
|
||||
return _storage[key];
|
||||
}
|
||||
|
||||
- (void)setObject:(id)obj forKeyedSubscript:(NSNumber *)key
|
||||
{
|
||||
if (obj) {
|
||||
_storage[key] = obj;
|
||||
} else {
|
||||
[_storage removeObjectForKey:key];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSUInteger)count
|
||||
{
|
||||
return _storage.count;
|
||||
}
|
||||
|
||||
- (NSArray *)allIndexes
|
||||
{
|
||||
return _storage.allKeys;
|
||||
}
|
||||
|
||||
- (NSArray *)allObjects
|
||||
{
|
||||
return _storage.allValues;
|
||||
}
|
||||
|
||||
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSNumber *idx, BOOL *stop))block
|
||||
{
|
||||
NSParameterAssert(block != nil);
|
||||
[_storage enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id obj, BOOL *stop) {
|
||||
block(obj, key, stop);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts usingBlock:(void (^)(id obj, NSNumber *idx, BOOL *stop))block
|
||||
{
|
||||
NSParameterAssert(block != nil);
|
||||
[_storage enumerateKeysAndObjectsWithOptions:opts usingBlock:^(NSNumber *key, id obj, BOOL *stop) {
|
||||
block(obj, key, stop);
|
||||
}];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
return [[[self class] allocWithZone:zone] initWithSparseArray:self];
|
||||
}
|
||||
|
||||
@end
|
||||
32
ReactKit/Base/RCTTouchHandler.h
Normal file
32
ReactKit/Base/RCTTouchHandler.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class RCTJavaScriptEventDispatcher;
|
||||
|
||||
@interface RCTTouchHandler : NSObject
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher
|
||||
rootView:(UIView *)rootView;
|
||||
|
||||
@property (nonatomic, readwrite, strong) RCTJavaScriptEventDispatcher *eventDispatcher;
|
||||
|
||||
/**
|
||||
* Maintaining the set of active touches by the time they started touching.
|
||||
*/
|
||||
@property (nonatomic, readonly, strong) NSMutableArray *orderedTouches;
|
||||
|
||||
/**
|
||||
* Array managed in parallel to `orderedTouches` tracking original `reactTag`
|
||||
* for each touch. This must be kept track of because `UIKit` destroys the
|
||||
* touch targets if touches are canceled and we have no other way to recover
|
||||
* this information.
|
||||
*/
|
||||
@property (nonatomic, readonly, strong) NSMutableArray *orderedTouchStartTags;
|
||||
|
||||
/**
|
||||
* IDs that uniquely represent a touch among all of the active touches.
|
||||
*/
|
||||
@property (nonatomic, readonly, strong) NSMutableArray *orderedTouchIDs;
|
||||
|
||||
@end
|
||||
296
ReactKit/Base/RCTTouchHandler.m
Normal file
296
ReactKit/Base/RCTTouchHandler.m
Normal file
@@ -0,0 +1,296 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTTouchHandler.h"
|
||||
|
||||
#import "RCTAssert.h"
|
||||
#import "RCTEventExtractor.h"
|
||||
#import "RCTJavaScriptEventDispatcher.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTMultiTouchGestureRecognizer.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "UIView+ReactKit.h"
|
||||
|
||||
@interface RCTTouchHandler () <RCTMultiTouchGestureRecognizerListener, UIGestureRecognizerDelegate>
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTTouchHandler
|
||||
{
|
||||
UIView *_rootView;
|
||||
NSMutableArray *_gestureRecognizers;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
RCT_NOT_DESIGNATED_INITIALIZER();
|
||||
}
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTJavaScriptEventDispatcher *)eventDispatcher
|
||||
rootView:(UIView *)rootView
|
||||
{
|
||||
if (self = [super init]) {
|
||||
RCTAssert(eventDispatcher != nil, @"Expect an event dispatcher");
|
||||
RCTAssert(rootView != nil, @"Expect a root view");
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_rootView = rootView;
|
||||
_gestureRecognizers = [NSMutableArray new];
|
||||
_orderedTouches = [[NSMutableArray alloc] init];
|
||||
_orderedTouchStartTags = [[NSMutableArray alloc] init];
|
||||
_orderedTouchIDs = [[NSMutableArray alloc] init];
|
||||
[self _loadGestureRecognizers];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self removeGestureRecognizers];
|
||||
}
|
||||
|
||||
#pragma mark - Gesture Recognizers
|
||||
|
||||
- (void)_loadGestureRecognizers
|
||||
{
|
||||
[self _addRecognizerForEvent:RCTEventTap];
|
||||
[self _addRecognizerForEvent:RCTEventLongPress];
|
||||
[self _addRecognizerForSimpleTouchEvents];
|
||||
}
|
||||
|
||||
- (void)_addRecognizerForSimpleTouchEvents
|
||||
{
|
||||
RCTMultiTouchGestureRecognizer *multiTouchRecognizer =
|
||||
[[RCTMultiTouchGestureRecognizer alloc] initWithTarget:self action:@selector(handleMultiTouchGesture:)];
|
||||
multiTouchRecognizer.touchEventDelegate = self;
|
||||
[self _addRecognizer:multiTouchRecognizer];
|
||||
}
|
||||
|
||||
- (void)_addRecognizerForEvent:(RCTEventType)event
|
||||
{
|
||||
UIGestureRecognizer *recognizer = nil;
|
||||
switch (event) {
|
||||
case RCTEventTap:
|
||||
recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
|
||||
((UITapGestureRecognizer *)recognizer).numberOfTapsRequired = 1;
|
||||
break;
|
||||
|
||||
case RCTEventVisibleCellsChange:
|
||||
case RCTEventNavigateBack:
|
||||
case RCTEventNavRightButtonTap:
|
||||
case RCTEventChange:
|
||||
case RCTEventTextFieldDidFocus:
|
||||
case RCTEventTextFieldWillBlur:
|
||||
case RCTEventTextFieldSubmitEditing:
|
||||
case RCTEventTextFieldEndEditing:
|
||||
case RCTEventScroll:
|
||||
break;
|
||||
|
||||
case RCTEventLongPress:
|
||||
recognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
|
||||
break;
|
||||
default:
|
||||
RCTLogError(@"Unrecognized event type for gesture: %zd", event);
|
||||
|
||||
}
|
||||
[self _addRecognizer:recognizer];
|
||||
}
|
||||
|
||||
- (void)_addRecognizer:(UIGestureRecognizer *)recognizer
|
||||
{
|
||||
// `cancelsTouchesInView` is needed in order to be used as a top level event delegated recognizer. Otherwise, lower
|
||||
// level components not build using RCT, will fail to recognize gestures.
|
||||
recognizer.cancelsTouchesInView = NO;
|
||||
recognizer.delegate = self;
|
||||
[_gestureRecognizers addObject:recognizer];
|
||||
[_rootView addGestureRecognizer:recognizer];
|
||||
}
|
||||
|
||||
- (void)removeGestureRecognizers
|
||||
{
|
||||
for (UIGestureRecognizer *recognizer in _gestureRecognizers) {
|
||||
[recognizer setDelegate:nil];
|
||||
[recognizer removeTarget:nil action:NULL];
|
||||
[_rootView removeGestureRecognizer:recognizer];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Bookkeeping for touch indices
|
||||
|
||||
- (void)_recordNewTouches:(NSSet *)touches
|
||||
{
|
||||
for (UITouch *touch in touches) {
|
||||
NSUInteger currentIndex = [_orderedTouches indexOfObject:touch];
|
||||
if (currentIndex != NSNotFound) {
|
||||
RCTLogError(@"Touch is already recorded. This is a critical bug.");
|
||||
[_orderedTouches removeObjectAtIndex:currentIndex];
|
||||
[_orderedTouchStartTags removeObjectAtIndex:currentIndex];
|
||||
[_orderedTouchIDs removeObjectAtIndex:currentIndex];
|
||||
}
|
||||
NSNumber *touchStartTag = [RCTEventExtractor touchStartTarget:touch inView:_rootView];
|
||||
|
||||
// Get new, unique touch id
|
||||
const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices
|
||||
NSInteger touchID = ([_orderedTouchIDs.lastObject integerValue] + 1) % RCTMaxTouches;
|
||||
for (NSNumber *n in _orderedTouchIDs) {
|
||||
NSInteger usedID = [n integerValue];
|
||||
if (usedID == touchID) {
|
||||
// ID has already been used, try next value
|
||||
touchID ++;
|
||||
} else if (usedID > touchID) {
|
||||
// If usedID > touchID, touchID must be unique, so we can stop looking
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[_orderedTouches addObject:touch];
|
||||
[_orderedTouchStartTags addObject:touchStartTag];
|
||||
[_orderedTouchIDs addObject:@(touchID)];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_recordRemovedTouches:(NSSet *)touches
|
||||
{
|
||||
for (UITouch *touch in touches) {
|
||||
NSUInteger currentIndex = [_orderedTouches indexOfObject:touch];
|
||||
if (currentIndex == NSNotFound) {
|
||||
RCTLogError(@"Touch is already removed. This is a critical bug.");
|
||||
} else {
|
||||
[_orderedTouches removeObjectAtIndex:currentIndex];
|
||||
[_orderedTouchStartTags removeObjectAtIndex:currentIndex];
|
||||
[_orderedTouchIDs removeObjectAtIndex:currentIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Gesture Recognizer Delegate Callbacks
|
||||
|
||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSArray *)_indicesOfTouches:(NSSet *)touchSet inArray:(NSArray *)array
|
||||
{
|
||||
NSMutableArray *result = [[NSMutableArray alloc] init];
|
||||
for (UITouch *touch in touchSet) {
|
||||
[result addObject:@([array indexOfObject:touch])];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)handleTouchesStarted:(NSSet *)startedTouches
|
||||
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
|
||||
withEvent:(UIEvent *)event
|
||||
{
|
||||
// "start" has to record new touches before extracting the event.
|
||||
// "end"/"cancel" needs to remove the touch *after* extracting the event.
|
||||
[self _recordNewTouches:startedTouches];
|
||||
NSArray *indicesOfStarts = [self _indicesOfTouches:startedTouches inArray:_orderedTouches];
|
||||
NSArray *args =
|
||||
[RCTEventExtractor touchEventArgsForOrderedTouches:_orderedTouches
|
||||
orderedStartTags:_orderedTouchStartTags
|
||||
orderedTouchIDs:_orderedTouchIDs
|
||||
changedIndices:indicesOfStarts
|
||||
type:RCTEventTouchStart
|
||||
view:_rootView];
|
||||
[_eventDispatcher sendTouchesWithArgs:args];
|
||||
}
|
||||
|
||||
- (void)handleTouchesMoved:(NSSet *)movedTouches
|
||||
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
|
||||
withEvent:(UIEvent *)event
|
||||
{
|
||||
NSArray *indicesOfMoves = [self _indicesOfTouches:movedTouches inArray:_orderedTouches];
|
||||
NSArray *args =
|
||||
[RCTEventExtractor touchEventArgsForOrderedTouches:_orderedTouches
|
||||
orderedStartTags:_orderedTouchStartTags
|
||||
orderedTouchIDs:_orderedTouchIDs
|
||||
changedIndices:indicesOfMoves
|
||||
type:RCTEventTouchMove
|
||||
view:_rootView];
|
||||
[_eventDispatcher sendTouchesWithArgs:args];
|
||||
}
|
||||
|
||||
- (void)handleTouchesEnded:(NSSet *)endedTouches
|
||||
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
|
||||
withEvent:(UIEvent *)event
|
||||
{
|
||||
NSArray *indicesOfEnds = [self _indicesOfTouches:endedTouches inArray:_orderedTouches];
|
||||
NSArray *args =
|
||||
[RCTEventExtractor touchEventArgsForOrderedTouches:_orderedTouches
|
||||
orderedStartTags:_orderedTouchStartTags
|
||||
orderedTouchIDs:_orderedTouchIDs
|
||||
changedIndices:indicesOfEnds
|
||||
type:RCTEventTouchEnd
|
||||
view:_rootView];
|
||||
[_eventDispatcher sendTouchesWithArgs:args];
|
||||
[self _recordRemovedTouches:endedTouches];
|
||||
}
|
||||
|
||||
- (void)handleTouchesCancelled:(NSSet *)cancelledTouches
|
||||
forMultiGestureRecognizer:(RCTMultiTouchGestureRecognizer *)multiTouchGestureRecognizer
|
||||
withEvent:(UIEvent *)event
|
||||
{
|
||||
NSArray *indicesOfCancels = [self _indicesOfTouches:cancelledTouches inArray:_orderedTouches];
|
||||
NSArray *args =
|
||||
[RCTEventExtractor touchEventArgsForOrderedTouches:_orderedTouches
|
||||
orderedStartTags:_orderedTouchStartTags
|
||||
orderedTouchIDs:_orderedTouchIDs
|
||||
changedIndices:indicesOfCancels
|
||||
type:RCTEventTouchCancel
|
||||
view:_rootView];
|
||||
[_eventDispatcher sendTouchesWithArgs:args];
|
||||
[self _recordRemovedTouches:cancelledTouches];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Needed simply to be provided to the `RCTMultiTouchGestureRecognizer`. If not,
|
||||
* other gestures are cancelled.
|
||||
*/
|
||||
- (void)handleMultiTouchGesture:(UIGestureRecognizer *)sender
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
- (NSDictionary *)_nativeEventForGesture:(UIGestureRecognizer *)sender
|
||||
target:(UIView *)target
|
||||
reactTargetView:(UIView *)reactTargetView
|
||||
{
|
||||
return @{
|
||||
@"state": @(sender.state),
|
||||
@"target": reactTargetView.reactTag,
|
||||
};
|
||||
}
|
||||
|
||||
- (void)handleTap:(UIGestureRecognizer *)sender
|
||||
{
|
||||
// This calculation may not be accurate when views overlap.
|
||||
UIView *touchedView = sender.view;
|
||||
CGPoint location = [sender locationInView:touchedView];
|
||||
UIView *target = [touchedView hitTest:location withEvent:nil];
|
||||
|
||||
// Views outside the RCT system can be present (e.g., UITableViewCellContentView)
|
||||
// they have no registry. we can safely ignore events happening on them.
|
||||
if (sender.state == UIGestureRecognizerStateEnded) {
|
||||
UIView *reactTargetView = [RCTUIManager closestReactAncestor:target];
|
||||
if (reactTargetView) {
|
||||
NSMutableDictionary *nativeEvent =[[self _nativeEventForGesture:sender target:target reactTargetView:reactTargetView] mutableCopy];
|
||||
nativeEvent[@"pageX"] = @(location.x);
|
||||
nativeEvent[@"pageY"] = @(location.y);
|
||||
CGPoint locInView = [sender.view convertPoint:location toView:target];
|
||||
nativeEvent[@"locationX"] = @(locInView.x);
|
||||
nativeEvent[@"locationY"] = @(locInView.y);
|
||||
[_eventDispatcher sendEventWithArgs:[RCTEventExtractor eventArgs:[reactTargetView reactTag]
|
||||
type:RCTEventTap
|
||||
nativeEventObj:nativeEvent]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handleLongPress:(UIGestureRecognizer *)sender
|
||||
{
|
||||
}
|
||||
|
||||
@end
|
||||
36
ReactKit/Base/RCTUtils.h
Normal file
36
ReactKit/Base/RCTUtils.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <tgmath.h>
|
||||
|
||||
#import "RCTAssert.h"
|
||||
|
||||
// Macro to indicate when inherited initializer is not to be used
|
||||
#define RCT_NOT_DESIGNATED_INITIALIZER() \
|
||||
do { \
|
||||
RCTAssert(NO, @"%@ is not the designated initializer for instances of %@.", NSStringFromSelector(_cmd), [self class]); \
|
||||
return nil; \
|
||||
} while (0)
|
||||
|
||||
// Utility functions for JSON object <-> string serialization/deserialization
|
||||
NSString *RCTJSONStringify(id jsonObject, NSError **error);
|
||||
id RCTJSONParse(NSString *jsonString, NSError **error);
|
||||
|
||||
// Get MD5 hash of a string
|
||||
NSString *RCTMD5Hash(NSString *string);
|
||||
|
||||
// Get screen scale in a thread-safe way
|
||||
CGFloat RCTScreenScale(void);
|
||||
|
||||
// Round float coordinates to nearest whole screen pixel (not point)
|
||||
CGFloat RCTRoundPixelValue(CGFloat value);
|
||||
CGFloat RCTCeilPixelValue(CGFloat value);
|
||||
CGFloat RCTFloorPixelValue(CGFloat value);
|
||||
|
||||
// Get current time, for precise performance metrics
|
||||
NSTimeInterval RCTTGetAbsoluteTime(void);
|
||||
|
||||
// Method swizzling
|
||||
void RCTSwapClassMethods(Class cls, SEL original, SEL replacement);
|
||||
void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement);
|
||||
122
ReactKit/Base/RCTUtils.m
Normal file
122
ReactKit/Base/RCTUtils.m
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#import <CommonCrypto/CommonCrypto.h>
|
||||
#import <mach/mach_time.h>
|
||||
#import <objc/runtime.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NSString *RCTJSONStringify(id jsonObject, NSError **error)
|
||||
{
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonObject options:0 error:error];
|
||||
return jsonData ? [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] : nil;
|
||||
}
|
||||
|
||||
id RCTJSONParse(NSString *jsonString, NSError **error)
|
||||
{
|
||||
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:error];
|
||||
}
|
||||
|
||||
NSString *RCTMD5Hash(NSString *string)
|
||||
{
|
||||
const char *str = [string UTF8String];
|
||||
unsigned char result[CC_MD5_DIGEST_LENGTH];
|
||||
CC_MD5(str, (CC_LONG)strlen(str), result);
|
||||
|
||||
return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
result[0], result[1], result[2], result[3],
|
||||
result[4], result[5], result[6], result[7],
|
||||
result[8], result[9], result[10], result[11],
|
||||
result[12], result[13], result[14], result[15]
|
||||
];
|
||||
}
|
||||
|
||||
CGFloat RCTScreenScale()
|
||||
{
|
||||
static CGFloat scale = -1;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if (![NSThread isMainThread]) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
scale = [UIScreen mainScreen].scale;
|
||||
});
|
||||
} else {
|
||||
scale = [UIScreen mainScreen].scale;
|
||||
}
|
||||
});
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
CGFloat RCTRoundPixelValue(CGFloat value)
|
||||
{
|
||||
CGFloat scale = RCTScreenScale();
|
||||
return round(value * scale) / scale;
|
||||
}
|
||||
|
||||
CGFloat RCTCeilPixelValue(CGFloat value)
|
||||
{
|
||||
CGFloat scale = RCTScreenScale();
|
||||
return ceil(value * scale) / scale;
|
||||
}
|
||||
|
||||
CGFloat RCTFloorPixelValue(CGFloat value)
|
||||
{
|
||||
CGFloat scale = RCTScreenScale();
|
||||
return floor(value * scale) / scale;
|
||||
}
|
||||
|
||||
NSTimeInterval RCTTGetAbsoluteTime(void)
|
||||
{
|
||||
static struct mach_timebase_info tb_info = {0};
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
int ret = mach_timebase_info(&tb_info);
|
||||
assert(0 == ret);
|
||||
});
|
||||
|
||||
uint64_t timeInNanoseconds = (mach_absolute_time() * tb_info.numer) / tb_info.denom;
|
||||
return ((NSTimeInterval)timeInNanoseconds) / 1000000;
|
||||
}
|
||||
|
||||
void RCTSwapClassMethods(Class cls, SEL original, SEL replacement)
|
||||
{
|
||||
Method originalMethod = class_getClassMethod(cls, original);
|
||||
IMP originalImplementation = method_getImplementation(originalMethod);
|
||||
const char *originalArgTypes = method_getTypeEncoding(originalMethod);
|
||||
|
||||
Method replacementMethod = class_getClassMethod(cls, replacement);
|
||||
IMP replacementImplementation = method_getImplementation(replacementMethod);
|
||||
const char *replacementArgTypes = method_getTypeEncoding(replacementMethod);
|
||||
|
||||
if (class_addMethod(cls, original, replacementImplementation, replacementArgTypes))
|
||||
{
|
||||
class_replaceMethod(cls, replacement, originalImplementation, originalArgTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
method_exchangeImplementations(originalMethod, replacementMethod);
|
||||
}
|
||||
}
|
||||
|
||||
void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement)
|
||||
{
|
||||
Method originalMethod = class_getInstanceMethod(cls, original);
|
||||
IMP originalImplementation = method_getImplementation(originalMethod);
|
||||
const char *originalArgTypes = method_getTypeEncoding(originalMethod);
|
||||
|
||||
Method replacementMethod = class_getInstanceMethod(cls, replacement);
|
||||
IMP replacementImplementation = method_getImplementation(replacementMethod);
|
||||
const char *replacementArgTypes = method_getTypeEncoding(replacementMethod);
|
||||
|
||||
if (class_addMethod(cls, original, replacementImplementation, replacementArgTypes))
|
||||
{
|
||||
class_replaceMethod(cls, replacement, originalImplementation, originalArgTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
method_exchangeImplementations(originalMethod, replacementMethod);
|
||||
}
|
||||
}
|
||||
29
ReactKit/Base/RCTViewNodeProtocol.h
Normal file
29
ReactKit/Base/RCTViewNodeProtocol.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
/**
|
||||
|
||||
* Logical node in a tree of application components. Both `ShadowView`s and
|
||||
* `UIView+ReactKit`s conform to this. Allows us to write utilities that
|
||||
* reason about trees generally.
|
||||
*/
|
||||
@protocol RCTViewNodeProtocol <NSObject>
|
||||
|
||||
@property (nonatomic, strong) NSNumber *reactTag;
|
||||
|
||||
- (void)insertReactSubview:(id<RCTViewNodeProtocol>)subview atIndex:(NSInteger)atIndex;
|
||||
- (void)removeReactSubview:(id<RCTViewNodeProtocol>)subview;
|
||||
- (NSMutableArray *)reactSubviews;
|
||||
|
||||
// View is an RCTRootView
|
||||
- (BOOL)isReactRootView;
|
||||
|
||||
@optional
|
||||
|
||||
// TODO: Deprecate this
|
||||
- (void)reactBridgeDidFinishTransaction;
|
||||
|
||||
// Invoked when react determines that the view will be removed from the view
|
||||
// hierarchy and never replaced.
|
||||
- (void)reactWillDestroy;
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user