Updates from Thu 26 Mar

- [React Native] Fix incorrect if-statement in RCTGeolocation | Alex Akers
- [ReactNative] s/ReactKit/React/g | Tadeu Zagallo
- [React Native] View border support | Nick Lockwood
- [Assets] Allow scripts to override assetRoots | Amjad Masad
- [ReactNative] Navigator docs | Eric Vicenti
- [ReactNative] License headers and renaming | Eric Vicenti
- [React Native] Add CocoaPods spec | Tadeu Zagallo
- Added explicit types for all view properties | Nick Lockwood
- [ReactNative] s/ReactNavigator/Navigator/ | Tadeu Zagallo
- [ReactNative] Add copyright header for code copied from the jQuery UI project | Martin Konicek
- [ReactNative] PanResponder documentation | Eric Vicenti
This commit is contained in:
Christopher Chedeau
2015-03-26 06:32:01 -07:00
parent d6c9648aea
commit 6500ba7bfb
182 changed files with 2094 additions and 1780 deletions

43
React/Base/RCTAssert.h Normal file
View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#define RCTErrorDomain @"RCTErrorDomain"
#define RCTAssert(condition, message, ...) _RCTAssert((condition) != 0, message, ##__VA_ARGS__)
#define RCTCAssert(condition, message, ...) _RCTCAssert((condition) != 0, 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");

19
React/Base/RCTAssert.m Normal file
View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTAssert.h"
RCTAssertFunction RCTInjectedAssertFunction = nil;
RCTAssertFunction RCTInjectedCAssertFunction = nil;
void RCTInjectAssertFunctions(RCTAssertFunction assertFunction, RCTAssertFunction cAssertFunction)
{
RCTInjectedAssertFunction = assertFunction;
RCTInjectedCAssertFunction = cAssertFunction;
}

92
React/Base/RCTBridge.h Normal file
View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTBridgeModule.h"
#import "RCTInvalidating.h"
#import "RCTJavaScriptExecutor.h"
@class RCTBridge;
@class RCTEventDispatcher;
/**
* This block can be used to instantiate modules that require additional
* init parameters, or additional configuration prior to being used.
* The bridge will call this block to instatiate the modules, and will
* be responsible for invalidating/releasing them when the bridge is destroyed.
* For this reason, the block should always return new module instances, and
* module instances should not be shared between bridges.
*/
typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
/**
* Async batched bridge used to communicate with the JavaScript application.
*/
@interface RCTBridge : NSObject <RCTInvalidating>
/**
* The designated initializer. This creates a new bridge on top of the specified
* executor. The bridge should then be used for all subsequent communication
* with the JavaScript code running in the executor. Modules will be automatically
* instantiated using the default contructor, but you can optionally pass in an
* array of pre-initialized module instances if they require additional init
* parameters or configuration.
*/
- (instancetype)initWithBundlePath:(NSString *)bundlepath
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions NS_DESIGNATED_INITIALIZER;
/**
* This method is used to call functions in the JavaScript application context.
* It is primarily intended for use by modules that require two-way communication
* with the JavaScript code.
*/
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
/**
* This method is used to execute a new application script. It is called
* internally whenever a JS application bundle is loaded/reloaded, but should
* probably not be used at any other time.
*/
- (void)enqueueApplicationScript:(NSString *)script url:(NSURL *)url onComplete:(RCTJavaScriptCompleteBlock)onComplete;
/**
* The event dispatcher is a wrapper around -enqueueJSCall:args: that provides a
* higher-level interface for sending UI events such as touches and text input.
*/
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
/**
* A dictionary of all registered RCTBridgeModule instances, keyed by moduleName.
*/
@property (nonatomic, copy, readonly) NSDictionary *modules;
/**
* The shadow queue is used to execute callbacks from the JavaScript code. All
* native hooks (e.g. exported module methods) will be executed on the shadow
* queue.
*/
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
/**
* Global logging function that 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;
@property (nonatomic, copy, readonly) NSDictionary *launchOptions;
/**
* Method to check that a valid executor exists with which to log
*/
+ (BOOL)hasValidJSExecutor;
@end

826
React/Base/RCTBridge.m Normal file
View File

@@ -0,0 +1,826 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTBridge.h"
#import <dlfcn.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h"
/**
* Must be kept in sync with `MessageQueue.js`.
*/
typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldRequestModuleIDs = 0,
RCTBridgeFieldMethodIDs,
RCTBridgeFieldParamss,
RCTBridgeFieldResponseCBIDs,
RCTBridgeFieldResponseReturnValues,
RCTBridgeFieldFlushDateMillis
};
/**
* This function returns the module name for a given class.
*/
static NSString *RCTModuleNameForClass(Class cls)
{
return [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
}
/**
* This function scans all classes available at runtime and returns an array
* of all JSMethods registered.
*/
static NSArray *RCTJSMethods(void)
{
static NSArray *JSMethods;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableSet *uniqueMethods = [NSMutableSet set];
RCTEnumerateClasses(^(__unsafe_unretained Class cls) {
if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) {
[uniqueMethods addObjectsFromArray:[cls JSMethods]];
}
});
JSMethods = [uniqueMethods allObjects];
});
return JSMethods;
}
/**
* This function scans all classes available at runtime and returns an array
* of all classes that implement the RTCBridgeModule protocol.
*/
static NSArray *RCTModuleNamesByID;
static NSArray *RCTBridgeModuleClassesByModuleID(void)
{
static NSArray *modules;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
modules = [NSMutableArray array];
RCTModuleNamesByID = [NSMutableArray array];
RCTEnumerateClasses(^(__unsafe_unretained Class cls) {
if ([cls conformsToProtocol:@protocol(RCTBridgeModule)]) {
// Add module
[(NSMutableArray *)modules addObject:cls];
// Add module name
NSString *moduleName = RCTModuleNameForClass(cls);
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
}
});
modules = [modules copy];
RCTModuleNamesByID = [RCTModuleNamesByID copy];
});
return modules;
}
@interface RCTBridge ()
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args;
@end
/**
* This private class is used as a container for exported method info
*/
@interface RCTModuleMethod : NSObject
@property (nonatomic, copy, readonly) NSString *moduleClassName;
@property (nonatomic, copy, readonly) NSString *JSMethodName;
@end
@implementation RCTModuleMethod
{
BOOL _isClassMethod;
Class _moduleClass;
SEL _selector;
NSMethodSignature *_methodSignature;
NSArray *_argumentBlocks;
NSString *_methodName;
}
- (instancetype)initWithMethodName:(NSString *)methodName
JSMethodName:(NSString *)JSMethodName
{
if ((self = [super init])) {
_methodName = methodName;
NSArray *parts = [[methodName substringWithRange:NSMakeRange(2, methodName.length - 3)] componentsSeparatedByString:@" "];
// Parse class and method
_moduleClassName = parts[0];
NSRange categoryRange = [_moduleClassName rangeOfString:@"("];
if (categoryRange.length)
{
_moduleClassName = [_moduleClassName substringToIndex:categoryRange.location];
}
// Extract class and method details
_isClassMethod = [methodName characterAtIndex:0] == '+';
_moduleClass = NSClassFromString(_moduleClassName);
_selector = NSSelectorFromString(parts[1]);
_JSMethodName = JSMethodName ?: [NSStringFromSelector(_selector) componentsSeparatedByString:@":"][0];
#if DEBUG
// Sanity check
RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"You are attempting to export the method %@, but %@ does not \
conform to the RCTBridgeModule Protocol", methodName, _moduleClassName);
#endif
// Get method signature
_methodSignature = _isClassMethod ?
[_moduleClass methodSignatureForSelector:_selector] :
[_moduleClass instanceMethodSignatureForSelector:_selector];
// Process arguments
NSUInteger numberOfArguments = _methodSignature.numberOfArguments;
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i];
switch (argumentType[0]) {
#define RCT_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
_logic \
[invocation setArgument:&value atIndex:index]; \
}]; \
#define RCT_CASE(_value, _class, _logic) \
case _value: { \
RCT_ARG_BLOCK( \
if (json && ![json isKindOfClass:[_class class]]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
json, RCTModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
return; \
} \
_logic \
) \
break; \
}
RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); );
RCT_CASE('*', NSString, const char *value = [json UTF8String]; );
#define RCT_SIMPLE_CASE(_value, _type, _selector) \
case _value: { \
RCT_ARG_BLOCK( \
if (json && ![json respondsToSelector:@selector(_selector)]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
index, json, RCTModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
return; \
} \
_type value = [json _selector]; \
) \
break; \
}
RCT_SIMPLE_CASE('c', char, charValue)
RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue)
RCT_SIMPLE_CASE('s', short, shortValue)
RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue)
RCT_SIMPLE_CASE('i', int, intValue)
RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue)
RCT_SIMPLE_CASE('l', long, longValue)
RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue)
RCT_SIMPLE_CASE('q', long long, longLongValue)
RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue)
RCT_SIMPLE_CASE('f', float, floatValue)
RCT_SIMPLE_CASE('d', double, doubleValue)
RCT_SIMPLE_CASE('B', BOOL, boolValue)
default: {
static const char *blockType = @encode(typeof(^{}));
if (!strcmp(argumentType, blockType)) {
RCT_ARG_BLOCK(
if (json && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
json, RCTModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing id value = (json ? ^(NSArray *args) {
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, args]];
} : ^(NSArray *unused) {});
)
} else {
RCT_ARG_BLOCK( id value = json; )
}
break;
}
}
}
_argumentBlocks = [argumentBlocks copy];
}
return self;
}
- (void)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments
{
#if DEBUG
// Sanity check
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
%@ on a module of class %@", _methodName, [module class]);
#endif
// Safety check
if (arguments.count != _argumentBlocks.count) {
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
RCTModuleNameForClass(_moduleClass), _JSMethodName,
arguments.count, _argumentBlocks.count);
return;
}
// Create invocation (we can't re-use this as it wouldn't be thread-safe)
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_methodSignature];
[invocation setArgument:&_selector atIndex:1];
// Set arguments
NSUInteger index = 0;
for (id json in arguments) {
id arg = (json == [NSNull null]) ? nil : json;
void (^block)(RCTBridge *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
block(bridge, invocation, index + 2, arg);
index ++;
}
// Invoke method
[invocation invokeWithTarget:_isClassMethod ? [module class] : module];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@;>", NSStringFromClass(self.class), self, _methodName, _JSMethodName];
}
@end
/**
* This function parses the exported methods inside RCTBridgeModules and
* generates an array of arrays of RCTModuleMethod objects, keyed
* by module index.
*/
static RCTSparseArray *RCTExportedMethodsByModuleID(void)
{
static RCTSparseArray *methodsByModuleID;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Dl_info info;
dladdr(&RCTExportedMethodsByModuleID, &info);
#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
const RCTExportValue mach_header = (RCTExportValue)info.dli_fbase;
const RCTExportSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTExport");
if (section == NULL) {
return;
}
NSArray *classes = RCTBridgeModuleClassesByModuleID();
NSMutableDictionary *methodsByModuleClassName = [NSMutableDictionary dictionaryWithCapacity:[classes count]];
for (RCTExportValue addr = section->offset;
addr < section->offset + section->size;
addr += sizeof(const char **) * 2) {
// Get data entry
const char **entries = (const char **)(mach_header + addr);
// Create method
RCTModuleMethod *moduleMethod =
[[RCTModuleMethod alloc] initWithMethodName:@(entries[0])
JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
// Cache method
NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName];
methodsByModuleClassName[moduleMethod.moduleClassName] =
methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod];
}
methodsByModuleID = [[RCTSparseArray alloc] initWithCapacity:[classes count]];
[classes enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
methodsByModuleID[moduleID] = methodsByModuleClassName[NSStringFromClass(moduleClass)];
}];
});
return methodsByModuleID;
}
/**
* This constructs the remote modules configuration data structure,
* which represents the native modules and methods that will be called
* by JS. A numeric ID is assigned to each module and method, which will
* be used to communicate via the bridge. The structure of each
* module is as follows:
*
* "ModuleName1": {
* "moduleID": 0,
* "methods": {
* "methodName1": {
* "methodID": 0,
* "type": "remote"
* },
* "methodName2": {
* "methodID": 1,
* "type": "remote"
* },
* etc...
* },
* "constants": {
* ...
* }
* },
* etc...
*/
static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
{
static NSMutableDictionary *remoteModuleConfigByClassName;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
remoteModuleConfigByClassName = [[NSMutableDictionary alloc] init];
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
NSArray *methods = RCTExportedMethodsByModuleID()[moduleID];
NSMutableDictionary *methodsByName = [NSMutableDictionary dictionaryWithCapacity:methods.count];
[methods enumerateObjectsUsingBlock:^(RCTModuleMethod *method, NSUInteger methodID, BOOL *_stop) {
methodsByName[method.JSMethodName] = @{
@"methodID": @(methodID),
@"type": @"remote",
};
}];
NSDictionary *module = @{
@"moduleID": @(moduleID),
@"methods": methodsByName
};
remoteModuleConfigByClassName[NSStringFromClass(moduleClass)] = module;
}];
});
// Create config
NSMutableDictionary *moduleConfig = [[NSMutableDictionary alloc] init];
[modulesByName enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, id<RCTBridgeModule> module, BOOL *stop) {
// Add constants
NSMutableDictionary *config = remoteModuleConfigByClassName[NSStringFromClass([module class])];
if ([module respondsToSelector:@selector(constantsToExport)]) {
NSDictionary *constants = [module constantsToExport];
if (constants) {
NSMutableDictionary *mutableConfig = [NSMutableDictionary dictionaryWithDictionary:config];
mutableConfig[@"constants"] = constants; // There's no real need to copy this
config = mutableConfig; // Nor this - receiver is unlikely to mutate it
}
}
moduleConfig[moduleName] = config;
}];
return moduleConfig;
}
/**
* As above, but for local modules/methods, which represent JS classes
* and methods that will be called by the native code via the bridge.
* Structure is essentially the same as for remote modules:
*
* "ModuleName1": {
* "moduleID": 0,
* "methods": {
* "methodName1": {
* "methodID": 0,
* "type": "local"
* },
* "methodName2": {
* "methodID": 1,
* "type": "local"
* },
* etc...
* }
* },
* etc...
*/
static NSMutableDictionary *RCTLocalModuleIDs;
static NSMutableDictionary *RCTLocalMethodIDs;
static NSDictionary *RCTLocalModulesConfig()
{
static NSMutableDictionary *localModules;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTLocalModuleIDs = [[NSMutableDictionary alloc] init];
RCTLocalMethodIDs = [[NSMutableDictionary alloc] init];
localModules = [[NSMutableDictionary alloc] init];
for (NSString *moduleDotMethod in RCTJSMethods()) {
NSArray *parts = [moduleDotMethod componentsSeparatedByString:@"."];
RCTCAssert(parts.count == 2, @"'%@' is not a valid JS method definition - expected 'Module.method' format.", moduleDotMethod);
// Add module if it doesn't already exist
NSString *moduleName = parts[0];
NSDictionary *module = localModules[moduleName];
if (!module) {
module = @{
@"moduleID": @(localModules.count),
@"methods": [[NSMutableDictionary alloc] init]
};
localModules[moduleName] = module;
}
// Add method if it doesn't already exist
NSString *methodName = parts[1];
NSMutableDictionary *methods = module[@"methods"];
if (!methods[methodName]) {
methods[methodName] = @{
@"methodID": @(methods.count),
@"type": @"local"
};
}
// Add module and method lookup
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
}
});
return localModules;
}
@implementation RCTBridge
{
RCTSparseArray *_modulesByID;
NSDictionary *_modulesByName;
id<RCTJavaScriptExecutor> _javaScriptExecutor;
RCTBridgeModuleProviderBlock _moduleProvider;
}
static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (instancetype)initWithBundlePath:(NSString *)bundlepath
moduleProvider:(RCTBridgeModuleProviderBlock)block
launchOptions:(NSDictionary *)launchOptions
{
if ((self = [super init])) {
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_shadowQueue = dispatch_queue_create("com.facebook.React.ShadowQueue", DISPATCH_QUEUE_SERIAL);
_moduleProvider = block;
_launchOptions = launchOptions;
}
return self;
}
- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor
{
_javaScriptExecutor = executor;
_latestJSExecutor = _javaScriptExecutor;
[self setUp];
}
- (void)setUp
{
// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) {
preregisteredModules[RCTModuleNameForClass([module class])] = module;
}
// Instantiate modules
_modulesByID = [[RCTSparseArray alloc] init];
NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
[RCTBridgeModuleClassesByModuleID() enumerateObjectsUsingBlock:^(Class moduleClass, NSUInteger moduleID, BOOL *stop) {
NSString *moduleName = RCTModuleNamesByID[moduleID];
// Check if module instance has already been registered for this name
if ((_modulesByID[moduleID] = modulesByName[moduleName])) {
// Preregistered instances takes precedence, no questions asked
if (!preregisteredModules[moduleName]) {
// It's OK to have a name collision as long as the second instance is nil
RCTAssert([[moduleClass alloc] init] == nil,
@"Attempted to register RCTBridgeModule class %@ for the name '%@', \
but name was already registered by class %@", moduleClass,
moduleName, [modulesByName[moduleName] class]);
}
} else {
// Module name hasn't been used before, so go ahead and instantiate
id<RCTBridgeModule> module = [[moduleClass alloc] init];
if (module) {
_modulesByID[moduleID] = modulesByName[moduleName] = module;
}
}
}];
// Store modules
_modulesByName = [modulesByName copy];
// Set bridge
for (id<RCTBridgeModule> module in _modulesByName.allValues) {
if ([module respondsToSelector:@selector(setBridge:)]) {
module.bridge = self;
}
}
// Inject module data into JS context
NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
@"localModulesConfig": RCTLocalModulesConfig()
}, NULL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {
dispatch_semaphore_signal(semaphore);
}];
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
RCTLogError(@"JavaScriptExecutor took too long to inject JSON object");
}
}
- (NSDictionary *)modules
{
RCTAssert(_modulesByName != nil, @"Bridge modules have not yet been initialized. \
You may be trying to access a module too early in the startup procedure.");
return _modulesByName;
}
- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
}
#pragma mark - RCTInvalidating
- (BOOL)isValid
{
return _javaScriptExecutor != nil;
}
- (void)invalidate
{
// Release executor
if (_latestJSExecutor == _javaScriptExecutor) {
_latestJSExecutor = nil;
}
[_javaScriptExecutor invalidate];
_javaScriptExecutor = nil;
// Wait for queued methods to finish
dispatch_sync(self.shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
// TODO: is this still needed?
});
// Invalidate modules
for (id target in _modulesByID.allObjects) {
if ([target respondsToSelector:@selector(invalidate)]) {
[(id<RCTInvalidating>)target invalidate];
}
}
// Release modules (breaks retain cycle if module has strong bridge reference)
_modulesByID = nil;
_modulesByName = 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:(NSString *)moduleDotMethod args:(NSArray *)args
{
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
RCTAssert(moduleID != nil, @"Module '%@' not registered.",
[[moduleDotMethod componentsSeparatedByString:@"."] firstObject]);
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, 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 json, NSError *error) {
[self _handleBuffer:json];
onComplete(error);
}];
}];
}
#pragma mark - Payload Generation
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"JS_PERF_ENQUEUE" object:nil userInfo:nil];
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"JS_PERF_DEQUEUE" object:nil userInfo:nil];
[self _handleBuffer:json];
};
[_javaScriptExecutor executeJSCall:module
method:method
arguments:args
callback:processResponse];
}
#pragma mark - Payload Processing
- (void)_handleBuffer:(id)buffer
{
if (buffer == nil || buffer == (id)kCFNull) {
return;
}
if (![buffer isKindOfClass:[NSArray class]]) {
RCTLogError(@"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) {
RCTLogError(@"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]]) {
RCTLogError(@"Field at index %zd in buffer must be an instance of NSArray, got %@", fieldIndex, NSStringFromClass([field class]));
return;
}
}
NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss];
NSUInteger numRequests = [moduleIDs count];
BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramsArrays count];
if (!allSame) {
RCTLogError(@"Invalid data message - all must be length: %zd", numRequests);
return;
}
for (NSUInteger i = 0; i < numRequests; i++) {
@autoreleasepool {
[self _handleRequestNumber:i
moduleID:[moduleIDs[i] integerValue]
methodID:[methodIDs[i] integerValue]
params:paramsArrays[i]];
}
}
// TODO: only used by RCTUIManager - can we eliminate this special case?
dispatch_async(self.shadowQueue, ^{
for (id module in _modulesByID.allObjects) {
if ([module respondsToSelector:@selector(batchDidComplete)]) {
[module batchDidComplete];
}
}
});
}
- (BOOL)_handleRequestNumber:(NSUInteger)i
moduleID:(NSUInteger)moduleID
methodID:(NSUInteger)methodID
params:(NSArray *)params
{
if (![params isKindOfClass:[NSArray class]]) {
RCTLogError(@"Invalid module/method/params tuple for request #%zd", i);
return NO;
}
// Look up method
NSArray *methods = RCTExportedMethodsByModuleID()[moduleID];
if (methodID >= methods.count) {
RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, RCTModuleNamesByID[moduleID]);
return NO;
}
RCTModuleMethod *method = methods[methodID];
__weak RCTBridge *weakSelf = self;
dispatch_async(self.shadowQueue, ^{
__strong RCTBridge *strongSelf = weakSelf;
if (!strongSelf.isValid) {
// strongSelf has been invalidated since the dispatch_async call and this
// invocation should not continue.
return;
}
// Look up module
id module = strongSelf->_modulesByID[moduleID];
if (!module) {
RCTLogError(@"No module found for name '%@'", RCTModuleNamesByID[moduleID]);
return;
}
@try {
[method invokeWithBridge:strongSelf module:module arguments:params];
}
@catch (NSException *exception) {
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
}
});
return YES;
}
+ (BOOL)hasValidJSExecutor
{
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);
}
+ (void)log:(NSArray *)objects level:(NSString *)level
{
if (!_latestJSExecutor || ![_latestJSExecutor isValid]) {
RCTLogError(@"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 json, NSError *error) {}];
}
@end

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "RCTJSMethodRegistrar.h"
@class RCTBridge;
/**
* 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 the interface needed to register a bridge module.
*/
@protocol RCTBridgeModule <RCTJSMethodRegistrar>
@optional
/**
* A reference to the RCTBridge. Useful for modules that require access
* to bridge features, such as sending events or making JS calls. This
* will be set automatically by the bridge when it initializes the module.
* To implement this in your module, just add @synthesize bridge = _bridge;
*/
@property (nonatomic, strong) RCTBridge *bridge;
/**
* The module name exposed to JS. If omitted, this will be inferred
* automatically by using the native module's class name.
*/
+ (NSString *)moduleName;
/**
* 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 method name
* (the method will be namespaced to the module name, as specified above).
* If omitted, the JS method name will match the first part of the Objective-C
* method selector name (up to the first colon).
*/
#define RCT_EXPORT(js_name) __attribute__((used, section("__DATA,RCTExport" \
))) static const char *__rct_export_entry__[] = { __func__, #js_name }
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.ModuleName.X. This method is called when the module is
* registered by the bridge. It is only called once for the lifetime of the
* bridge, so it is not suitable for returning dynamic values, but may be
* used for long-lived values such as session keys, that are regenerated only
* as part of a reload of the entire React application.
*/
- (NSDictionary *)constantsToExport;
/**
* Notifies the module that a batch of JS method invocations has just completed.
*/
- (void)batchDidComplete;
@end

29
React/Base/RCTCache.h Normal file
View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
@interface RCTCache : NSObject
- (instancetype)init; // name = @"default"
- (instancetype)initWithName:(NSString *)name;
@property (nonatomic, assign) NSUInteger maximumDiskSize; // in bytes
#pragma mark - Retrieval
- (BOOL)hasDataForKey:(NSString *)key;
- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *data))completionHandler;
#pragma mark - Insertion
- (void)setData:(NSData *)data forKey:(NSString *)key;
- (void)removeAllData;
@end

231
React/Base/RCTCache.m Normal file
View File

@@ -0,0 +1,231 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTCache.h"
#import <UIKit/UIKit.h>
#import <sys/xattr.h>
static NSString *const RCTCacheSubdirectoryName = @"React";
static NSString *const RCTKeyExtendedAttributeName = @"com.facebook.React.RCTCacheManager.Key";
static NSMapTable *RCTLivingCachesByName;
static NSError *RCTPOSIXError(int errorNumber)
{
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: @(strerror(errorNumber))
};
return [NSError errorWithDomain:NSPOSIXErrorDomain code:errorNumber userInfo:userInfo];
}
static NSString *RCTGetExtendedAttribute(NSURL *fileURL, NSString *key, NSError **error)
{
const char *path = fileURL.fileSystemRepresentation;
ssize_t length = getxattr(path, key.UTF8String, NULL, 0, 0, 0);
if (length <= 0) {
if (error) *error = RCTPOSIXError(errno);
return nil;
}
char *buffer = malloc(length);
length = getxattr(path, key.UTF8String, buffer, length, 0, 0);
if (length > 0) {
return [[NSString alloc] initWithBytesNoCopy:buffer length:length encoding:NSUTF8StringEncoding freeWhenDone:YES];
}
free(buffer);
if (error) *error = RCTPOSIXError(errno);
return nil;
}
static BOOL RCTSetExtendedAttribute(NSURL *fileURL, NSString *key, NSString *value, NSError **error)
{
const char *path = fileURL.fileSystemRepresentation;
int result;
if (value) {
const char *valueUTF8String = value.UTF8String;
result = setxattr(path, key.UTF8String, valueUTF8String, strlen(valueUTF8String), 0, 0);
} else {
result = removexattr(path, key.UTF8String, 0);
}
if (result) {
if (error) *error = RCTPOSIXError(errno);
return NO;
}
return YES;
}
#pragma mark - Cache Record -
@interface RCTCacheRecord : NSObject
@property (readonly) NSUUID *UUID;
@property (readonly, weak) dispatch_queue_t queue;
@property (nonatomic, copy) NSData *data;
@end
@implementation RCTCacheRecord
- (instancetype)initWithUUID:(NSUUID *)UUID
{
if ((self = [super init])) {
_UUID = [UUID copy];
}
return self;
}
- (void)enqueueBlock:(dispatch_block_t)block
{
dispatch_queue_t queue = _queue;
if (!queue) {
NSString *queueName = [NSString stringWithFormat:@"com.facebook.React.RCTCache.%@", _UUID.UUIDString];
queue = dispatch_queue_create(queueName.UTF8String, DISPATCH_QUEUE_SERIAL);
_queue = queue;
}
dispatch_async(queue, block);
}
@end
#pragma mark - Cache
@implementation RCTCache
{
NSString *_name;
NSFileManager *_fileManager;
NSMutableDictionary *_storage;
NSURL *_cacheDirectoryURL;
}
+ (void)initialize
{
if (self == [RCTCache class]) {
RCTLivingCachesByName = [NSMapTable strongToWeakObjectsMapTable];
}
}
- (instancetype)init
{
return [self initWithName:@"default"];
}
- (instancetype)initWithName:(NSString *)name
{
NSParameterAssert(name.length < NAME_MAX);
RCTCache *cachedCache = [RCTLivingCachesByName objectForKey:name];
if (cachedCache) {
self = cachedCache;
return self;
}
if ((self = [super init])) {
_name = [name copy];
_fileManager = [[NSFileManager alloc] init];
_storage = [NSMutableDictionary dictionary];
NSURL *cacheDirectoryURL = [[_fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:RCTCacheSubdirectoryName isDirectory:YES];
_cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:name isDirectory:YES];
[_fileManager createDirectoryAtURL:_cacheDirectoryURL withIntermediateDirectories:YES attributes:nil error:NULL];
NSArray *fileURLs = [_fileManager contentsOfDirectoryAtURL:_cacheDirectoryURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:NULL];
for (NSURL *fileURL in fileURLs) {
NSUUID *UUID = [[NSUUID alloc] initWithUUIDString:fileURL.lastPathComponent];
if (!UUID) continue;
NSString *key = RCTGetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, NULL);
if (!key) {
[_fileManager removeItemAtURL:fileURL error:NULL];
continue;
}
_storage[key] = [[RCTCacheRecord alloc] initWithUUID:UUID];
}
}
return self;
}
- (BOOL)hasDataForKey:(NSString *)key
{
return _storage[key] != nil;
}
- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *))completionHandler
{
NSParameterAssert(key.length > 0);
NSParameterAssert(completionHandler != nil);
RCTCacheRecord *record = _storage[key];
if (!record) {
completionHandler(nil);
return;
}
[record enqueueBlock:^{
if (!record.data) {
record.data = [NSData dataWithContentsOfURL:[_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]];
}
completionHandler(record.data);
}];
}
- (void)setData:(NSData *)data forKey:(NSString *)key
{
NSParameterAssert(key.length > 0);
RCTCacheRecord *record = _storage[key];
if (!record) {
if (!data) return;
record = [[RCTCacheRecord alloc] initWithUUID:[NSUUID UUID]];
_storage[key] = record;
}
NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString];
UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
[record enqueueBlock:^{
if (data) {
[data writeToURL:fileURL options:NSDataWritingAtomic error:NULL];
RCTSetExtendedAttribute(fileURL, RCTKeyExtendedAttributeName, key, NULL);
} else {
[_fileManager removeItemAtURL:fileURL error:NULL];
}
if (identifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:identifier];
}
}];
}
- (void)removeAllData
{
UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
dispatch_group_t group = dispatch_group_create();
[_storage enumerateKeysAndObjectsUsingBlock:^(NSString *key, RCTCacheRecord *record, BOOL *stop) {
NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString];
dispatch_group_async(group, record.queue, ^{
[_fileManager removeItemAtURL:fileURL error:NULL];
});
}];
if (identifier != UIBackgroundTaskInvalid) {
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] endBackgroundTask:identifier];
});
}
[_storage removeAllObjects];
}
@end

256
React/Base/RCTConvert.h Normal file
View File

@@ -0,0 +1,256 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIKit.h>
#import "Layout.h"
#import "RCTAnimationType.h"
#import "RCTLog.h"
#import "RCTPointerEvents.h"
/**
* This class provides a collection of conversion functions for mapping
* JSON objects to native types and classes. These are useful when writing
* custom RCTViewManager setter methods.
*/
@interface RCTConvert : NSObject
+ (BOOL)BOOL:(id)json;
+ (double)double:(id)json;
+ (float)float:(id)json;
+ (int)int:(id)json;
+ (int64_t)int64_t:(id)json;
+ (uint64_t)uint64_t:(id)json;
+ (NSInteger)NSInteger:(id)json;
+ (NSUInteger)NSUInteger:(id)json;
+ (NSArray *)NSArray:(id)json;
+ (NSDictionary *)NSDictionary:(id)json;
+ (NSString *)NSString:(id)json;
+ (NSNumber *)NSNumber:(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;
+ (UITextFieldViewMode)UITextFieldViewMode:(id)json;
+ (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json;
+ (UIKeyboardType)UIKeyboardType:(id)json;
+ (UIViewContentMode)UIViewContentMode:(id)json;
+ (UIBarStyle)UIBarStyle:(id)json;
+ (CGFloat)CGFloat:(id)json;
+ (CGPoint)CGPoint:(id)json;
+ (CGSize)CGSize:(id)json;
+ (CGRect)CGRect:(id)json;
+ (UIEdgeInsets)UIEdgeInsets:(id)json;
+ (CGLineCap)CGLineCap:(id)json;
+ (CGLineJoin)CGLineJoin:(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 withStyle:(id)json;
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json;
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)family
size:(id)size weight:(id)weight style:(id)style;
+ (NSArray *)NSStringArray:(id)json;
+ (NSArray *)NSDictionaryArray:(id)json;
+ (NSArray *)NSURLArray:(id)json;
+ (NSArray *)NSNumberArray:(id)json;
+ (NSArray *)UIColorArray:(id)json;
+ (NSArray *)CGColorArray:(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;
+ (RCTPointerEvents)RCTPointerEvents:(id)json;
+ (RCTAnimationType)RCTAnimationType:(id)json;
@end
#ifdef __cplusplus
extern "C" {
#endif
/**
* This function will attempt to set a property using a json value by first
* inferring the correct type from all available information, and then
* applying an appropriate conversion method. If the property does not
* exist, or the type cannot be inferred, the function will return NO.
*/
BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json);
/**
* This function attempts to copy a property from the source object to the
* destination object using KVC. If the property does not exist, or cannot
* be set, it will do nothing and return NO.
*/
BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
#ifdef __cplusplus
}
#endif
/**
* This macro is used for creating converter functions with arbitrary logic.
*/
#define RCT_CONVERTER_CUSTOM(type, name, code) \
+ (type)name:(id)json \
{ \
if (json == [NSNull null]) { \
json = nil; \
} \
@try { \
return code; \
} \
@catch (__unused NSException *e) { \
RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \
json, [json class], #type); \
json = nil; \
return code; \
} \
}
/**
* This macro is used for creating simple converter functions that just call
* the specified getter method on the json value.
*/
#define RCT_CONVERTER(type, name, getter) \
RCT_CONVERTER_CUSTOM(type, name, [json getter])
/**
* This macro is similar to RCT_CONVERTER, but specifically geared towards
* numeric types. It will handle string input correctly, and provides more
* detailed error reporting if a wrong value is passed in.
*/
#define RCT_NUMBER_CONVERTER(type, getter) \
RCT_CONVERTER_CUSTOM(type, type, [[self NSNumber:json] getter])
/**
* This macro is used for creating converters for enum types.
*/
#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 || json == [NSNull null]) { \
return default; \
} \
if ([json isKindOfClass:[NSNumber class]]) { \
if ([[mapping allValues] containsObject:json] || [json getter] == default) { \
return [json getter]; \
} \
RCTLogError(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allValues]); \
return default; \
} \
if (![json isKindOfClass:[NSString class]]) { \
RCTLogError(@"Expected NSNumber or NSString for %s, received %@: %@", #type, [json class], json); \
} \
id value = mapping[json]; \
if(!value && [json description].length > 0) { \
RCTLogError(@"Invalid %s '%@'. should be one of: %@", #type, json, [mapping allKeys]); \
} \
return value ? [value getter] : default; \
}
/**
* This macro is used for creating converter functions for structs that consist
* of a number of CGFloat properties, such as CGPoint, CGRect, etc.
*/
#define RCT_CGSTRUCT_CONVERTER(type, values, _aliases) \
+ (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) { \
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
} else { \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [self CGFloat:json[i]]; \
} \
} \
} else if ([json isKindOfClass:[NSDictionary class]]) { \
NSDictionary *aliases = _aliases; \
if (aliases.count) { \
json = [json mutableCopy]; \
for (NSString *alias in aliases) { \
NSString *key = aliases[alias]; \
NSNumber *number = json[key]; \
if (number) { \
((NSMutableDictionary *)json)[key] = number; \
} \
} \
} \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
} \
} else if (json && json != [NSNull null]) { \
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
} \
return result; \
} \
@catch (__unused NSException *e) { \
RCTLogError(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
type result; \
return result; \
} \
}
/**
* This macro is used for creating converter functions for typed arrays.
*/
#define RCT_ARRAY_CONVERTER(type) \
+ (NSArray *)type##Array:(id)json \
{ \
NSMutableArray *values = [[NSMutableArray alloc] init]; \
for (id jsonValue in [self NSArray:json]) { \
id value = [self type:jsonValue]; \
if (value) { \
[values addObject:value]; \
} \
} \
return values; \
}

770
React/Base/RCTConvert.m Normal file
View File

@@ -0,0 +1,770 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTConvert.h"
#import <objc/message.h>
@implementation RCTConvert
RCT_CONVERTER(BOOL, BOOL, boolValue)
RCT_NUMBER_CONVERTER(double, doubleValue)
RCT_NUMBER_CONVERTER(float, floatValue)
RCT_NUMBER_CONVERTER(int, intValue)
RCT_NUMBER_CONVERTER(int64_t, longLongValue);
RCT_NUMBER_CONVERTER(uint64_t, unsignedLongLongValue);
RCT_NUMBER_CONVERTER(NSInteger, integerValue)
RCT_NUMBER_CONVERTER(NSUInteger, unsignedIntegerValue)
RCT_CONVERTER_CUSTOM(NSArray *, NSArray, [NSArray arrayWithArray:json])
RCT_CONVERTER_CUSTOM(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
RCT_CONVERTER(NSString *, NSString, description)
+ (NSNumber *)NSNumber:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
return json;
} else if ([json isKindOfClass:[NSString class]]) {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *number = [formatter numberFromString:json];
if (!number) {
RCTLogError(@"JSON String '%@' could not be interpreted as a number", json);
}
return number;
} else if (json && json != [NSNull null]) {
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json class]);
}
return nil;
}
+ (NSURL *)NSURL:(id)json
{
if (![json isKindOfClass:[NSString class]]) {
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json class], json);
return nil;
}
NSString *path = json;
if ([path isAbsolutePath])
{
return [NSURL fileURLWithPath:path];
}
else if ([path length])
{
NSURL *URL = [NSURL URLWithString:path relativeToURL:[[NSBundle mainBundle] resourceURL]];
if ([URL isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:[URL path]]) {
RCTLogWarn(@"The file '%@' does not exist", URL);
return nil;
}
return URL;
}
return nil;
}
+ (NSURLRequest *)NSURLRequest:(id)json
{
return [NSURLRequest requestWithURL:[self NSURL:json]];
}
// JS Standard for time is milliseconds
RCT_CONVERTER_CUSTOM(NSDate *, NSDate, [NSDate dateWithTimeIntervalSince1970:[self double:json] / 1000.0])
RCT_CONVERTER_CUSTOM(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0)
// JS standard for time zones is minutes.
RCT_CONVERTER_CUSTOM(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
RCT_ENUM_CONVERTER(NSTextAlignment, (@{
@"auto": @(NSTextAlignmentNatural),
@"left": @(NSTextAlignmentLeft),
@"center": @(NSTextAlignmentCenter),
@"right": @(NSTextAlignmentRight),
@"justify": @(NSTextAlignmentJustified),
}), NSTextAlignmentNatural, integerValue)
RCT_ENUM_CONVERTER(NSWritingDirection, (@{
@"auto": @(NSWritingDirectionNatural),
@"ltr": @(NSWritingDirectionLeftToRight),
@"rtl": @(NSWritingDirectionRightToLeft),
}), NSWritingDirectionNatural, integerValue)
RCT_ENUM_CONVERTER(UITextAutocapitalizationType, (@{
@"none": @(UITextAutocapitalizationTypeNone),
@"words": @(UITextAutocapitalizationTypeWords),
@"sentences": @(UITextAutocapitalizationTypeSentences),
@"characters": @(UITextAutocapitalizationTypeAllCharacters)
}), UITextAutocapitalizationTypeSentences, integerValue)
RCT_ENUM_CONVERTER(UITextFieldViewMode, (@{
@"never": @(UITextFieldViewModeNever),
@"while-editing": @(UITextFieldViewModeWhileEditing),
@"unless-editing": @(UITextFieldViewModeUnlessEditing),
@"always": @(UITextFieldViewModeAlways),
}), UITextFieldViewModeNever, integerValue)
RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{
@"none": @(UIScrollViewKeyboardDismissModeNone),
@"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag),
@"interactive": @(UIScrollViewKeyboardDismissModeInteractive),
}), UIScrollViewKeyboardDismissModeNone, integerValue)
RCT_ENUM_CONVERTER(UIKeyboardType, (@{
@"numeric": @(UIKeyboardTypeDecimalPad),
@"default": @(UIKeyboardTypeDefault),
}), UIKeyboardTypeDefault, integerValue)
RCT_ENUM_CONVERTER(UIViewContentMode, (@{
@"scale-to-fill": @(UIViewContentModeScaleToFill),
@"scale-aspect-fit": @(UIViewContentModeScaleAspectFit),
@"scale-aspect-fill": @(UIViewContentModeScaleAspectFill),
@"redraw": @(UIViewContentModeRedraw),
@"center": @(UIViewContentModeCenter),
@"top": @(UIViewContentModeTop),
@"bottom": @(UIViewContentModeBottom),
@"left": @(UIViewContentModeLeft),
@"right": @(UIViewContentModeRight),
@"top-left": @(UIViewContentModeTopLeft),
@"top-right": @(UIViewContentModeTopRight),
@"bottom-left": @(UIViewContentModeBottomLeft),
@"bottom-right": @(UIViewContentModeBottomRight),
}), UIViewContentModeScaleToFill, integerValue)
RCT_ENUM_CONVERTER(UIBarStyle, (@{
@"default": @(UIBarStyleDefault),
@"black": @(UIBarStyleBlack),
}), UIBarStyleDefault, integerValue)
// TODO: normalise the use of w/width so we can do away with the alias values (#6566645)
RCT_CONVERTER_CUSTOM(CGFloat, CGFloat, [self double:json])
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]), nil)
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
RCT_CGSTRUCT_CONVERTER(UIEdgeInsets, (@[@"top", @"left", @"bottom", @"right"]), nil)
RCT_ENUM_CONVERTER(CGLineJoin, (@{
@"miter": @(kCGLineJoinMiter),
@"round": @(kCGLineJoinRound),
@"bevel": @(kCGLineJoinBevel),
}), kCGLineJoinMiter, intValue)
RCT_ENUM_CONVERTER(CGLineCap, (@{
@"butt": @(kCGLineCapButt),
@"round": @(kCGLineCapRound),
@"square": @(kCGLineCapSquare),
}), kCGLineCapButt, intValue)
RCT_CGSTRUCT_CONVERTER(CATransform3D, (@[
@"m11", @"m12", @"m13", @"m14",
@"m21", @"m22", @"m23", @"m24",
@"m31", @"m32", @"m33", @"m34",
@"m41", @"m42", @"m43", @"m44"
]), nil)
RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
@"a", @"b", @"c", @"d", @"tx", @"ty"
]), nil)
+ (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)",
};
});
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 {
RCTLogError(@"Unrecognized color format '%@', must be one of #hex|rgba|rgb", colorString);
}
if (red == -1 || green == -1 || blue == -1 || alpha > 1.0 || alpha < 0.0) {
RCTLogError(@"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) {
RCTLogError(@"Expected array with count 3 or 4, but count is %zd: %@", [json count], json);
} else {
// Color array
color = [UIColor colorWithRed:[self double:json[0]]
green:[self double:json[1]]
blue:[self double:json[2]]
alpha:[json count] > 3 ? [self double:json[3]] : 1];
}
} else if ([json isKindOfClass:[NSDictionary class]]) {
// Color dictionary
color = [UIColor colorWithRed:[self double:json[@"r"]]
green:[self double:json[@"g"]]
blue:[self double:json[@"b"]]
alpha:[self double:json[@"a"] ?: @1]];
} else if (json && ![json isKindOfClass:[NSNull class]]) {
RCTLogError(@"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
{
// TODO: we might as well cache the result of these checks (and possibly the
// image itself) so as to reduce overhead on subsequent checks of the same input
if (![json isKindOfClass:[NSString class]]) {
RCTLogError(@"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]];
}
}
// NOTE: we don't warn about nil images because there are legitimate
// case where we find out if a string is an image by using this method
return image;
}
+ (CGImageRef)CGImage:(id)json
{
return [self UIImage:json].CGImage;
}
#ifndef __IPHONE_8_2
// These constants are defined in iPhone SDK 8.2
// They'll work fine in earlier iOS versions, but the app cannot be built with
// an SDK version < 8.2 unless we redefine them here. This will be removed
// in a future version of React, once 8.2 is more widely adopted.
static const CGFloat UIFontWeightUltraLight = -0.8;
static const CGFloat UIFontWeightThin = -0.6;
static const CGFloat UIFontWeightLight = -0.4;
static const CGFloat UIFontWeightRegular = 0;
static const CGFloat UIFontWeightMedium = 0.23;
static const CGFloat UIFontWeightSemibold = 0.3;
static const CGFloat UIFontWeightBold = 0.4;
static const CGFloat UIFontWeightHeavy = 0.56;
static const CGFloat UIFontWeightBlack = 0.62;
#endif
typedef CGFloat RCTFontWeight;
RCT_ENUM_CONVERTER(RCTFontWeight, (@{
@"normal": @(UIFontWeightRegular),
@"bold": @(UIFontWeightBold),
@"100": @(UIFontWeightUltraLight),
@"200": @(UIFontWeightThin),
@"300": @(UIFontWeightLight),
@"400": @(UIFontWeightRegular),
@"500": @(UIFontWeightMedium),
@"600": @(UIFontWeightSemibold),
@"700": @(UIFontWeightBold),
@"800": @(UIFontWeightHeavy),
@"900": @(UIFontWeightBlack),
}), UIFontWeightRegular, doubleValue)
typedef BOOL RCTFontStyle;
RCT_ENUM_CONVERTER(RCTFontStyle, (@{
@"normal": @NO,
@"italic": @YES,
@"oblique": @YES,
}), NO, boolValue)
static RCTFontWeight RCTWeightOfFont(UIFont *font)
{
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
return [traits[UIFontWeightTrait] doubleValue];
}
static BOOL RCTFontIsItalic(UIFont *font)
{
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue];
return (symbolicTraits & UIFontDescriptorTraitItalic) != 0;
}
static BOOL RCTFontIsCondensed(UIFont *font)
{
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
UIFontDescriptorSymbolicTraits symbolicTraits = [traits[UIFontSymbolicTrait] unsignedIntValue];
return (symbolicTraits & UIFontDescriptorTraitCondensed) != 0;
}
+ (UIFont *)UIFont:(UIFont *)font withSize:(id)json
{
return [self UIFont:font withFamily:nil size:json weight:nil style:nil];
}
+ (UIFont *)UIFont:(UIFont *)font withWeight:(id)json
{
return [self UIFont:font withFamily:nil size:nil weight:json style:nil];
}
+ (UIFont *)UIFont:(UIFont *)font withStyle:(id)json
{
return [self UIFont:font withFamily:nil size:nil weight:nil style:json];
}
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json
{
return [self UIFont:font withFamily:json size:nil weight:nil style:nil];
}
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)family
size:(id)size weight:(id)weight style:(id)style
{
// Defaults
NSString *const RCTDefaultFontFamily = @"Helvetica Neue";
const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular;
const CGFloat RCTDefaultFontSize = 14;
// Get existing properties
BOOL isItalic = NO;
BOOL isCondensed = NO;
RCTFontWeight fontWeight = RCTDefaultFontWeight;
if (font) {
family = font.familyName;
fontWeight = RCTWeightOfFont(font);
isItalic = RCTFontIsItalic(font);
isCondensed = RCTFontIsCondensed(font);
}
// Get font weight
if (weight) {
fontWeight = [self RCTFontWeight:weight];
}
// Get font style
if (style) {
isItalic = [self RCTFontStyle:style];
}
// Get font size
CGFloat fontSize = [self CGFloat:size] ?: RCTDefaultFontSize;
// Get font family
NSString *familyName = [self NSString:family] ?: RCTDefaultFontFamily;
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
font = [UIFont fontWithName:familyName size:fontSize];
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;
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
fontWeight = [traits[UIFontWeightTrait] doubleValue];
} else {
// Not a valid font or family
RCTLogError(@"Unrecognized font family '%@'", familyName);
familyName = RCTDefaultFontFamily;
}
}
// Get closest match
UIFont *bestMatch = font;
CGFloat closestWeight = font ? RCTWeightOfFont(font) : INFINITY;
for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) {
UIFont *match = [UIFont fontWithName:name size:fontSize];
if (isItalic == RCTFontIsItalic(match) &&
isCondensed == RCTFontIsCondensed(match)) {
CGFloat testWeight = RCTWeightOfFont(match);
if (ABS(testWeight - fontWeight) < ABS(closestWeight - fontWeight)) {
bestMatch = match;
closestWeight = testWeight;
}
}
}
// Safety net
if (!bestMatch) {
RCTLogError(@"Could not find font with family: '%@', size: %@, \
weight: %@, style: %@", family, size, weight, style);
bestMatch = [UIFont fontWithName:[[UIFont fontNamesForFamilyName:familyName] firstObject]
size:fontSize];
}
return bestMatch;
}
RCT_ARRAY_CONVERTER(NSString)
RCT_ARRAY_CONVERTER(NSDictionary)
RCT_ARRAY_CONVERTER(NSURL)
RCT_ARRAY_CONVERTER(NSNumber)
RCT_ARRAY_CONVERTER(UIColor)
// Can't use RCT_ARRAY_CONVERTER due to bridged cast
+ (NSArray *)CGColorArray:(id)json
{
NSMutableArray *colors = [[NSMutableArray alloc] init];
for (id value in [self NSArray:json]) {
[colors addObject:(__bridge id)[self CGColor:value]];
}
return colors;
}
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)
RCT_ENUM_CONVERTER(RCTPointerEvents, (@{
@"none": @(RCTPointerEventsNone),
@"box-only": @(RCTPointerEventsBoxOnly),
@"box-none": @(RCTPointerEventsBoxNone),
@"auto": @(RCTPointerEventsUnspecified)
}), RCTPointerEventsUnspecified, integerValue)
RCT_ENUM_CONVERTER(RCTAnimationType, (@{
@"spring": @(RCTAnimationTypeSpring),
@"linear": @(RCTAnimationTypeLinear),
@"easeIn": @(RCTAnimationTypeEaseIn),
@"easeOut": @(RCTAnimationTypeEaseOut),
@"easeInEaseOut": @(RCTAnimationTypeEaseInEaseOut),
}), RCTAnimationTypeEaseInEaseOut, integerValue)
@end
BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json)
{
// 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;
}
}
// Get property setter
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1]]);
// Fail early
if (![target respondsToSelector:setter]) {
return NO;
}
// Get converted value
NSMethodSignature *signature = [RCTConvert methodSignatureForSelector:type];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&type atIndex:1];
[invocation setArgument:&json atIndex:2];
[invocation invokeWithTarget:[RCTConvert class]];
NSUInteger length = [signature methodReturnLength];
void *value = malloc(length);
[invocation getReturnValue:value];
// Set converted value
signature = [target methodSignatureForSelector:setter];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&setter atIndex:1];
[invocation setArgument:value atIndex:2];
[invocation invokeWithTarget:target];
free(value);
return YES;
}
BOOL RCTCopyProperty(id target, id source, NSString *keyPath)
{
// Split keypath
NSArray *parts = [keyPath componentsSeparatedByString:@"."];
NSString *key = [parts lastObject];
for (NSUInteger i = 0; i < parts.count - 1; i++) {
source = [source valueForKey:parts[i]];
target = [target valueForKey:parts[i]];
if (!source || !target) {
return NO;
}
}
// Get property getter
SEL getter = NSSelectorFromString(key);
// Get property setter
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@:",
[[key substringToIndex:1] uppercaseString],
[key substringFromIndex:1]]);
// Fail early
if (![source respondsToSelector:getter] || ![target respondsToSelector:setter]) {
return NO;
}
// Get converted value
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&getter atIndex:1];
[invocation invokeWithTarget:source];
NSUInteger length = [signature methodReturnLength];
void *value = malloc(length);
[invocation getReturnValue:value];
// Set converted value
signature = [target methodSignatureForSelector:setter];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&setter atIndex:1];
[invocation setArgument:value atIndex:2];
[invocation invokeWithTarget:target];
free(value);
return YES;
}

19
React/Base/RCTDevMenu.h Normal file
View File

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

84
React/Base/RCTDevMenu.m Normal file
View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDevMenu.h"
#import "RCTRedBox.h"
#import "RCTRootView.h"
@interface RCTDevMenu () <UIActionSheetDelegate> {
BOOL _liveReload;
}
@property (nonatomic, weak) RCTRootView *view;
@end
@implementation RCTDevMenu
- (instancetype)initWithRootView:(RCTRootView *)rootView
{
if (self = [super init]) {
self.view = rootView;
}
return self;
}
- (void)show
{
NSString *debugTitle = self.view.executorClass == nil ? @"Enable Debugging" : @"Disable Debugging";
NSString *liveReloadTitle = _liveReload ? @"Disable Live Reload" : @"Enable Live Reload";
UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitle, liveReloadTitle, nil];
actionSheet.actionSheetStyle = UIBarStyleBlack;
[actionSheet showInView:self.view];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) {
[self.view reload];
} else if (buttonIndex == 1) {
self.view.executorClass = self.view.executorClass == nil ? NSClassFromString(@"RCTWebSocketExecutor") : nil;
[self.view reload];
} else if (buttonIndex == 2) {
_liveReload = !_liveReload;
[self _pollAndReload];
}
}
- (void)_pollAndReload
{
if (_liveReload) {
NSURL *url = [self.view scriptURL];
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:url];
[self performSelectorInBackground:@selector(_checkForUpdates:) withObject:longPollURL];
}
}
- (void)_checkForUpdates:(NSURL *)URL
{
NSMutableURLRequest *longPollRequest = [NSMutableURLRequest requestWithURL:URL];
longPollRequest.timeoutInterval = 30;
NSHTTPURLResponse *response;
[NSURLConnection sendSynchronousRequest:longPollRequest returningResponse:&response error:nil];
dispatch_async(dispatch_get_main_queue(), ^{
if (_liveReload && response.statusCode == 205) {
[[RCTRedBox sharedInstance] dismiss];
[self.view reload];
}
[self _pollAndReload];
});
}
@end

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@class RCTBridge;
typedef NS_ENUM(NSInteger, RCTTextEventType) {
RCTTextEventTypeFocus,
RCTTextEventTypeBlur,
RCTTextEventTypeChange,
RCTTextEventTypeSubmit,
RCTTextEventTypeEnd
};
typedef NS_ENUM(NSInteger, RCTScrollEventType) {
RCTScrollEventTypeStart,
RCTScrollEventTypeMove,
RCTScrollEventTypeEnd,
RCTScrollEventTypeStartDeceleration,
RCTScrollEventTypeEndDeceleration,
RCTScrollEventTypeEndAnimation,
};
/**
* This class wraps the -[RCTBridge enqueueJSCall:args:] method, and
* provides some convenience methods for generating event calls.
*/
@interface RCTEventDispatcher : NSObject
- (instancetype)initWithBridge:(RCTBridge *)bridge;
/**
* Send an application-specific event that does not relate to a specific
* view, e.g. a navigation or data update notification.
*/
- (void)sendAppEventWithName:(NSString *)name body:(id)body;
/**
* Send a device or iOS event that does not relate to a specific view,
* e.g.rotation, location, keyboard show/hide, background/awake, etc.
*/
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body;
/**
* Send a user input event. The body dictionary must contain a "target"
* parameter, representing the react tag of the view sending the event
*/
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body;
/**
* Send a text input/focus event.
*/
- (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag
text:(NSString *)text;
/**
* Send a scroll event.
* (You can send a fake scroll event by passing nil for scrollView).
*/
- (void)sendScrollEventWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData;
@end

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTEventDispatcher.h"
#import "RCTAssert.h"
#import "RCTBridge.h"
@implementation RCTEventDispatcher
{
RCTBridge __weak *_bridge;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super init])) {
_bridge = bridge;
}
return self;
}
+ (NSArray *)JSMethods
{
return @[
@"RCTNativeAppEventEmitter.emit",
@"RCTDeviceEventEmitter.emit",
@"RCTEventEmitter.receiveEvent",
];
}
- (void)sendAppEventWithName:(NSString *)name body:(id)body
{
[_bridge enqueueJSCall:@"RCTNativeAppEventEmitter.emit"
args:body ? @[name, body] : @[name]];
}
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body
{
[_bridge enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:body ? @[name, body] : @[name]];
}
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
{
RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]],
@"Event body dictionary must include a 'target' property containing a react tag");
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
args:body ? @[body[@"target"], name, body] : @[body[@"target"], name]];
}
- (void)sendTextEventWithType:(RCTTextEventType)type
reactTag:(NSNumber *)reactTag
text:(NSString *)text
{
static NSString *events[] = {
@"topFocus",
@"topBlur",
@"topChange",
@"topSubmitEditing",
@"topEndEditing",
};
[self sendInputEventWithName:events[type] body:text ? @{
@"text": text,
@"target": reactTag
} : @{
@"target": reactTag
}];
}
/**
* TODO: throttling
* NOTE: the old system used a per-scrollview throttling
* which would be fairly easy to re-implement if needed,
* but this is non-optimal as it leads to degradation in
* scroll responsiveness. A better solution would be to
* coalesce multiple scroll events into a single batch.
*/
- (void)sendScrollEventWithType:(RCTScrollEventType)type
reactTag:(NSNumber *)reactTag
scrollView:(UIScrollView *)scrollView
userData:(NSDictionary *)userData
{
static NSString *events[] = {
@"topScrollBeginDrag",
@"topScroll",
@"topScrollEndDrag",
@"topMomentumScrollBegin",
@"topMomentumScrollEnd",
@"topScrollAnimationEnd",
};
NSDictionary *body = @{
@"contentOffset": @{
@"x": @(scrollView.contentOffset.x),
@"y": @(scrollView.contentOffset.y)
},
@"contentSize": @{
@"width": @(scrollView.contentSize.width),
@"height": @(scrollView.contentSize.height)
},
@"layoutMeasurement": @{
@"width": @(scrollView.frame.size.width),
@"height": @(scrollView.frame.size.height)
},
@"zoomScale": @(scrollView.zoomScale ?: 1),
@"target": reactTag
};
if (userData) {
NSMutableDictionary *mutableBody = [body mutableCopy];
[mutableBody addEntriesFromDictionary:userData];
body = mutableBody;
}
[self sendInputEventWithName:events[type] body:body];
}
@end

View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <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

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
@class RCTBridge;
/**
* Provides an interface to register JS methods to be called via the bridge.
*/
@protocol RCTJSMethodRegistrar <NSObject>
@optional
/**
* An array of JavaScript methods that the class will call via the
* -[RCTBridge enqueueJSCall:args:] method. Each method should be specified
* as a string of the form "JSModuleName.jsMethodName". Attempting to call a
* method that has not been registered will result in an error. If a method
* has already been registered by another class, it is not necessary to
* register it again, but it is good practice. Registering the same method
* more than once is silently ignored and will not result in an error.
*/
+ (NSArray *)JSMethods;
@end

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <JavaScriptCore/JavaScriptCore.h>
#import "RCTInvalidating.h"
typedef void (^RCTJavaScriptCompleteBlock)(NSError *error);
typedef void (^RCTJavaScriptCallback)(id json, 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

View File

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

123
React/Base/RCTKeyCommands.m Normal file
View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "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

65
React/Base/RCTLog.h Normal file
View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "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
extern __unsafe_unretained NSString *RCTLogLevels[];
#define _RCTLog(_level, ...) do { \
NSString *__RCTLog__levelStr = RCTLogLevels[_level - 1]; \
NSString *__RCTLog__msg = RCTLogObjects(RCTLogFormat(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__), __RCTLog__levelStr); \
if (_level >= RCTLOG_FATAL_LEVEL) { \
BOOL __RCTLog__fail = YES; \
if (RCTLOG_FATAL_REGEX) { \
NSRegularExpression *__RCTLog__regex = [NSRegularExpression regularExpressionWithPattern:RCTLOG_FATAL_REGEX options:0 error:NULL]; \
__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 (_level >= RCTLOG_REDBOX_LEVEL) { \
[[RCTRedBox sharedInstance] 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__)
#ifdef __cplusplus
extern "C" {
#endif
NSString *RCTLogObjects(NSArray *objects, NSString *level);
NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5);
void RCTInjectLogFunction(void (^logFunction)(NSString *msg));
#ifdef __cplusplus
}
#endif

93
React/Base/RCTLog.m Normal file
View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTLog.h"
#import "RCTBridge.h"
__unsafe_unretained NSString *RCTLogLevels[] = {
@"info",
@"warn",
@"error",
@"mustfix"
};
static void (^RCTInjectedLogFunction)(NSString *msg);
void RCTInjectLogFunction(void (^logFunction)(NSString *msg)) {
RCTInjectedLogFunction = logFunction;
}
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];
}
// 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 (RCTInjectedLogFunction) {
RCTInjectedLogFunction(str);
} else {
NSLog(@">\n %@", str);
}
}
return str;
}
// 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.
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);
[objects addObject:obj ?: @"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;
}

25
React/Base/RCTRedBox.h Normal file
View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@interface 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;
- (NSString *)currentErrorMessage;
- (void)dismiss;
@end

316
React/Base/RCTRedBox.m Normal file
View File

@@ -0,0 +1,316 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTRedBox.h"
#import "RCTUtils.h"
@interface RCTRedBoxWindow : UIWindow <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, copy) NSString *lastErrorMessage;
@end
@implementation RCTRedBoxWindow
{
UIView *_rootView;
UITableView *_stackTraceTableView;
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
{
if ((self.hidden && shouldShow) || (!self.hidden && [_lastErrorMessage isEqualToString:message])) {
_lastStackTrace = stack;
_lastErrorMessage = message;
_cachedMessageCell = [self reuseCell:nil forErrorMessage:message];
[_stackTraceTableView reloadData];
[_stackTraceTableView setNeedsLayout];
if (self.hidden) {
[_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
atScrollPosition:UITableViewScrollPositionTop
animated:NO];
}
[self makeKeyAndVisible];
[self becomeFirstResponder];
}
}
- (void)dismiss
{
self.hidden = YES;
[self resignFirstResponder];
[[[[UIApplication sharedApplication] delegate] window] makeKeyWindow];
}
- (void)reload
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTReloadNotification" object:nil userInfo:nil];
[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
}
- (NSString *)currentErrorMessage
{
if (_window && !_window.hidden) {
return _window.lastErrorMessage;
} else {
return nil;
}
}
- (void)dismiss
{
[_window dismiss];
}
@end

70
React/Base/RCTRootView.h Normal file
View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
@interface RCTRootView : UIView<RCTInvalidating>
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
launchOptions:(NSDictionary *)launchOptions /* NS_DESIGNATED_INITIALIZER */;
/**
* The URL of the bundled application script (required).
* Setting this will clear the view contents, and trigger
* an asynchronous load/download and execution of the script.
*/
@property (nonatomic, strong, readonly) NSURL *scriptURL;
/**
* The name of the JavaScript module to execute within the
* specified scriptURL (required). Setting this will not have
* any immediate effect, but it must be done prior to loading
* the script.
*/
@property (nonatomic, copy, readonly) NSString *moduleName;
/**
* A block that returns an array of pre-allocated modules. These
* modules will take precedence over any automatically registered
* modules of the same name.
*/
@property (nonatomic, copy, readonly) RCTBridgeModuleProviderBlock moduleProvider;
/**
* The default properties to apply to the view when the script bundle
* is first loaded. Defaults to nil/empty.
*/
@property (nonatomic, copy) NSDictionary *initialProperties;
/**
* The class of the RCTJavaScriptExecutor to use with this view.
* If not specified, it will default to using RCTContextExecutor.
* Changes will take effect next time the bundle is reloaded.
*/
@property (nonatomic, strong) Class executorClass;
/**
* If YES will watch for shake gestures and show development menu
* with options like "Reload", "Enable Debugging", etc.
*/
@property (nonatomic, assign) BOOL enableDevMenu;
/**
* Reload this root view, or all root views, respectively.
*/
- (void)reload;
+ (void)reloadAll;
- (void)startOrResetInteractionTiming;
- (NSDictionary *)endAndResetInteractionTiming;
@end

352
React/Base/RCTRootView.m Normal file
View File

@@ -0,0 +1,352 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTRootView.h"
#import "RCTBridge.h"
#import "RCTContextExecutor.h"
#import "RCTDevMenu.h"
#import "RCTEventDispatcher.h"
#import "RCTKeyCommands.h"
#import "RCTLog.h"
#import "RCTRedBox.h"
#import "RCTSourceCode.h"
#import "RCTTouchHandler.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "RCTWebViewExecutor.h"
#import "UIView+React.h"
NSString *const RCTReloadNotification = @"RCTReloadNotification";
/**
* HACK(t6568049) This should be removed soon, hiding to prevent people from
* relying on it
*/
@interface RCTBridge (RCTRootView)
- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor;
@end
@implementation RCTRootView
{
RCTDevMenu *_devMenu;
RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
id<RCTJavaScriptExecutor> _executor;
BOOL _registered;
NSDictionary *_launchOptions;
}
static Class _globalExecutorClass;
+ (void)initialize
{
#if TARGET_IPHONE_SIMULATOR
// Register Cmd-R as a global refresh key
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[self reloadAll];
}];
// Cmd-D reloads using the web view executor, allows attaching from Safari dev tools.
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
_globalExecutorClass = NSClassFromString(@"RCTWebSocketExecutor");
if (!_globalExecutorClass) {
RCTLogError(@"WebSocket debugger is not available. Did you forget to include RCTWebSocketExecutor?");
}
[self reloadAll];
}];
#endif
}
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
launchOptions:(NSDictionary *)launchOptions
{
if ((self = [super init])) {
RCTAssert(bundleURL, @"A bundleURL is required to create an RCTRootView");
RCTAssert(moduleName, @"A bundleURL is required to create an RCTRootView");
_moduleName = moduleName;
_launchOptions = launchOptions;
[self setUp];
[self setScriptURL:bundleURL];
}
return self;
}
/**
* HACK(t6568049) Private constructor for testing purposes
*/
- (instancetype)_initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
launchOptions:(NSDictionary *)launchOptions
moduleProvider:(RCTBridgeModuleProviderBlock)moduleProvider
{
if ((self = [super init])) {
_moduleProvider = moduleProvider;
_moduleName = moduleName;
_launchOptions = launchOptions;
[self setUp];
[self setScriptURL:bundleURL];
}
return self;
}
- (void)setUp
{
// 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);
#ifdef DEBUG
self.enableDevMenu = YES;
#endif
self.backgroundColor = [UIColor whiteColor];
rootViewTag += 10;
// Add reload observer
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTReloadNotification
object:nil];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake && self.enableDevMenu) {
if (!_devMenu) {
_devMenu = [[RCTDevMenu alloc] initWithRootView:self];
}
[_devMenu show];
}
}
+ (NSArray *)JSMethods
{
return @[
@"AppRegistry.runApplication",
@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[self.reactTag]];
[self invalidate];
}
#pragma mark - RCTInvalidating
- (BOOL)isValid
{
return [_bridge isValid];
}
- (void)invalidate
{
// Clear view
[self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
[self removeGestureRecognizer:_touchHandler];
[_touchHandler invalidate];
[_executor invalidate];
// TODO: eventually we'll want to be able to share the bridge between
// multiple rootviews, in which case we'll need to move this elsewhere
[_bridge invalidate];
}
#pragma mark Bundle loading
- (void)bundleFinishedLoading:(NSError *)error
{
if (error != nil) {
NSArray *stack = [[error userInfo] objectForKey:@"stack"];
if (stack) {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withStack:stack];
} else {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription] withDetails:[error localizedFailureReason]];
}
} else {
[_bridge.uiManager registerRootView:self];
_registered = YES;
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@"rootTag": self.reactTag,
@"initialProps": self.initialProperties ?: @{},
};
[_bridge enqueueJSCall:@"AppRegistry.runApplication"
args:@[moduleName, appParameters]];
}
}
- (void)loadBundle
{
[self invalidate];
if (!_scriptURL) {
return;
}
// Clean up
[self removeGestureRecognizer:_touchHandler];
[_touchHandler invalidate];
[_executor invalidate];
[_bridge invalidate];
_registered = NO;
// Choose local executor if specified, followed by global, followed by default
_executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init];
/**
* HACK(t6568049) Most of the properties passed into the bridge are not used
* right now but it'll be changed soon so it's here for convenience.
*/
_bridge = [[RCTBridge alloc] initWithBundlePath:_scriptURL.absoluteString
moduleProvider:_moduleProvider
launchOptions:_launchOptions];
[_bridge setJavaScriptExecutor:_executor];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[self addGestureRecognizer:_touchHandler];
// Load the bundle
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:_scriptURL completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// Handle general request errors
if (error) {
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: @"Could not connect to development server. Ensure node server is running - run 'npm start' from React root",
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
};
error = [NSError errorWithDomain:@"JSServer"
code:error.code
userInfo:userInfo];
}
[self bundleFinishedLoading:error];
return;
}
// Parse response as text
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];
// Handle HTTP errors
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};
}
error = [NSError errorWithDomain:@"JSServer"
code:[(NSHTTPURLResponse *)response statusCode]
userInfo:userInfo];
[self bundleFinishedLoading:error];
return;
}
if (!_bridge.isValid) {
return; // Bridge was invalidated in the meanwhile
}
// Success!
RCTSourceCode *sourceCodeModule = _bridge.modules[NSStringFromClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = _scriptURL;
sourceCodeModule.scriptText = rawText;
[_bridge enqueueApplicationScript:rawText url:_scriptURL onComplete:^(NSError *_error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (_bridge.isValid) {
[self bundleFinishedLoading:_error];
}
});
}];
}];
[task resume];
}
- (void)setScriptURL:(NSURL *)scriptURL
{
if ([_scriptURL isEqual:scriptURL]) {
return;
}
_scriptURL = scriptURL;
[self loadBundle];
}
- (void)layoutSubviews
{
[super layoutSubviews];
if (_registered) {
[_bridge.uiManager setFrame:self.frame forRootView:self];
}
}
- (void)reload
{
[self loadBundle];
}
+ (void)reloadAll
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil];
}
- (void)startOrResetInteractionTiming
{
[_touchHandler startOrResetInteractionTiming];
}
- (NSDictionary *)endAndResetInteractionTiming
{
return [_touchHandler endAndResetInteractionTiming];
}
@end

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
@interface RCTSparseArray : NSObject <NSCopying>
- (instancetype)initWithCapacity:(NSUInteger)capacity NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithSparseArray:(RCTSparseArray *)sparseArray NS_DESIGNATED_INITIALIZER;
+ (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;
- (void)removeAllObjects;
@end

123
React/Base/RCTSparseArray.m Normal file
View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "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)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);
}];
}
- (void)removeAllObjects
{
[_storage removeAllObjects];
}
- (id)copyWithZone:(NSZone *)zone
{
return [[[self class] allocWithZone:zone] initWithSparseArray:self];
}
- (NSString *)description
{
return [[super description] stringByAppendingString:[_storage description]];
}
@end

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "RCTInvalidating.h"
@class RCTBridge;
@interface RCTTouchHandler : UIGestureRecognizer<RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (void)startOrResetInteractionTiming;
- (NSDictionary *)endAndResetInteractionTiming;
@end

View File

@@ -0,0 +1,358 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTTouchHandler.h"
#import <UIKit/UIGestureRecognizerSubclass.h>
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTLog.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "UIView+React.h"
// TODO: this class behaves a lot like a module, and could be implemented as a
// module if we were to assume that modules and RootViews had a 1:1 relationship
@interface RCTTouchEvent : NSObject
@property (nonatomic, assign, readonly) NSUInteger id;
@property (nonatomic, copy, readonly) NSString *eventName;
@property (nonatomic, copy, readonly) NSArray *touches;
@property (nonatomic, copy, readonly) NSArray *changedIndexes;
@property (nonatomic, assign, readonly) CFTimeInterval originatingTime;
@end
@implementation RCTTouchEvent
+ (instancetype)touchWithEventName:(NSString *)eventName touches:(NSArray *)touches changedIndexes:(NSArray *)changedIndexes originatingTime:(CFTimeInterval)originatingTime
{
RCTTouchEvent *touchEvent = [[self alloc] init];
touchEvent->_id = [self newID];
touchEvent->_eventName = [eventName copy];
touchEvent->_touches = [touches copy];
touchEvent->_changedIndexes = [changedIndexes copy];
touchEvent->_originatingTime = originatingTime;
return touchEvent;
}
+ (NSUInteger)newID
{
static NSUInteger id = 0;
return ++id;
}
@end
@implementation RCTTouchHandler
{
__weak RCTBridge *_bridge;
/**
* Arrays managed in parallel tracking native touch object along with the
* native view that was touched, and the react touch data dictionary.
* 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.
*/
NSMutableOrderedSet *_nativeTouches;
NSMutableArray *_reactTouches;
NSMutableArray *_touchViews;
BOOL _recordingInteractionTiming;
CFTimeInterval _mostRecentEnqueueJS;
CADisplayLink *_displayLink;
NSMutableArray *_pendingTouches;
NSMutableArray *_bridgeInteractionTiming;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super initWithTarget:nil action:NULL])) {
RCTAssert(bridge != nil, @"Expect an event dispatcher");
_bridge = bridge;
_nativeTouches = [[NSMutableOrderedSet alloc] init];
_reactTouches = [[NSMutableArray alloc] init];
_touchViews = [[NSMutableArray alloc] init];
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update:)];
_pendingTouches = [[NSMutableArray alloc] init];
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
// `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.
self.cancelsTouchesInView = NO;
}
return self;
}
- (BOOL)isValid
{
return _displayLink != nil;
}
- (void)invalidate
{
[_displayLink invalidate];
_displayLink = nil;
}
typedef NS_ENUM(NSInteger, RCTTouchEventType) {
RCTTouchEventTypeStart,
RCTTouchEventTypeMove,
RCTTouchEventTypeEnd,
RCTTouchEventTypeCancel
};
#pragma mark - Bookkeeping for touch indices
- (void)_recordNewTouches:(NSSet *)touches
{
for (UITouch *touch in touches) {
RCTAssert(![_nativeTouches containsObject:touch],
@"Touch is already recorded. This is a critical bug.");
// Find closest React-managed touchable view
UIView *targetView = touch.view;
while (targetView) {
if (targetView.reactTag && targetView.userInteractionEnabled &&
[targetView reactRespondsToTouch:touch]) {
break;
}
targetView = targetView.superview;
}
NSNumber *reactTag = [targetView reactTagAtPoint:[touch locationInView:targetView]];
if (!reactTag || !targetView.userInteractionEnabled) {
return;
}
// Get new, unique touch id
const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices
NSInteger touchID = ([_reactTouches.lastObject[@"target"] integerValue] + 1) % RCTMaxTouches;
for (NSDictionary *reactTouch in _reactTouches) {
NSInteger usedID = [reactTouch[@"target"] 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;
}
}
// Create touch
NSMutableDictionary *reactTouch = [[NSMutableDictionary alloc] initWithCapacity:9];
reactTouch[@"target"] = reactTag;
reactTouch[@"identifier"] = @(touchID);
reactTouch[@"touches"] = [NSNull null]; // We hijack this touchObj to serve both as an event
reactTouch[@"changedTouches"] = [NSNull null]; // and as a Touch object, so making this JIT friendly.
// Add to arrays
[_touchViews addObject:targetView];
[_nativeTouches addObject:touch];
[_reactTouches addObject:reactTouch];
}
}
- (void)_recordRemovedTouches:(NSSet *)touches
{
for (UITouch *touch in touches) {
NSUInteger index = [_nativeTouches indexOfObject:touch];
if(index == NSNotFound) {
continue;
}
[_touchViews removeObjectAtIndex:index];
[_nativeTouches removeObjectAtIndex:index];
[_reactTouches removeObjectAtIndex:index];
}
}
- (void)_updateReactTouchAtIndex:(NSInteger)touchIndex
{
UITouch *nativeTouch = _nativeTouches[touchIndex];
CGPoint windowLocation = [nativeTouch locationInView:nativeTouch.window];
CGPoint rootViewLocation = [nativeTouch.window convertPoint:windowLocation toView:self.view];
UIView *touchView = _touchViews[touchIndex];
CGPoint touchViewLocation = [nativeTouch.window convertPoint:windowLocation toView:touchView];
NSMutableDictionary *reactTouch = _reactTouches[touchIndex];
reactTouch[@"pageX"] = @(rootViewLocation.x);
reactTouch[@"pageY"] = @(rootViewLocation.y);
reactTouch[@"locationX"] = @(touchViewLocation.x);
reactTouch[@"locationY"] = @(touchViewLocation.y);
reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS
}
+ (NSArray *)JSMethods
{
return @[@"RCTEventEmitter.receiveTouches"];
}
/**
* 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.
*/
- (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventName originatingTime:(CFTimeInterval)originatingTime
{
// Update touches
CFTimeInterval enqueueTime = CACurrentMediaTime();
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
for (UITouch *touch in touches) {
NSInteger index = [_nativeTouches indexOfObject:touch];
if (index == NSNotFound) {
continue;
}
[self _updateReactTouchAtIndex:index];
[changedIndexes addObject:@(index)];
}
if (changedIndexes.count == 0) {
return;
}
// Deep copy the touches because they will be accessed from another thread
// TODO: would it be safer to do this in the bridge or executor, rather than trusting caller?
NSMutableArray *reactTouches = [[NSMutableArray alloc] initWithCapacity:_reactTouches.count];
for (NSDictionary *touch in _reactTouches) {
[reactTouches addObject:[touch copy]];
}
RCTTouchEvent *touch = [RCTTouchEvent touchWithEventName:eventName
touches:reactTouches
changedIndexes:changedIndexes
originatingTime:originatingTime];
[_pendingTouches addObject:touch];
if (_recordingInteractionTiming) {
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(touch.originatingTime),
@"operation": @"taskOriginated",
@"taskID": @(touch.id),
}];
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(enqueueTime),
@"operation": @"taskEnqueuedPending",
@"taskID": @(touch.id),
}];
}
}
- (void)_update:(CADisplayLink *)sender
{
// Dispatch touch event
NSUInteger pendingCount = _pendingTouches.count;
for (RCTTouchEvent *touch in _pendingTouches) {
_mostRecentEnqueueJS = CACurrentMediaTime();
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveTouches"
args:@[touch.eventName, touch.touches, touch.changedIndexes]];
}
if (_recordingInteractionTiming) {
for (RCTTouchEvent *touch in _pendingTouches) {
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(sender.timestamp),
@"operation": @"frameAlignedDispatch",
@"taskID": @(touch.id),
}];
}
if (pendingCount > 0 || sender.timestamp - _mostRecentEnqueueJS < 0.1) {
[_bridgeInteractionTiming addObject:@{
@"timeSeconds": @(sender.timestamp),
@"operation": @"mainThreadDisplayLink",
@"taskID": @([RCTTouchEvent newID]),
}];
}
}
[_pendingTouches removeAllObjects];
}
- (void)startOrResetInteractionTiming
{
RCTAssertMainThread();
[_bridgeInteractionTiming removeAllObjects];
_recordingInteractionTiming = YES;
}
- (NSDictionary *)endAndResetInteractionTiming
{
RCTAssertMainThread();
_recordingInteractionTiming = NO;
NSArray *_prevInteractionTimingData = _bridgeInteractionTiming;
_bridgeInteractionTiming = [[NSMutableArray alloc] init];
return @{ @"interactionTiming": _prevInteractionTimingData };
}
#pragma mark - Gesture Recognizer Delegate Callbacks
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
self.state = UIGestureRecognizerStateBegan;
// "start" has to record new touches before extracting the event.
// "end"/"cancel" needs to remove the touch *after* extracting the event.
[self _recordNewTouches:touches];
[self _updateAndDispatchTouches:touches eventName:@"topTouchStart" originatingTime:event.timestamp];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
if (self.state == UIGestureRecognizerStateFailed) {
return;
}
[self _updateAndDispatchTouches:touches eventName:@"topTouchMove" originatingTime:event.timestamp];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventName:@"topTouchEnd" originatingTime:event.timestamp];
[self _recordRemovedTouches:touches];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesCancelled:touches withEvent:event];
[self _updateAndDispatchTouches:touches eventName:@"topTouchCancel" originatingTime:event.timestamp];
[self _recordRemovedTouches:touches];
}
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
{
return NO;
}
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
{
return NO;
}
@end

58
React/Base/RCTUtils.h Normal file
View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <tgmath.h>
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
#import "RCTAssert.h"
#ifdef __cplusplus
extern "C" {
#endif
// 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 (TODO: currently unused. Remove?)
NSString *RCTMD5Hash(NSString *string);
// Get screen metrics in a thread-safe way
CGFloat RCTScreenScale(void);
CGSize RCTScreenSize(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);
// Module subclass support
BOOL RCTClassOverridesClassMethod(Class cls, SEL selector);
BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector);
// Enumerate all classes that conform to NSObject protocol
void RCTEnumerateClasses(void (^block)(Class cls));
// Creates a standardized error object
// TODO(#6472857): create NSErrors and automatically convert them over the bridge.
NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData);
NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData);
#ifdef __cplusplus
}
#endif

228
React/Base/RCTUtils.m Normal file
View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTUtils.h"
#import <mach/mach_time.h>
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
#import <CommonCrypto/CommonCrypto.h>
#import "RCTLog.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)
{
if (!jsonString) {
return nil;
}
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
if (!jsonData) {
jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
if (jsonData) {
RCTLogWarn(@"RCTJSONParse received the following string, which could not be losslessly converted to UTF8 data: '%@'", jsonString);
} else {
// If our backup conversion fails, log the issue so we can see what strings are causing this (t6452813)
RCTLogError(@"RCTJSONParse received the following string, which could not be converted to UTF8 data: '%@'", jsonString);
return nil;
}
}
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;
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;
}
CGSize RCTScreenSize()
{
static CGSize size;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
size = [UIScreen mainScreen].bounds.size;
});
} else {
size = [UIScreen mainScreen].bounds.size;
}
});
return size;
}
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);
}
}
BOOL RCTClassOverridesClassMethod(Class cls, SEL selector)
{
return RCTClassOverridesInstanceMethod(object_getClass(cls), selector);
}
BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector)
{
unsigned int numberOfMethods;
Method *methods = class_copyMethodList(cls, &numberOfMethods);
for (unsigned int i = 0; i < numberOfMethods; i++)
{
if (method_getName(methods[i]) == selector)
{
free(methods);
return YES;
}
}
free(methods);
return NO;
}
void RCTEnumerateClasses(void (^block)(Class cls))
{
static Class *classes;
static unsigned int classCount;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
classes = objc_copyClassList(&classCount);
});
for (unsigned int i = 0; i < classCount; i++)
{
Class cls = classes[i];
Class superclass = cls;
while (superclass)
{
if (class_conformsToProtocol(superclass, @protocol(NSObject)))
{
block(cls);
break;
}
superclass = class_getSuperclass(superclass);
}
}
}
NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData)
{
if (toStringify) {
message = [NSString stringWithFormat:@"%@%@", message, toStringify];
}
NSMutableDictionary *error = [@{@"message": message} mutableCopy];
if (extraData) {
[error addEntriesFromDictionary:extraData];
}
return error;
}
NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData)
{
id error = RCTMakeError(message, toStringify, extraData);
RCTLogError(@"\nError: %@", error);
return error;
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <JavaScriptCore/JavaScriptCore.h>
#import "RCTJavaScriptExecutor.h"
// TODO (#5906496): Might RCTJSCoreExecutor be a better name for this?
/**
* Uses a JavaScriptCore context as the execution engine.
*/
@interface RCTContextExecutor : NSObject <RCTJavaScriptExecutor>
/**
* Configures the executor to run JavaScript on a custom performer.
* You probably don't want to use this; use -init instead.
*/
- (instancetype)initWithJavaScriptThread:(NSThread *)javaScriptThread
globalContextRef:(JSGlobalContextRef)context;
@end

View File

@@ -0,0 +1,301 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTContextExecutor.h"
#import <pthread.h>
#import <JavaScriptCore/JavaScriptCore.h>
#import "RCTAssert.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@implementation RCTContextExecutor
{
JSGlobalContextRef _context;
NSThread *_javaScriptThread;
}
/**
* The one tiny pure native hook that we implement is a native logging hook.
* You could even argue that this is not necessary - we could plumb logging
* calls through a batched bridge, but having the pure native hook allows
* logging to successfully come through even in the event that a batched bridge
* crashes.
*/
static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception)
{
if (argumentCount > 0) {
JSStringRef string = JSValueToStringCopy(context, arguments[0], exception);
if (!string) {
return JSValueMakeUndefined(context);
}
NSString *str = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string);
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
@"( stack: )?([_a-z0-9]*)@?(http://|file:///)[a-z.0-9:/_-]+/([a-z0-9_]+).includeRequire.runModule.bundle(:[0-9]+:[0-9]+)"
options:NSRegularExpressionCaseInsensitive
error:&error];
NSString *modifiedString = [regex stringByReplacingMatchesInString:str options:0 range:NSMakeRange(0, [str length]) withTemplate:@"[$4$5] \t$2"];
modifiedString = [@"RCTJSLog> " stringByAppendingString:modifiedString];
#if TARGET_IPHONE_SIMULATOR
fprintf(stderr, "%s\n", [modifiedString UTF8String]); // don't print timestamps and other junk
#else
// Print normal errors with timestamps to files when not in simulator.
RCTLogObjects(@[modifiedString], @"log");
#endif
JSStringRelease(string);
}
return JSValueMakeUndefined(context);
}
// Do-very-little native hook for testing.
static JSValueRef RCTNoop(JSContextRef context, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception)
{
static int counter = 0;
counter++;
return JSValueMakeUndefined(context);
}
static NSString *RCTJSValueToNSString(JSContextRef context, JSValueRef value)
{
JSStringRef JSString = JSValueToStringCopy(context, value, NULL);
CFStringRef string = JSStringCopyCFString(kCFAllocatorDefault, JSString);
JSStringRelease(JSString);
return (__bridge_transfer NSString *)string;
}
static NSString *RCTJSValueToJSONString(JSContextRef context, JSValueRef value, unsigned indent)
{
JSStringRef JSString = JSValueCreateJSONString(context, value, indent, NULL);
CFStringRef string = JSStringCopyCFString(kCFAllocatorDefault, JSString);
JSStringRelease(JSString);
return (__bridge_transfer NSString *)string;
}
static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
{
NSString *errorMessage = jsError ? RCTJSValueToNSString(context, jsError) : @"unknown JS error";
NSString *details = jsError ? RCTJSValueToJSONString(context, jsError, 2) : @"no details";
return [NSError errorWithDomain:@"JS" code:1 userInfo:@{NSLocalizedDescriptionKey: errorMessage, NSLocalizedFailureReasonErrorKey: details}];
}
+ (void)runRunLoopThread
{
// TODO (#5906496): Investigate exactly what this does and why
@autoreleasepool {
// copy thread name to pthread name
pthread_setname_np([[[NSThread currentThread] name] UTF8String]);
// Set up a dummy runloop source to avoid spinning
CFRunLoopSourceContext noSpinCtx = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef noSpinSource = CFRunLoopSourceCreate(NULL, 0, &noSpinCtx);
CFRunLoopAddSource(CFRunLoopGetCurrent(), noSpinSource, kCFRunLoopDefaultMode);
CFRelease(noSpinSource);
// run the run loop
while (kCFRunLoopRunStopped != CFRunLoopRunInMode(kCFRunLoopDefaultMode, [[NSDate distantFuture] timeIntervalSinceReferenceDate], NO)) {
RCTAssert(NO, @"not reached assertion"); // runloop spun. that's bad.
}
}
}
- (instancetype)init
{
static NSThread *javaScriptThread;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// All JS is single threaded, so a serial queue is our only option.
javaScriptThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoopThread) object:nil];
[javaScriptThread setName:@"com.facebook.React.JavaScript"];
[javaScriptThread setThreadPriority:[[NSThread mainThread] threadPriority]];
[javaScriptThread start];
});
return [self initWithJavaScriptThread:javaScriptThread globalContextRef:NULL];
}
- (instancetype)initWithJavaScriptThread:(NSThread *)javaScriptThread
globalContextRef:(JSGlobalContextRef)context
{
if ((self = [super init])) {
_javaScriptThread = javaScriptThread;
[self executeBlockOnJavaScriptQueue: ^{
// Assumes that no other JS tasks are scheduled before.
if (context) {
_context = JSGlobalContextRetain(context);
} else {
JSContextGroupRef group = JSContextGroupCreate();
_context = JSGlobalContextCreateInGroup(group, NULL);
#if FB_JSC_HACK
JSContextGroupBindToCurrentThread(group);
#endif
JSContextGroupRelease(group);
}
[self _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"];
[self _addNativeHook:RCTNoop withName:"noop"];
}];
}
return self;
}
- (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char *)name
{
JSObjectRef globalObject = JSContextGetGlobalObject(_context);
JSStringRef JSName = JSStringCreateWithUTF8CString(name);
JSObjectSetProperty(_context, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context, JSName, hook), kJSPropertyAttributeNone, NULL);
JSStringRelease(JSName);
}
- (BOOL)isValid
{
return _context != NULL;
}
- (void)invalidate
{
if ([NSThread currentThread] != _javaScriptThread) {
// Yes, block until done. If we're getting called right before dealloc, it's the only safe option.
[self performSelector:@selector(invalidate) onThread:_javaScriptThread withObject:nil waitUntilDone:YES];
} else if (_context != NULL) {
JSGlobalContextRelease(_context);
_context = NULL;
}
}
- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
}
- (void)executeJSCall:(NSString *)name
method:(NSString *)method
arguments:(NSArray *)arguments
callback:(RCTJavaScriptCallback)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
[self executeBlockOnJavaScriptQueue:^{
NSError *error;
NSString *argsString = RCTJSONStringify(arguments, &error);
if (!argsString) {
RCTLogError(@"Cannot convert argument to string: %@", error);
onComplete(nil, error);
return;
}
NSString *execString = [NSString stringWithFormat:@"require('%@').%@.apply(null, %@);", name, method, argsString];
JSValueRef jsError = NULL;
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)execString);
JSValueRef result = JSEvaluateScript(_context, execJSString, NULL, NULL, 0, &jsError);
JSStringRelease(execJSString);
if (!result) {
onComplete(nil, RCTNSErrorFromJSError(_context, jsError));
return;
}
// Looks like making lots of JSC API calls is slower than communicating by using a JSON
// string. Also it ensures that data stuctures don't have cycles and non-serializable fields.
// see [RCTContextExecutorTests testDeserializationPerf]
id objcValue;
// We often return `null` from JS when there is nothing for native side. JSONKit takes an extra hundred microseconds
// to handle this simple case, so we are adding a shortcut to make executeJSCall method even faster
if (!JSValueIsNull(_context, result)) {
JSStringRef jsJSONString = JSValueCreateJSONString(_context, result, 0, nil);
if (jsJSONString) {
NSString *objcJSONString = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsJSONString);
JSStringRelease(jsJSONString);
objcValue = RCTJSONParse(objcJSONString, NULL);
}
}
onComplete(objcValue, nil);
}];
}
- (void)executeApplicationScript:(NSString *)script
sourceURL:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(url != nil, @"url should not be nil");
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
[self executeBlockOnJavaScriptQueue:^{
JSValueRef jsError = NULL;
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
JSStringRef sourceURL = JSStringCreateWithCFString((__bridge CFStringRef)url.absoluteString);
JSValueRef result = JSEvaluateScript(_context, execJSString, NULL, sourceURL, 0, &jsError);
JSStringRelease(sourceURL);
JSStringRelease(execJSString);
NSError *error;
if (!result) {
error = RCTNSErrorFromJSError(_context, jsError);
}
onComplete(error);
}];
}
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
{
if ([NSThread currentThread] != _javaScriptThread) {
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
} else {
block();
}
}
- (void)injectJSONText:(NSString *)script
asGlobalObjectNamed:(NSString *)objectName
callback:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block should not be nil");
#if DEBUG
RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script);
#endif
[self executeBlockOnJavaScriptQueue:^{
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
JSValueRef valueToInject = JSValueMakeFromJSONString(_context, execJSString);
JSStringRelease(execJSString);
if (!valueToInject) {
NSString *errorDesc = [NSString stringWithFormat:@"Can't make JSON value from script '%@'", script];
RCTLogError(@"%@", errorDesc);
NSError *error = [NSError errorWithDomain:@"JS" code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}];
onComplete(error);
return;
}
JSObjectRef globalObject = JSContextGetGlobalObject(_context);
JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)objectName);
JSObjectSetProperty(_context, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL);
JSStringRelease(JSName);
onComplete(nil);
}];
}
@end

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "RCTJavaScriptExecutor.h"
/**
* Uses an embedded web view merely for the purpose of being able to reuse the
* existing webkit debugging tools. Fulfills the role of a very constrained
* `JSContext`, which we call `RCTJavaScriptExecutor`.
*
* TODO: To ensure production-identical execution, scrub the window
* environment. And ensure main thread operations are actually added to a queue
* instead of being executed immediately if already on the main thread.
*/
@interface RCTWebViewExecutor : NSObject<RCTJavaScriptExecutor>
// Only one callback stored - will only be invoked for the latest issued
// application script request.
@property (nonatomic, copy) RCTJavaScriptCompleteBlock onApplicationScriptLoaded;
/**
* Instantiate with a specific webview instance
*/
- (instancetype)initWithWebView:(UIWebView *)webView NS_DESIGNATED_INITIALIZER;
/**
* Invoke this to reclaim the web view for reuse. This is necessary in order to
* allow debuggers to remain open, when creating a new `RCTWebViewExecutor`.
* This guards against the web view being invalidated, and makes sure the
* `delegate` is cleared first.
*/
- (UIWebView *)invalidateAndReclaimWebView;
@end

View File

@@ -0,0 +1,192 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTWebViewExecutor.h"
#import <objc/runtime.h>
#import "RCTLog.h"
#import "RCTUtils.h"
static void RCTReportError(RCTJavaScriptCallback callback, NSString *fmt, ...)
{
va_list args;
va_start(args, fmt);
NSString *description = [[NSString alloc] initWithFormat:fmt arguments:args];
RCTLogError(@"%@", description);
NSError *error = [NSError errorWithDomain:NSStringFromClass([RCTWebViewExecutor class])
code:3
userInfo:@{NSLocalizedDescriptionKey:description}];
callback(nil, error);
va_end(args);
}
@interface RCTWebViewExecutor () <UIWebViewDelegate>
@end
@implementation RCTWebViewExecutor
{
UIWebView *_webView;
NSMutableDictionary *_objectsToInject;
}
- (instancetype)initWithWebView:(UIWebView *)webView
{
if (!webView) {
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Can't init with a nil webview" userInfo:nil];
}
if ((self = [super init])) {
_objectsToInject = [[NSMutableDictionary alloc] init];
_webView = webView;
_webView.delegate = self;
}
return self;
}
- (id)init
{
return [self initWithWebView:[[UIWebView alloc] init]];
}
- (BOOL)isValid
{
return _webView != nil;
}
- (void)invalidate
{
_webView.delegate = nil;
_webView = nil;
}
- (UIWebView *)invalidateAndReclaimWebView
{
UIWebView *webView = _webView;
[self invalidate];
return webView;
}
- (void)executeJSCall:(NSString *)name
method:(NSString *)method
arguments:(NSArray *)arguments
callback:(RCTJavaScriptCallback)onComplete
{
RCTAssert(onComplete != nil, @"");
[self executeBlockOnJavaScriptQueue:^{
NSError *error;
NSString *argsString = RCTJSONStringify(arguments, &error);
if (!argsString) {
RCTReportError(onComplete, @"Cannot convert argument to string: %@", error);
return;
}
NSString *execString = [NSString stringWithFormat:@"JSON.stringify(require('%@').%@.apply(null, %@));", name, method, argsString];
NSString *ret = [_webView stringByEvaluatingJavaScriptFromString:execString];
if (ret.length == 0) {
RCTReportError(onComplete, @"Empty return string: JavaScript error running script: %@", execString);
return;
}
id objcValue = RCTJSONParse(ret, &error);
if (!objcValue) {
RCTReportError(onComplete, @"Cannot parse json response: %@", error);
return;
}
onComplete(objcValue, nil);
}];
}
/**
* We cannot use the standard eval JS method. Source will not show up in the
* debugger. So we have to use this (essentially) async API - and register
* ourselves as the webview delegate to be notified when load is complete.
*/
- (void)executeApplicationScript:(NSString *)script
sourceURL:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self executeApplicationScript:script sourceURL:url onComplete:onComplete];
});
return;
}
RCTAssert(onComplete != nil, @"");
_onApplicationScriptLoaded = onComplete;
if (_objectsToInject.count > 0) {
NSMutableString *scriptWithInjections = [[NSMutableString alloc] initWithString:@"/* BEGIN NATIVELY INJECTED OBJECTS */\n"];
[_objectsToInject enumerateKeysAndObjectsUsingBlock:^(NSString *objectName, NSString *blockScript, BOOL *stop) {
[scriptWithInjections appendString:objectName];
[scriptWithInjections appendString:@" = ("];
[scriptWithInjections appendString:blockScript];
[scriptWithInjections appendString:@");\n"];
}];
[_objectsToInject removeAllObjects];
[scriptWithInjections appendString:@"/* END NATIVELY INJECTED OBJECTS */\n"];
[scriptWithInjections appendString:script];
script = scriptWithInjections;
}
NSString *runScript =
[NSString
stringWithFormat:@"<html><head></head><body><script type='text/javascript'>%@</script></body></html>",
script
];
[_webView loadHTMLString:runScript baseURL:url];
}
/**
* In order to avoid `UIWebView` thread locks, all JS executions should be
* performed outside of the event loop that notifies the `UIWebViewDelegate`
* that the page has loaded. This is only an issue with the remote debug mode of
* `UIWebView`. For a production `UIWebView` deployment, this delay is
* unnecessary and possibly harmful (or helpful?)
*
* The delay might not be needed as soon as the following change lands into
* iOS7. (Review the patch linked here and search for "crash"
* https://bugs.webkit.org/show_bug.cgi?id=125746).
*/
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
{
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC);
dispatch_after(when, dispatch_get_main_queue(), ^{
RCTAssertMainThread();
block();
});
}
/**
* `UIWebViewDelegate` methods. Handle application script load.
*/
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
RCTAssertMainThread();
if (_onApplicationScriptLoaded) {
_onApplicationScriptLoaded(nil); // TODO(frantic): how to fetch error from UIWebView?
}
_onApplicationScriptLoaded = nil;
}
- (void)injectJSONText:(NSString *)script
asGlobalObjectNamed:(NSString *)objectName
callback:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(!_objectsToInject[objectName],
@"already injected object named %@", _objectsToInject[objectName]);
_objectsToInject[objectName] = script;
onComplete(nil);
}
@end

817
React/Layout/Layout.c Normal file
View File

@@ -0,0 +1,817 @@
/**
* @generated SignedSource<<ebd198356f7097798f4891c0fff583cf>>
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in from github! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Go to https://github.com/facebook/css-layout !!
* !! 2) Make a pull request and get it merged !!
* !! 3) Execute ./import.sh to pull in the latest version !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "Layout.h"
bool isUndefined(float value) {
return isnan(value);
}
static bool eq(float a, float b) {
if (isUndefined(a)) {
return isUndefined(b);
}
return fabs(a - b) < 0.0001;
}
void init_css_node(css_node_t *node) {
node->style.align_items = CSS_ALIGN_STRETCH;
// Some of the fields default to undefined and not 0
node->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED;
node->style.dimensions[CSS_HEIGHT] = CSS_UNDEFINED;
node->style.position[CSS_LEFT] = CSS_UNDEFINED;
node->style.position[CSS_TOP] = CSS_UNDEFINED;
node->style.position[CSS_RIGHT] = CSS_UNDEFINED;
node->style.position[CSS_BOTTOM] = CSS_UNDEFINED;
node->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED;
node->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED;
// Such that the comparison is always going to be false
node->layout.last_requested_dimensions[CSS_WIDTH] = -1;
node->layout.last_requested_dimensions[CSS_HEIGHT] = -1;
node->layout.last_parent_max_width = -1;
node->layout.should_update = true;
}
css_node_t *new_css_node() {
css_node_t *node = calloc(1, sizeof(*node));
init_css_node(node);
return node;
}
void free_css_node(css_node_t *node) {
free(node);
}
static void indent(int n) {
for (int i = 0; i < n; ++i) {
printf(" ");
}
}
static void print_number_0(const char *str, float number) {
if (!eq(number, 0)) {
printf("%s: %g, ", str, number);
}
}
static void print_number_nan(const char *str, float number) {
if (!isnan(number)) {
printf("%s: %g, ", str, number);
}
}
static bool four_equal(float four[4]) {
return
eq(four[0], four[1]) &&
eq(four[0], four[2]) &&
eq(four[0], four[3]);
}
static void print_css_node_rec(
css_node_t *node,
css_print_options_t options,
int level
) {
indent(level);
printf("{");
if (node->print) {
node->print(node->context);
}
if (options & CSS_PRINT_LAYOUT) {
printf("layout: {");
printf("width: %g, ", node->layout.dimensions[CSS_WIDTH]);
printf("height: %g, ", node->layout.dimensions[CSS_HEIGHT]);
printf("top: %g, ", node->layout.position[CSS_TOP]);
printf("left: %g", node->layout.position[CSS_LEFT]);
printf("}, ");
}
if (options & CSS_PRINT_STYLE) {
if (node->style.flex_direction == CSS_FLEX_DIRECTION_ROW) {
printf("flexDirection: 'row', ");
}
if (node->style.justify_content == CSS_JUSTIFY_CENTER) {
printf("justifyContent: 'center', ");
} else if (node->style.justify_content == CSS_JUSTIFY_FLEX_END) {
printf("justifyContent: 'flex-end', ");
} else if (node->style.justify_content == CSS_JUSTIFY_SPACE_AROUND) {
printf("justifyContent: 'space-around', ");
} else if (node->style.justify_content == CSS_JUSTIFY_SPACE_BETWEEN) {
printf("justifyContent: 'space-between', ");
}
if (node->style.align_items == CSS_ALIGN_CENTER) {
printf("alignItems: 'center', ");
} else if (node->style.align_items == CSS_ALIGN_FLEX_END) {
printf("alignItems: 'flex-end', ");
} else if (node->style.align_items == CSS_ALIGN_STRETCH) {
printf("alignItems: 'stretch', ");
}
if (node->style.align_self == CSS_ALIGN_FLEX_START) {
printf("alignSelf: 'flex-start', ");
} else if (node->style.align_self == CSS_ALIGN_CENTER) {
printf("alignSelf: 'center', ");
} else if (node->style.align_self == CSS_ALIGN_FLEX_END) {
printf("alignSelf: 'flex-end', ");
} else if (node->style.align_self == CSS_ALIGN_STRETCH) {
printf("alignSelf: 'stretch', ");
}
print_number_nan("flex", node->style.flex);
if (four_equal(node->style.margin)) {
print_number_0("margin", node->style.margin[CSS_LEFT]);
} else {
print_number_0("marginLeft", node->style.margin[CSS_LEFT]);
print_number_0("marginRight", node->style.margin[CSS_RIGHT]);
print_number_0("marginTop", node->style.margin[CSS_TOP]);
print_number_0("marginBottom", node->style.margin[CSS_BOTTOM]);
}
if (four_equal(node->style.padding)) {
print_number_0("padding", node->style.margin[CSS_LEFT]);
} else {
print_number_0("paddingLeft", node->style.padding[CSS_LEFT]);
print_number_0("paddingRight", node->style.padding[CSS_RIGHT]);
print_number_0("paddingTop", node->style.padding[CSS_TOP]);
print_number_0("paddingBottom", node->style.padding[CSS_BOTTOM]);
}
if (four_equal(node->style.border)) {
print_number_0("borderWidth", node->style.border[CSS_LEFT]);
} else {
print_number_0("borderLeftWidth", node->style.border[CSS_LEFT]);
print_number_0("borderRightWidth", node->style.border[CSS_RIGHT]);
print_number_0("borderTopWidth", node->style.border[CSS_TOP]);
print_number_0("borderBottomWidth", node->style.border[CSS_BOTTOM]);
}
print_number_nan("width", node->style.dimensions[CSS_WIDTH]);
print_number_nan("height", node->style.dimensions[CSS_HEIGHT]);
if (node->style.position_type == CSS_POSITION_ABSOLUTE) {
printf("position: 'absolute', ");
}
print_number_nan("left", node->style.position[CSS_LEFT]);
print_number_nan("right", node->style.position[CSS_RIGHT]);
print_number_nan("top", node->style.position[CSS_TOP]);
print_number_nan("bottom", node->style.position[CSS_BOTTOM]);
}
if (options & CSS_PRINT_CHILDREN && node->children_count > 0) {
printf("children: [\n");
for (int i = 0; i < node->children_count; ++i) {
print_css_node_rec(node->get_child(node->context, i), options, level + 1);
}
indent(level);
printf("]},\n");
} else {
printf("},\n");
}
}
void print_css_node(css_node_t *node, css_print_options_t options) {
print_css_node_rec(node, options, 0);
}
static css_position_t leading[2] = {
/* CSS_FLEX_DIRECTION_COLUMN = */ CSS_TOP,
/* CSS_FLEX_DIRECTION_ROW = */ CSS_LEFT
};
static css_position_t trailing[2] = {
/* CSS_FLEX_DIRECTION_COLUMN = */ CSS_BOTTOM,
/* CSS_FLEX_DIRECTION_ROW = */ CSS_RIGHT
};
static css_position_t pos[2] = {
/* CSS_FLEX_DIRECTION_COLUMN = */ CSS_TOP,
/* CSS_FLEX_DIRECTION_ROW = */ CSS_LEFT
};
static css_dimension_t dim[2] = {
/* CSS_FLEX_DIRECTION_COLUMN = */ CSS_HEIGHT,
/* CSS_FLEX_DIRECTION_ROW = */ CSS_WIDTH
};
static float getMargin(css_node_t *node, int location) {
return node->style.margin[location];
}
static float getPadding(css_node_t *node, int location) {
if (node->style.padding[location] >= 0) {
return node->style.padding[location];
}
return 0;
}
static float getBorder(css_node_t *node, int location) {
if (node->style.border[location] >= 0) {
return node->style.border[location];
}
return 0;
}
static float getPaddingAndBorder(css_node_t *node, int location) {
return getPadding(node, location) + getBorder(node, location);
}
static float getMarginAxis(css_node_t *node, css_flex_direction_t axis) {
return getMargin(node, leading[axis]) + getMargin(node, trailing[axis]);
}
static float getPaddingAndBorderAxis(css_node_t *node, css_flex_direction_t axis) {
return getPaddingAndBorder(node, leading[axis]) + getPaddingAndBorder(node, trailing[axis]);
}
static css_position_type_t getPositionType(css_node_t *node) {
return node->style.position_type;
}
static css_justify_t getJustifyContent(css_node_t *node) {
return node->style.justify_content;
}
static css_align_t getAlignItem(css_node_t *node, css_node_t *child) {
if (child->style.align_self != CSS_ALIGN_AUTO) {
return child->style.align_self;
}
return node->style.align_items;
}
static css_flex_direction_t getFlexDirection(css_node_t *node) {
return node->style.flex_direction;
}
static float getFlex(css_node_t *node) {
return node->style.flex;
}
static bool isFlex(css_node_t *node) {
return (
getPositionType(node) == CSS_POSITION_RELATIVE &&
getFlex(node) > 0
);
}
static bool isFlexWrap(css_node_t *node) {
return node->style.flex_wrap == CSS_WRAP;
}
static float getDimWithMargin(css_node_t *node, css_flex_direction_t axis) {
return node->layout.dimensions[dim[axis]] +
getMargin(node, leading[axis]) +
getMargin(node, trailing[axis]);
}
static bool isDimDefined(css_node_t *node, css_flex_direction_t axis) {
return !isUndefined(node->style.dimensions[dim[axis]]);
}
static bool isPosDefined(css_node_t *node, css_position_t position) {
return !isUndefined(node->style.position[position]);
}
static bool isMeasureDefined(css_node_t *node) {
return node->measure;
}
static float getPosition(css_node_t *node, css_position_t position) {
float result = node->style.position[position];
if (!isUndefined(result)) {
return result;
}
return 0;
}
// When the user specifically sets a value for width or height
static void setDimensionFromStyle(css_node_t *node, css_flex_direction_t axis) {
// The parent already computed us a width or height. We just skip it
if (!isUndefined(node->layout.dimensions[dim[axis]])) {
return;
}
// We only run if there's a width or height defined
if (!isDimDefined(node, axis)) {
return;
}
// The dimensions can never be smaller than the padding and border
node->layout.dimensions[dim[axis]] = fmaxf(
node->style.dimensions[dim[axis]],
getPaddingAndBorderAxis(node, axis)
);
}
// If both left and right are defined, then use left. Otherwise return
// +left or -right depending on which is defined.
static float getRelativePosition(css_node_t *node, css_flex_direction_t axis) {
float lead = node->style.position[leading[axis]];
if (!isUndefined(lead)) {
return lead;
}
return -getPosition(node, trailing[axis]);
}
static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
/** START_GENERATED **/
css_flex_direction_t mainAxis = getFlexDirection(node);
css_flex_direction_t crossAxis = mainAxis == CSS_FLEX_DIRECTION_ROW ?
CSS_FLEX_DIRECTION_COLUMN :
CSS_FLEX_DIRECTION_ROW;
// Handle width and height style attributes
setDimensionFromStyle(node, mainAxis);
setDimensionFromStyle(node, crossAxis);
// The position is set by the parent, but we need to complete it with a
// delta composed of the margin and left/top/right/bottom
node->layout.position[leading[mainAxis]] += getMargin(node, leading[mainAxis]) +
getRelativePosition(node, mainAxis);
node->layout.position[leading[crossAxis]] += getMargin(node, leading[crossAxis]) +
getRelativePosition(node, crossAxis);
if (isMeasureDefined(node)) {
float width = CSS_UNDEFINED;
if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {
width = node->style.dimensions[CSS_WIDTH];
} else if (!isUndefined(node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]])) {
width = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]];
} else {
width = parentMaxWidth -
getMarginAxis(node, CSS_FLEX_DIRECTION_ROW);
}
width -= getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
// We only need to give a dimension for the text if we haven't got any
// for it computed yet. It can either be from the style attribute or because
// the element is flexible.
bool isRowUndefined = !isDimDefined(node, CSS_FLEX_DIRECTION_ROW) &&
isUndefined(node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]]);
bool isColumnUndefined = !isDimDefined(node, CSS_FLEX_DIRECTION_COLUMN) &&
isUndefined(node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]);
// Let's not measure the text if we already know both dimensions
if (isRowUndefined || isColumnUndefined) {
css_dim_t measure_dim = node->measure(
node->context,
width
);
if (isRowUndefined) {
node->layout.dimensions[CSS_WIDTH] = measure_dim.dimensions[CSS_WIDTH] +
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
}
if (isColumnUndefined) {
node->layout.dimensions[CSS_HEIGHT] = measure_dim.dimensions[CSS_HEIGHT] +
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);
}
}
return;
}
// Pre-fill some dimensions straight from the parent
for (int i = 0; i < node->children_count; ++i) {
css_node_t* child = node->get_child(node->context, i);
// Pre-fill cross axis dimensions when the child is using stretch before
// we call the recursive layout pass
if (getAlignItem(node, child) == CSS_ALIGN_STRETCH &&
getPositionType(child) == CSS_POSITION_RELATIVE &&
!isUndefined(node->layout.dimensions[dim[crossAxis]]) &&
!isDimDefined(child, crossAxis)) {
child->layout.dimensions[dim[crossAxis]] = fmaxf(
node->layout.dimensions[dim[crossAxis]] -
getPaddingAndBorderAxis(node, crossAxis) -
getMarginAxis(child, crossAxis),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, crossAxis)
);
} else if (getPositionType(child) == CSS_POSITION_ABSOLUTE) {
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (int ii = 0; ii < 2; ii++) {
css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(child, axis) &&
isPosDefined(child, leading[axis]) &&
isPosDefined(child, trailing[axis])) {
child->layout.dimensions[dim[axis]] = fmaxf(
node->layout.dimensions[dim[axis]] -
getPaddingAndBorderAxis(node, axis) -
getMarginAxis(child, axis) -
getPosition(child, leading[axis]) -
getPosition(child, trailing[axis]),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, axis)
);
}
}
}
}
float definedMainDim = CSS_UNDEFINED;
if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) {
definedMainDim = node->layout.dimensions[dim[mainAxis]] -
getPaddingAndBorderAxis(node, mainAxis);
}
// We want to execute the next two loops one per line with flex-wrap
int startLine = 0;
int endLine = 0;
int nextLine = 0;
// We aggregate the total dimensions of the container in those two variables
float linesCrossDim = 0;
float linesMainDim = 0;
while (endLine != node->children_count) {
// <Loop A> Layout non flexible children and count children by type
// mainContentDim is accumulation of the dimensions and margin of all the
// non flexible children. This will be used in order to either set the
// dimensions of the node if none already exist, or to compute the
// remaining space left for the flexible children.
float mainContentDim = 0;
// There are three kind of children, non flexible, flexible and absolute.
// We need to know how many there are in order to distribute the space.
int flexibleChildrenCount = 0;
float totalFlexible = 0;
int nonFlexibleChildrenCount = 0;
for (int i = startLine; i < node->children_count; ++i) {
css_node_t* child = node->get_child(node->context, i);
float nextContentDim = 0;
// It only makes sense to consider a child flexible if we have a computed
// dimension for the node->
if (!isUndefined(node->layout.dimensions[dim[mainAxis]]) && isFlex(child)) {
flexibleChildrenCount++;
totalFlexible += getFlex(child);
// Even if we don't know its exact size yet, we already know the padding,
// border and margin. We'll use this partial information to compute the
// remaining space.
nextContentDim = getPaddingAndBorderAxis(child, mainAxis) +
getMarginAxis(child, mainAxis);
} else {
float maxWidth = CSS_UNDEFINED;
if (mainAxis == CSS_FLEX_DIRECTION_ROW) {
// do nothing
} else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {
maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] -
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
} else {
maxWidth = parentMaxWidth -
getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) -
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
}
// This is the main recursive call. We layout non flexible children.
if (nextLine == 0) {
layoutNode(child, maxWidth);
}
// Absolute positioned elements do not take part of the layout, so we
// don't use them to compute mainContentDim
if (getPositionType(child) == CSS_POSITION_RELATIVE) {
nonFlexibleChildrenCount++;
// At this point we know the final size and margin of the element.
nextContentDim = getDimWithMargin(child, mainAxis);
}
}
// The element we are about to add would make us go to the next line
if (isFlexWrap(node) &&
!isUndefined(node->layout.dimensions[dim[mainAxis]]) &&
mainContentDim + nextContentDim > definedMainDim) {
nextLine = i + 1;
break;
}
nextLine = 0;
mainContentDim += nextContentDim;
endLine = i + 1;
}
// <Loop B> Layout flexible children and allocate empty space
// In order to position the elements in the main axis, we have two
// controls. The space between the beginning and the first element
// and the space between each two elements.
float leadingMainDim = 0;
float betweenMainDim = 0;
// The remaining available space that needs to be allocated
float remainingMainDim = 0;
if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) {
remainingMainDim = definedMainDim - mainContentDim;
} else {
remainingMainDim = fmaxf(mainContentDim, 0) - mainContentDim;
}
// If there are flexible children in the mix, they are going to fill the
// remaining space
if (flexibleChildrenCount != 0) {
float flexibleMainDim = remainingMainDim / totalFlexible;
// The non flexible children can overflow the container, in this case
// we should just assume that there is no space available.
if (flexibleMainDim < 0) {
flexibleMainDim = 0;
}
// We iterate over the full array and only apply the action on flexible
// children. This is faster than actually allocating a new array that
// contains only flexible children.
for (int i = startLine; i < endLine; ++i) {
css_node_t* child = node->get_child(node->context, i);
if (isFlex(child)) {
// At this point we know the final size of the element in the main
// dimension
child->layout.dimensions[dim[mainAxis]] = flexibleMainDim * getFlex(child) +
getPaddingAndBorderAxis(child, mainAxis);
float maxWidth = CSS_UNDEFINED;
if (mainAxis == CSS_FLEX_DIRECTION_ROW) {
// do nothing
} else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {
maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] -
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
} else {
maxWidth = parentMaxWidth -
getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) -
getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);
}
// And we recursively call the layout algorithm for this child
layoutNode(child, maxWidth);
}
}
// We use justifyContent to figure out how to allocate the remaining
// space available
} else {
css_justify_t justifyContent = getJustifyContent(node);
if (justifyContent == CSS_JUSTIFY_FLEX_START) {
// Do nothing
} else if (justifyContent == CSS_JUSTIFY_CENTER) {
leadingMainDim = remainingMainDim / 2;
} else if (justifyContent == CSS_JUSTIFY_FLEX_END) {
leadingMainDim = remainingMainDim;
} else if (justifyContent == CSS_JUSTIFY_SPACE_BETWEEN) {
remainingMainDim = fmaxf(remainingMainDim, 0);
if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 != 0) {
betweenMainDim = remainingMainDim /
(flexibleChildrenCount + nonFlexibleChildrenCount - 1);
} else {
betweenMainDim = 0;
}
} else if (justifyContent == CSS_JUSTIFY_SPACE_AROUND) {
// Space on the edges is half of the space between elements
betweenMainDim = remainingMainDim /
(flexibleChildrenCount + nonFlexibleChildrenCount);
leadingMainDim = betweenMainDim / 2;
}
}
// <Loop C> Position elements in the main axis and compute dimensions
// At this point, all the children have their dimensions set. We need to
// find their position. In order to do that, we accumulate data in
// variables that are also useful to compute the total dimensions of the
// container!
float crossDim = 0;
float mainDim = leadingMainDim +
getPaddingAndBorder(node, leading[mainAxis]);
for (int i = startLine; i < endLine; ++i) {
css_node_t* child = node->get_child(node->context, i);
if (getPositionType(child) == CSS_POSITION_ABSOLUTE &&
isPosDefined(child, leading[mainAxis])) {
// In case the child is position absolute and has left/top being
// defined, we override the position to whatever the user said
// (and margin/border).
child->layout.position[pos[mainAxis]] = getPosition(child, leading[mainAxis]) +
getBorder(node, leading[mainAxis]) +
getMargin(child, leading[mainAxis]);
} else {
// If the child is position absolute (without top/left) or relative,
// we put it at the current accumulated offset.
child->layout.position[pos[mainAxis]] += mainDim;
}
// Now that we placed the element, we need to update the variables
// We only need to do that for relative elements. Absolute elements
// do not take part in that phase.
if (getPositionType(child) == CSS_POSITION_RELATIVE) {
// The main dimension is the sum of all the elements dimension plus
// the spacing.
mainDim += betweenMainDim + getDimWithMargin(child, mainAxis);
// The cross dimension is the max of the elements dimension since there
// can only be one element in that cross dimension.
crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis));
}
}
float containerMainAxis = node->layout.dimensions[dim[mainAxis]];
// If the user didn't specify a width or height, and it has not been set
// by the container, then we set it via the children.
if (isUndefined(node->layout.dimensions[dim[mainAxis]])) {
containerMainAxis = fmaxf(
// We're missing the last padding at this point to get the final
// dimension
mainDim + getPaddingAndBorder(node, trailing[mainAxis]),
// We can never assign a width smaller than the padding and borders
getPaddingAndBorderAxis(node, mainAxis)
);
}
float containerCrossAxis = node->layout.dimensions[dim[crossAxis]];
if (isUndefined(node->layout.dimensions[dim[crossAxis]])) {
containerCrossAxis = fmaxf(
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
crossDim + getPaddingAndBorderAxis(node, crossAxis),
getPaddingAndBorderAxis(node, crossAxis)
);
}
// <Loop D> Position elements in the cross axis
for (int i = startLine; i < endLine; ++i) {
css_node_t* child = node->get_child(node->context, i);
if (getPositionType(child) == CSS_POSITION_ABSOLUTE &&
isPosDefined(child, leading[crossAxis])) {
// In case the child is absolutely positionned and has a
// top/left/bottom/right being set, we override all the previously
// computed positions to set it correctly.
child->layout.position[pos[crossAxis]] = getPosition(child, leading[crossAxis]) +
getBorder(node, leading[crossAxis]) +
getMargin(child, leading[crossAxis]);
} else {
float leadingCrossDim = getPaddingAndBorder(node, leading[crossAxis]);
// For a relative children, we're either using alignItems (parent) or
// alignSelf (child) in order to determine the position in the cross axis
if (getPositionType(child) == CSS_POSITION_RELATIVE) {
css_align_t alignItem = getAlignItem(node, child);
if (alignItem == CSS_ALIGN_FLEX_START) {
// Do nothing
} else if (alignItem == CSS_ALIGN_STRETCH) {
// You can only stretch if the dimension has not already been set
// previously.
if (!isDimDefined(child, crossAxis)) {
child->layout.dimensions[dim[crossAxis]] = fmaxf(
containerCrossAxis -
getPaddingAndBorderAxis(node, crossAxis) -
getMarginAxis(child, crossAxis),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, crossAxis)
);
}
} else {
// The remaining space between the parent dimensions+padding and child
// dimensions+margin.
float remainingCrossDim = containerCrossAxis -
getPaddingAndBorderAxis(node, crossAxis) -
getDimWithMargin(child, crossAxis);
if (alignItem == CSS_ALIGN_CENTER) {
leadingCrossDim += remainingCrossDim / 2;
} else { // CSS_ALIGN_FLEX_END
leadingCrossDim += remainingCrossDim;
}
}
}
// And we apply the position
child->layout.position[pos[crossAxis]] += linesCrossDim + leadingCrossDim;
}
}
linesCrossDim += crossDim;
linesMainDim = fmaxf(linesMainDim, mainDim);
startLine = endLine;
}
// If the user didn't specify a width or height, and it has not been set
// by the container, then we set it via the children.
if (isUndefined(node->layout.dimensions[dim[mainAxis]])) {
node->layout.dimensions[dim[mainAxis]] = fmaxf(
// We're missing the last padding at this point to get the final
// dimension
linesMainDim + getPaddingAndBorder(node, trailing[mainAxis]),
// We can never assign a width smaller than the padding and borders
getPaddingAndBorderAxis(node, mainAxis)
);
}
if (isUndefined(node->layout.dimensions[dim[crossAxis]])) {
node->layout.dimensions[dim[crossAxis]] = fmaxf(
// For the cross dim, we add both sides at the end because the value
// is aggregate via a max function. Intermediate negative values
// can mess this computation otherwise
linesCrossDim + getPaddingAndBorderAxis(node, crossAxis),
getPaddingAndBorderAxis(node, crossAxis)
);
}
// <Loop E> Calculate dimensions for absolutely positioned elements
for (int i = 0; i < node->children_count; ++i) {
css_node_t* child = node->get_child(node->context, i);
if (getPositionType(child) == CSS_POSITION_ABSOLUTE) {
// Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both
// left and right or top and bottom).
for (int ii = 0; ii < 2; ii++) {
css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (!isUndefined(node->layout.dimensions[dim[axis]]) &&
!isDimDefined(child, axis) &&
isPosDefined(child, leading[axis]) &&
isPosDefined(child, trailing[axis])) {
child->layout.dimensions[dim[axis]] = fmaxf(
node->layout.dimensions[dim[axis]] -
getPaddingAndBorderAxis(node, axis) -
getMarginAxis(child, axis) -
getPosition(child, leading[axis]) -
getPosition(child, trailing[axis]),
// You never want to go smaller than padding
getPaddingAndBorderAxis(child, axis)
);
}
}
for (int ii = 0; ii < 2; ii++) {
css_flex_direction_t axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;
if (isPosDefined(child, trailing[axis]) &&
!isPosDefined(child, leading[axis])) {
child->layout.position[leading[axis]] =
node->layout.dimensions[dim[axis]] -
child->layout.dimensions[dim[axis]] -
getPosition(child, trailing[axis]);
}
}
}
}
/** END_GENERATED **/
}
void layoutNode(css_node_t *node, float parentMaxWidth) {
css_layout_t *layout = &node->layout;
layout->should_update = true;
bool skipLayout =
!node->is_dirty(node->context) &&
eq(layout->last_requested_dimensions[CSS_WIDTH], layout->dimensions[CSS_WIDTH]) &&
eq(layout->last_requested_dimensions[CSS_HEIGHT], layout->dimensions[CSS_HEIGHT]) &&
eq(layout->last_parent_max_width, parentMaxWidth);
if (skipLayout) {
layout->dimensions[CSS_WIDTH] = layout->last_dimensions[CSS_WIDTH];
layout->dimensions[CSS_HEIGHT] = layout->last_dimensions[CSS_HEIGHT];
layout->position[CSS_TOP] = layout->last_position[CSS_TOP];
layout->position[CSS_LEFT] = layout->last_position[CSS_LEFT];
} else {
layout->last_requested_dimensions[CSS_WIDTH] = layout->dimensions[CSS_WIDTH];
layout->last_requested_dimensions[CSS_HEIGHT] = layout->dimensions[CSS_HEIGHT];
layout->last_parent_max_width = parentMaxWidth;
layoutNodeImpl(node, parentMaxWidth);
layout->last_dimensions[CSS_WIDTH] = layout->dimensions[CSS_WIDTH];
layout->last_dimensions[CSS_HEIGHT] = layout->dimensions[CSS_HEIGHT];
layout->last_position[CSS_TOP] = layout->position[CSS_TOP];
layout->last_position[CSS_LEFT] = layout->position[CSS_LEFT];
}
}

148
React/Layout/Layout.h Normal file
View File

@@ -0,0 +1,148 @@
/**
* @generated SignedSource<<58298c7a8815a8675e970b0347dedfed>>
*
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !! This file is a check-in from github! !!
* !! !!
* !! You should not modify this file directly. Instead: !!
* !! 1) Go to https://github.com/facebook/css-layout !!
* !! 2) Make a pull request and get it merged !!
* !! 3) Execute ./import.sh to pull in the latest version !!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef __LAYOUT_H
#define __LAYOUT_H
#include <math.h>
#include <stdbool.h>
#define CSS_UNDEFINED NAN
typedef enum {
CSS_FLEX_DIRECTION_COLUMN = 0,
CSS_FLEX_DIRECTION_ROW
} css_flex_direction_t;
typedef enum {
CSS_JUSTIFY_FLEX_START = 0,
CSS_JUSTIFY_CENTER,
CSS_JUSTIFY_FLEX_END,
CSS_JUSTIFY_SPACE_BETWEEN,
CSS_JUSTIFY_SPACE_AROUND
} css_justify_t;
// Note: auto is only a valid value for alignSelf. It is NOT a valid value for
// alignItems.
typedef enum {
CSS_ALIGN_AUTO = 0,
CSS_ALIGN_FLEX_START,
CSS_ALIGN_CENTER,
CSS_ALIGN_FLEX_END,
CSS_ALIGN_STRETCH
} css_align_t;
typedef enum {
CSS_POSITION_RELATIVE = 0,
CSS_POSITION_ABSOLUTE
} css_position_type_t;
typedef enum {
CSS_NOWRAP = 0,
CSS_WRAP
} css_wrap_type_t;
// Note: left and top are shared between position[2] and position[4], so
// they have to be before right and bottom.
typedef enum {
CSS_LEFT = 0,
CSS_TOP,
CSS_RIGHT,
CSS_BOTTOM,
CSS_POSITION_COUNT
} css_position_t;
typedef enum {
CSS_WIDTH = 0,
CSS_HEIGHT
} css_dimension_t;
typedef struct {
float position[2];
float dimensions[2];
// Instead of recomputing the entire layout every single time, we
// cache some information to break early when nothing changed
bool should_update;
float last_requested_dimensions[2];
float last_parent_max_width;
float last_dimensions[2];
float last_position[2];
} css_layout_t;
typedef struct {
float dimensions[2];
} css_dim_t;
typedef struct {
css_flex_direction_t flex_direction;
css_justify_t justify_content;
css_align_t align_items;
css_align_t align_self;
css_position_type_t position_type;
css_wrap_type_t flex_wrap;
float flex;
float margin[4];
float position[4];
/**
* You should skip all the rules that contain negative values for the
* following attributes. For example:
* {padding: 10, paddingLeft: -5}
* should output:
* {left: 10 ...}
* the following two are incorrect:
* {left: -5 ...}
* {left: 0 ...}
*/
float padding[4];
float border[4];
float dimensions[2];
} css_style_t;
typedef struct css_node {
css_style_t style;
css_layout_t layout;
int children_count;
css_dim_t (*measure)(void *context, float width);
void (*print)(void *context);
struct css_node* (*get_child)(void *context, int i);
bool (*is_dirty)(void *context);
void *context;
} css_node_t;
// Lifecycle of nodes and children
css_node_t *new_css_node(void);
void init_css_node(css_node_t *node);
void free_css_node(css_node_t *node);
// Print utilities
typedef enum {
CSS_PRINT_LAYOUT = 1,
CSS_PRINT_STYLE = 2,
CSS_PRINT_CHILDREN = 4,
} css_print_options_t;
void print_css_node(css_node_t *node, css_print_options_t options);
// Function that computes the layout!
void layoutNode(css_node_t *node, float maxWidth);
bool isUndefined(float value);
#endif

View File

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

View File

@@ -0,0 +1,114 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTAlertManager.h"
#import "RCTLog.h"
@interface RCTAlertManager() <UIAlertViewDelegate>
@end
@implementation RCTAlertManager
{
NSMutableArray *_alerts;
NSMutableArray *_alertCallbacks;
NSMutableArray *_alertButtonKeys;
}
- (instancetype)init
{
if ((self = [super init])) {
_alerts = [[NSMutableArray alloc] init];
_alertCallbacks = [[NSMutableArray alloc] init];
_alertButtonKeys = [[NSMutableArray alloc] init];
}
return self;
}
/**
* @param {NSDictionary} args Dictionary of the form
*
* @{
* @"message": @"<Alert message>",
* @"buttons": @[
* @{@"<key1>": @"<title1>"},
* @{@"<key2>": @"<cancelButtonTitle>"},
* ]
* }
* The key from the `buttons` dictionary is passed back in the callback on click.
* Buttons are displayed in the order they are specified. If "cancel" is used as
* the button key, it will be differently highlighted, according to iOS UI conventions.
*/
- (void)alertWithArgs:(NSDictionary *)args callback:(RCTResponseSenderBlock)callback
{
RCT_EXPORT();
NSString *title = args[@"title"];
NSString *message = args[@"message"];
NSArray *buttons = args[@"buttons"];
if (!title && !message) {
RCTLogError(@"Must specify either an alert title, or message, or both");
return;
} else if (buttons.count == 0) {
RCTLogError(@"Must have at least one button.");
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:title
message:message
delegate:self
cancelButtonTitle:nil
otherButtonTitles:nil];
NSMutableArray *buttonKeys = [[NSMutableArray alloc] initWithCapacity:buttons.count];
NSInteger index = 0;
for (NSDictionary *button in buttons) {
if (button.count != 1) {
RCTLogError(@"Button definitions should have exactly one key.");
}
NSString *buttonKey = [button.allKeys firstObject];
NSString *buttonTitle = [button[buttonKey] description];
[alertView addButtonWithTitle:buttonTitle];
if ([buttonKey isEqualToString: @"cancel"]) {
alertView.cancelButtonIndex = index;
}
[buttonKeys addObject:buttonKey];
index ++;
}
[_alerts addObject:alertView];
[_alertCallbacks addObject:callback ?: ^(id unused) {}];
[_alertButtonKeys addObject:buttonKeys];
[alertView show];
});
}
#pragma mark - UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSUInteger index = [_alerts indexOfObject:alertView];
RCTAssert(index != NSNotFound, @"Dismissed alert was not recognised");
RCTResponseSenderBlock callback = _alertCallbacks[index];
NSArray *buttonKeys = _alertButtonKeys[index];
callback(@[buttonKeys[buttonIndex]]);
[_alerts removeObjectAtIndex:index];
[_alertCallbacks removeObjectAtIndex:index];
[_alertButtonKeys removeObjectAtIndex:index];
}
@end

View File

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

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTAppState.h"
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
static NSString *RCTCurrentAppBackgroundState()
{
static NSDictionary *states;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
states = @{
@(UIApplicationStateActive): @"active",
@(UIApplicationStateBackground): @"background",
@(UIApplicationStateInactive): @"inactive"
};
});
return states[@([[UIApplication sharedApplication] applicationState])] ?: @"unknown";
}
@implementation RCTAppState
{
NSString *_lastKnownState;
}
@synthesize bridge = _bridge;
#pragma mark - Lifecycle
- (instancetype)init
{
if ((self = [super init])) {
_lastKnownState = RCTCurrentAppBackgroundState();
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification,
UIApplicationDidFinishLaunchingNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAppStateDidChange)
name:name
object:nil];
}
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - App Notification Methods
- (void)handleAppStateDidChange
{
NSString *newState = RCTCurrentAppBackgroundState();
if (![newState isEqualToString:_lastKnownState]) {
_lastKnownState = newState;
[_bridge.eventDispatcher sendDeviceEventWithName:@"appStateDidChange"
body:@{@"app_state": _lastKnownState}];
}
}
#pragma mark - Public API
/**
* Get the current background/foreground state of the app
*/
- (void)getCurrentAppState:(RCTResponseSenderBlock)callback
error:(__unused RCTResponseSenderBlock)error
{
RCT_EXPORT();
callback(@[@{@"app_state": _lastKnownState}]);
}
@end

View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTBridgeModule.h"
/**
* A simple, asynchronous, persistent, key-value storage system designed as a
* backend to the AsyncStorage JS module, which is modeled after LocalStorage.
*
* Current implementation stores small values in serialized dictionary and
* larger values in separate files. Since we use a serial file queue
* `RKFileQueue`, reading/writing from multiple threads should be perceived as
* being atomic, unless someone bypasses the `RCTAsyncLocalStorage` API.
*
* Keys and values must always be strings or an error is returned.
*/
@interface RCTAsyncLocalStorage : NSObject <RCTBridgeModule>
- (void)multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback;
- (void)multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback;
- (void)multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback;
- (void)clear:(RCTResponseSenderBlock)callback;
- (void)getAllKeys:(RCTResponseSenderBlock)callback;
@end

View File

@@ -0,0 +1,299 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTAsyncLocalStorage.h"
#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonDigest.h>
#import "RCTLog.h"
#import "RCTUtils.h"
static NSString *const kStorageDir = @"RCTAsyncLocalStorage_V1";
static NSString *const kManifestFilename = @"manifest.json";
static const NSUInteger kInlineValueThreshold = 100;
#pragma mark - Static helper functions
static id RCTErrorForKey(NSString *key)
{
if (![key isKindOfClass:[NSString class]]) {
return RCTMakeAndLogError(@"Invalid key - must be a string. Key: ", key, @{@"key": key});
} else if (key.length < 1) {
return RCTMakeAndLogError(@"Invalid key - must be at least one character. Key: ", key, @{@"key": key});
} else {
return nil;
}
}
static void RCTAppendError(id error, NSMutableArray **errors)
{
if (error && errors) {
if (!*errors) {
*errors = [NSMutableArray new];
}
[*errors addObject:error];
}
}
static id RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut)
{
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSError *error;
NSStringEncoding encoding;
NSString *entryString = [NSString stringWithContentsOfFile:filePath usedEncoding:&encoding error:&error];
if (error) {
*errorOut = RCTMakeError(@"Failed to read storage file.", error, @{@"key": key});
} else if (encoding != NSUTF8StringEncoding) {
*errorOut = RCTMakeError(@"Incorrect encoding of storage file: ", @(encoding), @{@"key": key});
} else {
return entryString;
}
}
return nil;
}
static dispatch_queue_t RCTFileQueue(void)
{
static dispatch_queue_t fileQueue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// All JS is single threaded, so a serial queue is our only option.
fileQueue = dispatch_queue_create("com.facebook.rkFile", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(fileQueue,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
});
return fileQueue;
}
#pragma mark - RCTAsyncLocalStorage
@implementation RCTAsyncLocalStorage
{
BOOL _haveSetup;
// The manifest is a dictionary of all keys with small values inlined. Null values indicate values that are stored
// in separate files (as opposed to nil values which don't exist). The manifest is read off disk at startup, and
// written to disk after all mutations.
NSMutableDictionary *_manifest;
NSString *_manifestPath;
NSString *_storageDirectory;
}
- (NSString *)_filePathForKey:(NSString *)key
{
NSString *safeFileName = RCTMD5Hash(key);
return [_storageDirectory stringByAppendingPathComponent:safeFileName];
}
- (id)_ensureSetup
{
if (_haveSetup) {
return nil;
}
NSString *documentDirectory =
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSURL *homeURL = [NSURL fileURLWithPath:documentDirectory isDirectory:YES];
_storageDirectory = [[homeURL URLByAppendingPathComponent:kStorageDir isDirectory:YES] path];
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:_storageDirectory
withIntermediateDirectories:YES
attributes:nil
error:&error];
if (error) {
return RCTMakeError(@"Failed to create storage directory.", error, nil);
}
_manifestPath = [_storageDirectory stringByAppendingPathComponent:kManifestFilename];
NSDictionary *errorOut;
NSString *serialized = RCTReadFile(_manifestPath, nil, &errorOut);
_manifest = serialized ? [RCTJSONParse(serialized, &error) mutableCopy] : [NSMutableDictionary new];
if (error) {
RCTLogWarn(@"Failed to parse manifest - creating new one.\n\n%@", error);
_manifest = [NSMutableDictionary new];
}
_haveSetup = YES;
return nil;
}
- (id)_writeManifest:(NSMutableArray **)errors
{
NSError *error;
NSString *serialized = RCTJSONStringify(_manifest, &error);
[serialized writeToFile:_manifestPath atomically:YES encoding:NSUTF8StringEncoding error:&error];
id errorOut;
if (error) {
errorOut = RCTMakeError(@"Failed to write manifest file.", error, nil);
RCTAppendError(errorOut, errors);
}
return errorOut;
}
- (id)_appendItemForKey:(NSString *)key toArray:(NSMutableArray *)result
{
id errorOut = RCTErrorForKey(key);
if (errorOut) {
return errorOut;
}
id value = _manifest[key]; // nil means missing, null means there is a data file, anything else is an inline value.
if (value == [NSNull null]) {
NSString *filePath = [self _filePathForKey:key];
value = RCTReadFile(filePath, key, &errorOut);
}
[result addObject:@[key, value ?: [NSNull null]]]; // Insert null if missing or failure.
return errorOut;
}
- (id)_writeEntry:(NSArray *)entry
{
if (![entry isKindOfClass:[NSArray class]] || entry.count != 2) {
return RCTMakeAndLogError(@"Entries must be arrays of the form [key: string, value: string], got: ", entry, nil);
}
if (![entry[1] isKindOfClass:[NSString class]]) {
return RCTMakeAndLogError(@"Values must be strings, got: ", entry[1], entry[0]);
}
NSString *key = entry[0];
id errorOut = RCTErrorForKey(key);
if (errorOut) {
return errorOut;
}
NSString *value = entry[1];
NSString *filePath = [self _filePathForKey:key];
NSError *error;
if (value.length <= kInlineValueThreshold) {
if (_manifest[key] && _manifest[key] != [NSNull null]) {
// If the value already existed but wasn't inlined, remove the old file.
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
}
_manifest[key] = value;
return nil;
}
[value writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error) {
errorOut = RCTMakeError(@"Failed to write value.", error, @{@"key": key});
} else {
_manifest[key] = [NSNull null]; // Mark existence of file with null, any other value is inline data.
}
return errorOut;
}
#pragma mark - Exported JS Functions
- (void)multiGet:(NSArray *)keys callback:(RCTResponseSenderBlock)callback
{
RCT_EXPORT();
if (!callback) {
RCTLogError(@"Called getItem without a callback.");
return;
}
dispatch_async(RCTFileQueue(), ^{
id errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut], [NSNull null]]);
return;
}
NSMutableArray *errors;
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:keys.count];
for (NSString *key in keys) {
id keyError = [self _appendItemForKey:key toArray:result];
RCTAppendError(keyError, &errors);
}
[self _writeManifest:&errors];
callback(@[errors ?: [NSNull null], result]);
});
}
- (void)multiSet:(NSArray *)kvPairs callback:(RCTResponseSenderBlock)callback
{
RCT_EXPORT();
dispatch_async(RCTFileQueue(), ^{
id errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut]]);
return;
}
NSMutableArray *errors;
for (NSArray *entry in kvPairs) {
id keyError = [self _writeEntry:entry];
RCTAppendError(keyError, &errors);
}
[self _writeManifest:&errors];
if (callback) {
callback(@[errors ?: [NSNull null]]);
}
});
}
- (void)multiRemove:(NSArray *)keys callback:(RCTResponseSenderBlock)callback
{
RCT_EXPORT();
dispatch_async(RCTFileQueue(), ^{
id errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut]]);
return;
}
NSMutableArray *errors;
for (NSString *key in keys) {
id keyError = RCTErrorForKey(key);
if (!keyError) {
NSString *filePath = [self _filePathForKey:key];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
[_manifest removeObjectForKey:key];
}
RCTAppendError(keyError, &errors);
}
[self _writeManifest:&errors];
if (callback) {
callback(@[errors ?: [NSNull null]]);
}
});
}
- (void)clear:(RCTResponseSenderBlock)callback
{
RCT_EXPORT();
dispatch_async(RCTFileQueue(), ^{
id errorOut = [self _ensureSetup];
if (!errorOut) {
NSError *error;
for (NSString *key in _manifest) {
NSString *filePath = [self _filePathForKey:key];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
}
[_manifest removeAllObjects];
errorOut = [self _writeManifest:nil];
}
if (callback) {
callback(@[errorOut ?: [NSNull null]]);
}
});
}
- (void)getAllKeys:(RCTResponseSenderBlock)callback
{
RCT_EXPORT();
dispatch_async(RCTFileQueue(), ^{
id errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[errorOut, [NSNull null]]);
} else {
callback(@[[NSNull null], [_manifest allKeys]]);
}
});
}
@end

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"
@protocol RCTExceptionsManagerDelegate <NSObject>
- (void)unhandledJSExceptionWithMessage:(NSString *)message stack:(NSArray *)stack;
@end
@interface RCTExceptionsManager : NSObject <RCTBridgeModule>
- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate NS_DESIGNATED_INITIALIZER;
@end

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTExceptionsManager.h"
#import "RCTRedBox.h"
@implementation RCTExceptionsManager
{
__weak id<RCTExceptionsManagerDelegate> _delegate;
}
- (instancetype)initWithDelegate:(id<RCTExceptionsManagerDelegate>)delegate
{
if ((self = [super init])) {
_delegate = delegate;
}
return self;
}
- (instancetype)init
{
return [self initWithDelegate:nil];
}
- (void)reportUnhandledExceptionWithMessage:(NSString *)message stack:(NSArray *)stack
{
RCT_EXPORT(reportUnhandledException);
if (_delegate) {
[_delegate unhandledJSExceptionWithMessage:message stack:stack];
} else {
[[RCTRedBox sharedInstance] showErrorMessage:message withStack:stack];
}
}
- (void)updateExceptionMessage:(NSString *)message stack:(NSArray *)stack
{
RCT_EXPORT(updateExceptionMessage);
[[RCTRedBox sharedInstance] updateErrorMessage:message withStack:stack];
}
@end

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
#import "RCTBridgeModule.h"
@interface RCTSourceCode : NSObject <RCTBridgeModule>
@property (nonatomic, copy) NSString *scriptText;
@property (nonatomic, copy) NSURL *scriptURL;
@end

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTSourceCode.h"
#import "RCTAssert.h"
#import "RCTUtils.h"
@implementation RCTSourceCode
- (void)getScriptText:(RCTResponseSenderBlock)successCallback failureCallback:(RCTResponseSenderBlock)failureCallback
{
RCT_EXPORT();
if (self.scriptText && self.scriptURL) {
successCallback(@[@{@"text": self.scriptText, @"url":[self.scriptURL absoluteString]}]);
} else {
failureCallback(@[RCTMakeError(@"Source code is not available", nil, nil)]);
}
}
@end

View File

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

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTStatusBarManager.h"
#import "RCTLog.h"
@implementation RCTStatusBarManager
static BOOL RCTViewControllerBasedStatusBarAppearance()
{
static BOOL value;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
value = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"] ?: @YES boolValue];
});
return value;
}
- (void)setStyle:(UIStatusBarStyle)statusBarStyle animated:(BOOL)animated
{
RCT_EXPORT();
dispatch_async(dispatch_get_main_queue(), ^{
if (RCTViewControllerBasedStatusBarAppearance()) {
RCTLogError(@"RCTStatusBarManager module requires that the \
UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO");
} else {
[[UIApplication sharedApplication] setStatusBarStyle:statusBarStyle
animated:animated];
}
});
}
- (void)setHidden:(BOOL)hidden withAnimation:(UIStatusBarAnimation)animation
{
RCT_EXPORT();
dispatch_async(dispatch_get_main_queue(), ^{
if (RCTViewControllerBasedStatusBarAppearance()) {
RCTLogError(@"RCTStatusBarManager module requires that the \
UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO");
} else {
[[UIApplication sharedApplication] setStatusBarHidden:hidden
withAnimation:animation];
}
});
}
- (NSDictionary *)constantsToExport
{
return @{
@"Style": @{
@"default": @(UIStatusBarStyleDefault),
@"lightContent": @(UIStatusBarStyleLightContent),
},
@"Animation": @{
@"none": @(UIStatusBarAnimationNone),
@"fade": @(UIStatusBarAnimationFade),
@"slide": @(UIStatusBarAnimationSlide),
},
};
}
@end

17
React/Modules/RCTTiming.h Normal file
View File

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

221
React/Modules/RCTTiming.m Normal file
View File

@@ -0,0 +1,221 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTTiming.h"
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTLog.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h"
@interface RCTTimer : NSObject
@property (nonatomic, strong, readonly) NSDate *target;
@property (nonatomic, assign, readonly) BOOL repeats;
@property (nonatomic, copy, readonly) NSNumber *callbackID;
@property (nonatomic, assign, readonly) NSTimeInterval interval;
@end
@implementation RCTTimer
- (instancetype)initWithCallbackID:(NSNumber *)callbackID
interval:(NSTimeInterval)interval
targetTime:(NSTimeInterval)targetTime
repeats:(BOOL)repeats
{
if ((self = [super init])) {
_interval = interval;
_repeats = repeats;
_callbackID = callbackID;
_target = [NSDate dateWithTimeIntervalSinceNow:targetTime];
}
return self;
}
/**
* Returns `YES` if we should invoke the JS callback.
*/
- (BOOL)updateFoundNeedsJSUpdate
{
if (_target && _target.timeIntervalSinceNow <= 0) {
// The JS Timers will do fine grained calculating of expired timeouts.
_target = _repeats ? [NSDate dateWithTimeIntervalSinceNow:_interval] : nil;
return YES;
}
return NO;
}
@end
@implementation RCTTiming
{
RCTSparseArray *_timers;
id _updateTimer;
}
@synthesize bridge = _bridge;
+ (NSArray *)JSMethods
{
return @[@"RCTJSTimers.callTimers"];
}
- (instancetype)init
{
if ((self = [super init])) {
_timers = [[RCTSparseArray alloc] init];
for (NSString *name in @[UIApplicationWillResignActiveNotification,
UIApplicationDidEnterBackgroundNotification,
UIApplicationWillTerminateNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(stopTimers)
name:name
object:nil];
}
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
UIApplicationWillEnterForegroundNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(startTimers)
name:name
object:nil];
}
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (BOOL)isValid
{
return _bridge != nil;
}
- (void)invalidate
{
[self stopTimers];
_bridge = nil;
}
- (void)stopTimers
{
[_updateTimer invalidate];
_updateTimer = nil;
}
- (void)startTimers
{
RCTAssertMainThread();
if (![self isValid] || _updateTimer != nil || _timers.count == 0) {
return;
}
_updateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
if (_updateTimer) {
[_updateTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
} else {
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60)
target:self
selector:@selector(update)
userInfo:nil
repeats:YES];
}
}
- (void)update
{
RCTAssertMainThread();
NSMutableArray *timersToCall = [[NSMutableArray alloc] init];
for (RCTTimer *timer in _timers.allObjects) {
if ([timer updateFoundNeedsJSUpdate]) {
[timersToCall addObject:timer.callbackID];
}
if (!timer.target) {
_timers[timer.callbackID] = nil;
}
}
// call timers that need to be called
if ([timersToCall count] > 0) {
[_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[timersToCall]];
}
}
/**
* There's a small difference between the time when we call
* setTimeout/setInterval/requestAnimation frame and the time it actually makes
* it here. This is important and needs to be taken into account when
* calculating the timer's target time. We calculate this by passing in
* Date.now() from JS and then subtracting that from the current time here.
*/
- (void)createTimer:(NSNumber *)callbackID
duration:(double)jsDuration
jsSchedulingTime:(double)jsSchedulingTime
repeats:(BOOL)repeats
{
RCT_EXPORT();
if (jsDuration == 0 && repeats == NO) {
// For super fast, one-off timers, just enqueue them immediately rather than waiting a frame.
[_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[@[callbackID]]];
return;
}
NSTimeInterval interval = jsDuration / 1000;
NSTimeInterval jsCreationTimeSinceUnixEpoch = jsSchedulingTime / 1000;
NSTimeInterval currentTimeSinceUnixEpoch = [[NSDate date] timeIntervalSince1970];
NSTimeInterval jsSchedulingOverhead = currentTimeSinceUnixEpoch - jsCreationTimeSinceUnixEpoch;
if (jsSchedulingOverhead < 0) {
RCTLogWarn(@"jsSchedulingOverhead (%ims) should be positive", (int)(jsSchedulingOverhead * 1000));
}
NSTimeInterval targetTime = interval - jsSchedulingOverhead;
if (interval < 0.018) { // Make sure short intervals run each frame
interval = 0;
}
RCTTimer *timer = [[RCTTimer alloc] initWithCallbackID:callbackID
interval:interval
targetTime:targetTime
repeats:repeats];
dispatch_async(dispatch_get_main_queue(), ^{
_timers[callbackID] = timer;
[self startTimers];
});
}
- (void)deleteTimer:(NSNumber *)timerID
{
RCT_EXPORT();
if (timerID) {
dispatch_async(dispatch_get_main_queue(), ^{
_timers[timerID] = nil;
if (_timers.count == 0) {
[self stopTimers];
}
});
} else {
RCTLogWarn(@"Called deleteTimer: with a nil timerID");
}
}
@end

View File

@@ -0,0 +1,66 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTBridgeModule.h"
#import "RCTInvalidating.h"
#import "RCTViewManager.h"
@protocol RCTScrollableProtocol;
/**
* The RCTUIManager is the module responsible for updating the view hierarchy.
*/
@interface RCTUIManager : NSObject <RCTBridgeModule, RCTInvalidating>
@property (nonatomic, weak) id<RCTScrollableProtocol> mainScrollView;
/**
* Allows native environment code to respond to "the main scroll view" events.
* see `RCTUIManager`'s `setMainScrollViewTag`.
*/
@property (nonatomic, readwrite, weak) id<UIScrollViewDelegate> nativeMainScrollDelegate;
/**
* Register a root view with the RCTUIManager. Theoretically, a single manager
* can support multiple root views, however this feature is not currently exposed.
*/
- (void)registerRootView:(UIView *)rootView;
/**
* Update the frame of a root view. This might be in response to a screen rotation
* or some other layout event outsde of the React-managed view hierarchy.
*/
- (void)setFrame:(CGRect)frame forRootView:(UIView *)rootView;
/**
* Schedule a block to be executed on the UI thread. Useful if you need to execute
* view logic after all currently queued view updates have completed.
*/
- (void)addUIBlock:(RCTViewManagerUIBlock)block;
/**
* The view that is currently first responder, according to the JS context.
*/
+ (UIView *)JSResponder;
@end
/**
* This category makes the current RCTUIManager instance available via the
* RCTBridge, which is useful for RCTBridgeModules or RCTViewManagers that
* need to access the RCTUIManager.
*/
@interface RCTBridge (RCTUIManager)
@property (nonatomic, readonly) RCTUIManager *uiManager;
@end

1397
React/Modules/RCTUIManager.m Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,647 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */; };
00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */; };
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; };
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */; };
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */ = {isa = PBXBuildFile; fileRef = 134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */; };
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */; };
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7091AB030C200659ED6 /* RCTAppState.m */; };
137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E01AA5CF210034F82E /* RCTTabBar.m */; };
137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E21AA5CF210034F82E /* RCTTabBarItem.m */; };
137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */; };
137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137327E61AA5CF210034F82E /* RCTTabBarManager.m */; };
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */ = {isa = PBXBuildFile; fileRef = 13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */; };
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FE81A69327A00A75B9A /* RCTAlertManager.m */; };
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */; };
13B07FF21A69327A00A75B9A /* RCTTiming.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FEE1A69327A00A75B9A /* RCTTiming.m */; };
13B080051A6947C200A75B9A /* RCTScrollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FF71A6947C200A75B9A /* RCTScrollView.m */; };
13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FF91A6947C200A75B9A /* RCTScrollViewManager.m */; };
13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B0800D1A69489C00A75B9A /* RCTNavigator.m */; };
13B0801B1A69489C00A75B9A /* RCTNavigatorManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B0800F1A69489C00A75B9A /* RCTNavigatorManager.m */; };
13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080111A69489C00A75B9A /* RCTNavItem.m */; };
13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080131A69489C00A75B9A /* RCTNavItemManager.m */; };
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080151A69489C00A75B9A /* RCTTextField.m */; };
13B0801F1A69489C00A75B9A /* RCTTextFieldManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080171A69489C00A75B9A /* RCTTextFieldManager.m */; };
13B080201A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080191A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m */; };
13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; };
13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; };
13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156041AB1A2840079392D /* RCTWebViewManager.m */; };
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067491A70F434002CDEE1 /* RCTUIManager.m */; };
13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */; };
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */; };
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067501A70F44B002CDEE1 /* RCTView.m */; };
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E067541A70F44B002CDEE1 /* UIView+React.m */; };
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE21AAC4AE100FC20F4 /* RCTMap.m */; };
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */; };
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F362081AABD06A001CE568 /* RCTSwitch.m */; };
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */; };
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */; };
58114A161AAE854800E7D092 /* RCTPicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A131AAE854800E7D092 /* RCTPicker.m */; };
58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A151AAE854800E7D092 /* RCTPickerManager.m */; };
58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */; };
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */; };
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = 830A229D1A66C68A008503DA /* RCTRootView.m */; };
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 830BA4541A8E3BDA00D53203 /* RCTCache.m */; };
832348161A77A5AA00B55238 /* Layout.c in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FC71A68125100A75B9A /* Layout.c */; };
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */; };
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA4E1A601E3B00E9B192 /* RCTLog.m */; };
83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA501A601E3B00E9B192 /* RCTUtils.m */; };
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA591A601E9000E9B192 /* RCTRedBox.m */; };
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */; };
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; };
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; };
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
83CBBA2C1A601D0E00E9B192 /* Copy Files */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
name = "Copy Files";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
000E6CE91AB0E97F000CDF4D /* RCTSourceCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSourceCode.h; sourceTree = "<group>"; };
000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSourceCode.m; sourceTree = "<group>"; };
00C1A2B11AC0B7E000E89A1C /* RCTDevMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDevMenu.h; sourceTree = "<group>"; };
00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDevMenu.m; sourceTree = "<group>"; };
13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationType.h; sourceTree = "<group>"; };
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = "<group>"; };
13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewControllerProtocol.h; sourceTree = "<group>"; };
134FCB391A6E7F0800051CC8 /* RCTContextExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTContextExecutor.h; sourceTree = "<group>"; };
134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTContextExecutor.m; sourceTree = "<group>"; };
134FCB3B1A6E7F0800051CC8 /* RCTWebViewExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewExecutor.h; sourceTree = "<group>"; };
134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewExecutor.m; sourceTree = "<group>"; };
13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStatusBarManager.h; sourceTree = "<group>"; };
13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStatusBarManager.m; sourceTree = "<group>"; };
1372B7081AB030C200659ED6 /* RCTAppState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAppState.h; sourceTree = "<group>"; };
1372B7091AB030C200659ED6 /* RCTAppState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAppState.m; sourceTree = "<group>"; };
137327DF1AA5CF210034F82E /* RCTTabBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBar.h; sourceTree = "<group>"; };
137327E01AA5CF210034F82E /* RCTTabBar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBar.m; sourceTree = "<group>"; };
137327E11AA5CF210034F82E /* RCTTabBarItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarItem.h; sourceTree = "<group>"; };
137327E21AA5CF210034F82E /* RCTTabBarItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarItem.m; sourceTree = "<group>"; };
137327E31AA5CF210034F82E /* RCTTabBarItemManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarItemManager.h; sourceTree = "<group>"; };
137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarItemManager.m; sourceTree = "<group>"; };
137327E51AA5CF210034F82E /* RCTTabBarManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTabBarManager.h; sourceTree = "<group>"; };
137327E61AA5CF210034F82E /* RCTTabBarManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTabBarManager.m; sourceTree = "<group>"; };
13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTKeyCommands.h; sourceTree = "<group>"; };
13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTKeyCommands.m; sourceTree = "<group>"; };
13B07FC71A68125100A75B9A /* Layout.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = Layout.c; sourceTree = "<group>"; };
13B07FC81A68125100A75B9A /* Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Layout.h; sourceTree = "<group>"; };
13B07FE71A69327A00A75B9A /* RCTAlertManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAlertManager.h; sourceTree = "<group>"; };
13B07FE81A69327A00A75B9A /* RCTAlertManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAlertManager.m; sourceTree = "<group>"; };
13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTExceptionsManager.h; sourceTree = "<group>"; };
13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTExceptionsManager.m; sourceTree = "<group>"; };
13B07FED1A69327A00A75B9A /* RCTTiming.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTiming.h; sourceTree = "<group>"; };
13B07FEE1A69327A00A75B9A /* RCTTiming.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTiming.m; sourceTree = "<group>"; };
13B07FF61A6947C200A75B9A /* RCTScrollView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTScrollView.h; sourceTree = "<group>"; };
13B07FF71A6947C200A75B9A /* RCTScrollView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTScrollView.m; sourceTree = "<group>"; };
13B07FF81A6947C200A75B9A /* RCTScrollViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTScrollViewManager.h; sourceTree = "<group>"; };
13B07FF91A6947C200A75B9A /* RCTScrollViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTScrollViewManager.m; sourceTree = "<group>"; };
13B0800C1A69489C00A75B9A /* RCTNavigator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNavigator.h; sourceTree = "<group>"; };
13B0800D1A69489C00A75B9A /* RCTNavigator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNavigator.m; sourceTree = "<group>"; };
13B0800E1A69489C00A75B9A /* RCTNavigatorManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNavigatorManager.h; sourceTree = "<group>"; };
13B0800F1A69489C00A75B9A /* RCTNavigatorManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNavigatorManager.m; sourceTree = "<group>"; };
13B080101A69489C00A75B9A /* RCTNavItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNavItem.h; sourceTree = "<group>"; };
13B080111A69489C00A75B9A /* RCTNavItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNavItem.m; sourceTree = "<group>"; };
13B080121A69489C00A75B9A /* RCTNavItemManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNavItemManager.h; sourceTree = "<group>"; };
13B080131A69489C00A75B9A /* RCTNavItemManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNavItemManager.m; sourceTree = "<group>"; };
13B080141A69489C00A75B9A /* RCTTextField.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextField.h; sourceTree = "<group>"; };
13B080151A69489C00A75B9A /* RCTTextField.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextField.m; sourceTree = "<group>"; };
13B080161A69489C00A75B9A /* RCTTextFieldManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextFieldManager.h; sourceTree = "<group>"; };
13B080171A69489C00A75B9A /* RCTTextFieldManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextFieldManager.m; sourceTree = "<group>"; };
13B080181A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUIActivityIndicatorViewManager.h; sourceTree = "<group>"; };
13B080191A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIActivityIndicatorViewManager.m; sourceTree = "<group>"; };
13B080231A694A8400A75B9A /* RCTWrapperViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWrapperViewController.h; sourceTree = "<group>"; };
13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = "<group>"; };
13C156011AB1A2840079392D /* RCTWebView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebView.h; sourceTree = "<group>"; };
13C156021AB1A2840079392D /* RCTWebView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebView.m; sourceTree = "<group>"; };
13C156031AB1A2840079392D /* RCTWebViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWebViewManager.h; sourceTree = "<group>"; };
13C156041AB1A2840079392D /* RCTWebViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWebViewManager.m; sourceTree = "<group>"; };
13C325261AA63B6A0048765F /* RCTAutoInsetsProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAutoInsetsProtocol.h; sourceTree = "<group>"; };
13C325271AA63B6A0048765F /* RCTScrollableProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTScrollableProtocol.h; sourceTree = "<group>"; };
13C325281AA63B6A0048765F /* RCTViewNodeProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewNodeProtocol.h; sourceTree = "<group>"; };
13E067481A70F434002CDEE1 /* RCTUIManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUIManager.h; sourceTree = "<group>"; };
13E067491A70F434002CDEE1 /* RCTUIManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManager.m; sourceTree = "<group>"; };
13E0674B1A70F44B002CDEE1 /* RCTShadowView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTShadowView.h; sourceTree = "<group>"; };
13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowView.m; sourceTree = "<group>"; };
13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewManager.h; sourceTree = "<group>"; };
13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTViewManager.m; sourceTree = "<group>"; };
13E0674F1A70F44B002CDEE1 /* RCTView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTView.h; sourceTree = "<group>"; };
13E067501A70F44B002CDEE1 /* RCTView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTView.m; sourceTree = "<group>"; };
13E067531A70F44B002CDEE1 /* UIView+React.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+React.h"; sourceTree = "<group>"; };
13E067541A70F44B002CDEE1 /* UIView+React.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIView+React.m"; sourceTree = "<group>"; };
13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTJSMethodRegistrar.h; sourceTree = "<group>"; };
14435CE11AAC4AE100FC20F4 /* RCTMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMap.h; sourceTree = "<group>"; };
14435CE21AAC4AE100FC20F4 /* RCTMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMap.m; sourceTree = "<group>"; };
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapManager.h; sourceTree = "<group>"; };
14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapManager.m; sourceTree = "<group>"; };
14F362071AABD06A001CE568 /* RCTSwitch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitch.h; sourceTree = "<group>"; };
14F362081AABD06A001CE568 /* RCTSwitch.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitch.m; sourceTree = "<group>"; };
14F362091AABD06A001CE568 /* RCTSwitchManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSwitchManager.h; sourceTree = "<group>"; };
14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSwitchManager.m; sourceTree = "<group>"; };
14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSliderManager.h; sourceTree = "<group>"; };
14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSliderManager.m; sourceTree = "<group>"; };
58114A121AAE854800E7D092 /* RCTPicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPicker.h; sourceTree = "<group>"; };
58114A131AAE854800E7D092 /* RCTPicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPicker.m; sourceTree = "<group>"; };
58114A141AAE854800E7D092 /* RCTPickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPickerManager.h; sourceTree = "<group>"; };
58114A151AAE854800E7D092 /* RCTPickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPickerManager.m; sourceTree = "<group>"; };
58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAsyncLocalStorage.m; sourceTree = "<group>"; };
58114A4F1AAE93D500E7D092 /* RCTAsyncLocalStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAsyncLocalStorage.h; sourceTree = "<group>"; };
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePickerManager.m; sourceTree = "<group>"; };
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePickerManager.h; sourceTree = "<group>"; };
830213F31A654E0800B993E6 /* RCTBridgeModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTBridgeModule.h; sourceTree = "<group>"; };
830A229C1A66C68A008503DA /* RCTRootView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootView.h; sourceTree = "<group>"; };
830A229D1A66C68A008503DA /* RCTRootView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootView.m; sourceTree = "<group>"; };
830BA4531A8E3BDA00D53203 /* RCTCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCache.h; sourceTree = "<group>"; };
830BA4541A8E3BDA00D53203 /* RCTCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCache.m; sourceTree = "<group>"; };
83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSparseArray.h; sourceTree = "<group>"; };
83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSparseArray.m; sourceTree = "<group>"; };
83CBBA2E1A601D0E00E9B192 /* libReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libReact.a; sourceTree = BUILT_PRODUCTS_DIR; };
83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssert.h; sourceTree = "<group>"; };
83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssert.m; sourceTree = "<group>"; };
83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTInvalidating.h; sourceTree = "<group>"; };
83CBBA4D1A601E3B00E9B192 /* RCTLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLog.h; sourceTree = "<group>"; };
83CBBA4E1A601E3B00E9B192 /* RCTLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLog.m; sourceTree = "<group>"; };
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUtils.h; sourceTree = "<group>"; };
83CBBA501A601E3B00E9B192 /* RCTUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUtils.m; sourceTree = "<group>"; };
83CBBA581A601E9000E9B192 /* RCTRedBox.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRedBox.h; sourceTree = "<group>"; };
83CBBA591A601E9000E9B192 /* RCTRedBox.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRedBox.m; sourceTree = "<group>"; };
83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTBridge.h; sourceTree = "<group>"; };
83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTBridge.m; sourceTree = "<group>"; };
83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTJavaScriptExecutor.h; sourceTree = "<group>"; };
83CBBA651A601EF300E9B192 /* RCTEventDispatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTEventDispatcher.h; sourceTree = "<group>"; };
83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTEventDispatcher.m; sourceTree = "<group>"; };
83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTouchHandler.h; sourceTree = "<group>"; };
83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTouchHandler.m; sourceTree = "<group>"; };
83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = "<group>"; };
83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
83CBBA2B1A601D0E00E9B192 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134FCB381A6E7F0800051CC8 /* Executors */ = {
isa = PBXGroup;
children = (
134FCB391A6E7F0800051CC8 /* RCTContextExecutor.h */,
134FCB3A1A6E7F0800051CC8 /* RCTContextExecutor.m */,
134FCB3B1A6E7F0800051CC8 /* RCTWebViewExecutor.h */,
134FCB3C1A6E7F0800051CC8 /* RCTWebViewExecutor.m */,
);
path = Executors;
sourceTree = "<group>";
};
13B07FC41A68125100A75B9A /* Layout */ = {
isa = PBXGroup;
children = (
13B07FC71A68125100A75B9A /* Layout.c */,
13B07FC81A68125100A75B9A /* Layout.h */,
);
path = Layout;
sourceTree = "<group>";
};
13B07FE01A69315300A75B9A /* Modules */ = {
isa = PBXGroup;
children = (
1372B7081AB030C200659ED6 /* RCTAppState.h */,
1372B7091AB030C200659ED6 /* RCTAppState.m */,
13B07FE71A69327A00A75B9A /* RCTAlertManager.h */,
13B07FE81A69327A00A75B9A /* RCTAlertManager.m */,
58114A4F1AAE93D500E7D092 /* RCTAsyncLocalStorage.h */,
58114A4E1AAE93D500E7D092 /* RCTAsyncLocalStorage.m */,
13B07FE91A69327A00A75B9A /* RCTExceptionsManager.h */,
13B07FEA1A69327A00A75B9A /* RCTExceptionsManager.m */,
000E6CE91AB0E97F000CDF4D /* RCTSourceCode.h */,
000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */,
13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */,
13723B4F1A82FD3C00F88898 /* RCTStatusBarManager.m */,
13B07FED1A69327A00A75B9A /* RCTTiming.h */,
13B07FEE1A69327A00A75B9A /* RCTTiming.m */,
13E067481A70F434002CDEE1 /* RCTUIManager.h */,
13E067491A70F434002CDEE1 /* RCTUIManager.m */,
);
path = Modules;
sourceTree = "<group>";
};
13B07FF31A6947C200A75B9A /* Views */ = {
isa = PBXGroup;
children = (
13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */,
13C325261AA63B6A0048765F /* RCTAutoInsetsProtocol.h */,
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */,
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */,
14435CE11AAC4AE100FC20F4 /* RCTMap.h */,
14435CE21AAC4AE100FC20F4 /* RCTMap.m */,
14435CE31AAC4AE100FC20F4 /* RCTMapManager.h */,
14435CE41AAC4AE100FC20F4 /* RCTMapManager.m */,
13B0800C1A69489C00A75B9A /* RCTNavigator.h */,
13B0800D1A69489C00A75B9A /* RCTNavigator.m */,
13B0800E1A69489C00A75B9A /* RCTNavigatorManager.h */,
13B0800F1A69489C00A75B9A /* RCTNavigatorManager.m */,
13B080101A69489C00A75B9A /* RCTNavItem.h */,
13B080111A69489C00A75B9A /* RCTNavItem.m */,
13B080121A69489C00A75B9A /* RCTNavItemManager.h */,
13B080131A69489C00A75B9A /* RCTNavItemManager.m */,
58114A121AAE854800E7D092 /* RCTPicker.h */,
58114A131AAE854800E7D092 /* RCTPicker.m */,
58114A141AAE854800E7D092 /* RCTPickerManager.h */,
58114A151AAE854800E7D092 /* RCTPickerManager.m */,
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */,
13B07FF61A6947C200A75B9A /* RCTScrollView.h */,
13B07FF71A6947C200A75B9A /* RCTScrollView.m */,
13B07FF81A6947C200A75B9A /* RCTScrollViewManager.h */,
13B07FF91A6947C200A75B9A /* RCTScrollViewManager.m */,
13C325271AA63B6A0048765F /* RCTScrollableProtocol.h */,
13E0674B1A70F44B002CDEE1 /* RCTShadowView.h */,
13E0674C1A70F44B002CDEE1 /* RCTShadowView.m */,
14F484541AABFCE100FDF6B9 /* RCTSliderManager.h */,
14F484551AABFCE100FDF6B9 /* RCTSliderManager.m */,
14F362071AABD06A001CE568 /* RCTSwitch.h */,
14F362081AABD06A001CE568 /* RCTSwitch.m */,
14F362091AABD06A001CE568 /* RCTSwitchManager.h */,
14F3620A1AABD06A001CE568 /* RCTSwitchManager.m */,
137327DF1AA5CF210034F82E /* RCTTabBar.h */,
137327E01AA5CF210034F82E /* RCTTabBar.m */,
137327E11AA5CF210034F82E /* RCTTabBarItem.h */,
137327E21AA5CF210034F82E /* RCTTabBarItem.m */,
137327E31AA5CF210034F82E /* RCTTabBarItemManager.h */,
137327E41AA5CF210034F82E /* RCTTabBarItemManager.m */,
137327E51AA5CF210034F82E /* RCTTabBarManager.h */,
137327E61AA5CF210034F82E /* RCTTabBarManager.m */,
13B080141A69489C00A75B9A /* RCTTextField.h */,
13B080151A69489C00A75B9A /* RCTTextField.m */,
13B080161A69489C00A75B9A /* RCTTextFieldManager.h */,
13B080171A69489C00A75B9A /* RCTTextFieldManager.m */,
13B080181A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.h */,
13B080191A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m */,
13E0674F1A70F44B002CDEE1 /* RCTView.h */,
13E067501A70F44B002CDEE1 /* RCTView.m */,
13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */,
13E0674D1A70F44B002CDEE1 /* RCTViewManager.h */,
13E0674E1A70F44B002CDEE1 /* RCTViewManager.m */,
13C325281AA63B6A0048765F /* RCTViewNodeProtocol.h */,
13C156011AB1A2840079392D /* RCTWebView.h */,
13C156021AB1A2840079392D /* RCTWebView.m */,
13C156031AB1A2840079392D /* RCTWebViewManager.h */,
13C156041AB1A2840079392D /* RCTWebViewManager.m */,
13B080231A694A8400A75B9A /* RCTWrapperViewController.h */,
13B080241A694A8400A75B9A /* RCTWrapperViewController.m */,
13E067531A70F44B002CDEE1 /* UIView+React.h */,
13E067541A70F44B002CDEE1 /* UIView+React.m */,
);
path = Views;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
83CBBA2F1A601D0F00E9B192 /* React */,
83CBBA001A601CBA00E9B192 /* Products */,
);
sourceTree = "<group>";
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
83CBBA2E1A601D0E00E9B192 /* libReact.a */,
);
name = Products;
sourceTree = "<group>";
};
83CBBA2F1A601D0F00E9B192 /* React */ = {
isa = PBXGroup;
children = (
83CBBA491A601E3B00E9B192 /* Base */,
134FCB381A6E7F0800051CC8 /* Executors */,
13B07FC41A68125100A75B9A /* Layout */,
13B07FE01A69315300A75B9A /* Modules */,
13B07FF31A6947C200A75B9A /* Views */,
);
name = React;
sourceTree = "<group>";
};
83CBBA491A601E3B00E9B192 /* Base */ = {
isa = PBXGroup;
children = (
83CBBA4A1A601E3B00E9B192 /* RCTAssert.h */,
83CBBA4B1A601E3B00E9B192 /* RCTAssert.m */,
83CBBA5E1A601EAA00E9B192 /* RCTBridge.h */,
83CBBA5F1A601EAA00E9B192 /* RCTBridge.m */,
830213F31A654E0800B993E6 /* RCTBridgeModule.h */,
830BA4531A8E3BDA00D53203 /* RCTCache.h */,
830BA4541A8E3BDA00D53203 /* RCTCache.m */,
83CBBACA1A6023D300E9B192 /* RCTConvert.h */,
83CBBACB1A6023D300E9B192 /* RCTConvert.m */,
83CBBA651A601EF300E9B192 /* RCTEventDispatcher.h */,
83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */,
83CBBA4C1A601E3B00E9B192 /* RCTInvalidating.h */,
83CBBA631A601ECA00E9B192 /* RCTJavaScriptExecutor.h */,
13EFFCCF1A98E6FE002607DC /* RCTJSMethodRegistrar.h */,
13A1F71C1A75392D00D3D453 /* RCTKeyCommands.h */,
13A1F71D1A75392D00D3D453 /* RCTKeyCommands.m */,
83CBBA4D1A601E3B00E9B192 /* RCTLog.h */,
83CBBA4E1A601E3B00E9B192 /* RCTLog.m */,
83CBBA581A601E9000E9B192 /* RCTRedBox.h */,
83CBBA591A601E9000E9B192 /* RCTRedBox.m */,
830A229C1A66C68A008503DA /* RCTRootView.h */,
830A229D1A66C68A008503DA /* RCTRootView.m */,
00C1A2B11AC0B7E000E89A1C /* RCTDevMenu.h */,
00C1A2B21AC0B7E000E89A1C /* RCTDevMenu.m */,
83BEE46C1A6D19BC00B5863B /* RCTSparseArray.h */,
83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */,
83CBBA961A6020BB00E9B192 /* RCTTouchHandler.h */,
83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */,
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
);
path = Base;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
83CBBA2D1A601D0E00E9B192 /* React */ = {
isa = PBXNativeTarget;
buildConfigurationList = 83CBBA3F1A601D0F00E9B192 /* Build configuration list for PBXNativeTarget "React" */;
buildPhases = (
006B79A01A781F38006873D1 /* ShellScript */,
83CBBA2A1A601D0E00E9B192 /* Sources */,
83CBBA2B1A601D0E00E9B192 /* Frameworks */,
83CBBA2C1A601D0E00E9B192 /* Copy Files */,
);
buildRules = (
);
dependencies = (
);
name = React;
productName = React;
productReference = 83CBBA2E1A601D0E00E9B192 /* libReact.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0610;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
83CBBA2D1A601D0E00E9B192 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "React" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
83CBBA2D1A601D0E00E9B192 /* React */,
);
};
/* End PBXProject section */
/* Begin PBXShellScriptBuildPhase section */
006B79A01A781F38006873D1 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "nc -w 5 -z localhost 8081 > /dev/null 2>&1 || open $SRCROOT/../packager/launchPackager.command || echo \"Can't start packager automatically\"";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
83CBBA2A1A601D0E00E9B192 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */,
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */,
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */,
83CBBA511A601E3B00E9B192 /* RCTAssert.m in Sources */,
58114A501AAE93D500E7D092 /* RCTAsyncLocalStorage.m in Sources */,
832348161A77A5AA00B55238 /* Layout.c in Sources */,
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */,
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */,
13B080201A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m in Sources */,
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */,
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */,
13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */,
137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */,
13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */,
13B080051A6947C200A75B9A /* RCTScrollView.m in Sources */,
13B07FF21A69327A00A75B9A /* RCTTiming.m in Sources */,
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */,
13B0801F1A69489C00A75B9A /* RCTTextFieldManager.m in Sources */,
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */,
13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */,
14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */,
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */,
83CBBA521A601E3B00E9B192 /* RCTLog.m in Sources */,
13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */,
13E067571A70F44B002CDEE1 /* RCTView.m in Sources */,
137327E91AA5CF210034F82E /* RCTTabBarItemManager.m in Sources */,
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */,
13A1F71E1A75392D00D3D453 /* RCTKeyCommands.m in Sources */,
83CBBA531A601E3B00E9B192 /* RCTUtils.m in Sources */,
14435CE61AAC4AE100FC20F4 /* RCTMapManager.m in Sources */,
13C156051AB1A2840079392D /* RCTWebView.m in Sources */,
83CBBA601A601EAA00E9B192 /* RCTBridge.m in Sources */,
13C156061AB1A2840079392D /* RCTWebViewManager.m in Sources */,
58114A161AAE854800E7D092 /* RCTPicker.m in Sources */,
137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */,
13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */,
58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */,
13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */,
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */,
137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */,
00C1A2B31AC0B7E000E89A1C /* RCTDevMenu.m in Sources */,
14435CE51AAC4AE100FC20F4 /* RCTMap.m in Sources */,
134FCB3E1A6E7F0800051CC8 /* RCTWebViewExecutor.m in Sources */,
13B0801C1A69489C00A75B9A /* RCTNavItem.m in Sources */,
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */,
13E0674A1A70F434002CDEE1 /* RCTUIManager.m in Sources */,
13B0801B1A69489C00A75B9A /* RCTNavigatorManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
83CBBA401A601D0F00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Debug;
};
83CBBA411A601D0F00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_WARN_ABOUT_MISSING_NEWLINE = YES;
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "React" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBBA3F1A601D0F00E9B192 /* Build configuration list for PBXNativeTarget "React" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA401A601D0F00E9B192 /* Debug */,
83CBBA411A601D0F00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, RCTAnimationType) {
RCTAnimationTypeSpring = 0,
RCTAnimationTypeLinear,
RCTAnimationTypeEaseIn,
RCTAnimationTypeEaseOut,
RCTAnimationTypeEaseInEaseOut,
};

View File

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

View File

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

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDatePickerManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "UIView+React.h"
@implementation RCTConvert(UIDatePicker)
RCT_ENUM_CONVERTER(UIDatePickerMode, (@{
@"time": @(UIDatePickerModeTime),
@"date": @(UIDatePickerModeDate),
@"datetime": @(UIDatePickerModeDateAndTime),
//@"countdown": @(UIDatePickerModeCountDownTimer) // not supported yet
}), UIDatePickerModeTime, integerValue)
@end
@implementation RCTDatePickerManager
- (UIView *)view
{
UIDatePicker *picker = [[UIDatePicker alloc] init];
[picker addTarget:self
action:@selector(onChange:)
forControlEvents:UIControlEventValueChanged];
return picker;
}
RCT_EXPORT_VIEW_PROPERTY(date, NSDate)
RCT_EXPORT_VIEW_PROPERTY(minimumDate, NSDate)
RCT_EXPORT_VIEW_PROPERTY(maximumDate, NSDate)
RCT_EXPORT_VIEW_PROPERTY(minuteInterval, NSInteger)
RCT_REMAP_VIEW_PROPERTY(mode, datePickerMode, UIDatePickerMode)
RCT_REMAP_VIEW_PROPERTY(timeZoneOffsetInMinutes, timeZone, NSTimeZone)
- (void)onChange:(UIDatePicker *)sender
{
NSDictionary *event = @{
@"target": sender.reactTag,
@"timestamp": @([sender.date timeIntervalSince1970] * 1000.0)
};
[self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event];
}
- (NSDictionary *)constantsToExport
{
UIDatePicker *dp = [[UIDatePicker alloc] init];
[dp layoutIfNeeded];
return @{
@"ComponentHeight": @(CGRectGetHeight(dp.frame)),
@"ComponentWidth": @(CGRectGetWidth(dp.frame)),
@"DatePickerModes": @{
@"time": @(UIDatePickerModeTime),
@"date": @(UIDatePickerModeDate),
@"datetime": @(UIDatePickerModeDateAndTime),
}
};
}
@end

27
React/Views/RCTMap.h Normal file
View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <MapKit/MapKit.h>
#import <UIKit/UIKit.h>
extern const CLLocationDegrees RCTMapDefaultSpan;
extern const NSTimeInterval RCTMapRegionChangeObserveInterval;
extern const CGFloat RCTMapZoomBoundBuffer;
@class RCTEventDispatcher;
@interface RCTMap: MKMapView
@property (nonatomic, assign) BOOL followUserLocation;
@property (nonatomic, assign) CGFloat minDelta;
@property (nonatomic, assign) CGFloat maxDelta;
@property (nonatomic, assign) UIEdgeInsets legalLabelInsets;
@property (nonatomic, strong) NSTimer *regionChangeObserveTimer;
@end

112
React/Views/RCTMap.m Normal file
View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTMap.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTUtils.h"
const CLLocationDegrees RCTMapDefaultSpan = 0.005;
const NSTimeInterval RCTMapRegionChangeObserveInterval = 0.1;
const CGFloat RCTMapZoomBoundBuffer = 0.01;
@implementation RCTMap
{
UIView *_legalLabel;
CLLocationManager *_locationManager;
}
- (instancetype)init
{
if ((self = [super init])) {
// Find Apple link label
for (UIView *subview in self.subviews) {
if ([NSStringFromClass(subview.class) isEqualToString:@"MKAttributionLabel"]) {
// This check is super hacky, but the whole premise of moving around Apple's internal subviews is super hacky
_legalLabel = subview;
break;
}
}
}
return self;
}
- (void)dealloc
{
[_regionChangeObserveTimer invalidate];
}
- (void)layoutSubviews
{
[super layoutSubviews];
// Force resize subviews - only the layer is resized by default
CGRect mapFrame = self.frame;
self.frame = CGRectZero;
self.frame = mapFrame;
if (_legalLabel) {
dispatch_async(dispatch_get_main_queue(), ^{
CGRect frame = _legalLabel.frame;
if (_legalLabelInsets.left) {
frame.origin.x = _legalLabelInsets.left;
} else if (_legalLabelInsets.right) {
frame.origin.x = mapFrame.size.width - _legalLabelInsets.right - frame.size.width;
}
if (_legalLabelInsets.top) {
frame.origin.y = _legalLabelInsets.top;
} else if (_legalLabelInsets.bottom) {
frame.origin.y = mapFrame.size.height - _legalLabelInsets.bottom - frame.size.height;
}
_legalLabel.frame = frame;
});
}
}
#pragma mark Accessors
- (void)setShowsUserLocation:(BOOL)showsUserLocation
{
if (self.showsUserLocation != showsUserLocation) {
if (showsUserLocation && !_locationManager) {
_locationManager = [[CLLocationManager alloc] init];
if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
[_locationManager requestWhenInUseAuthorization];
}
}
[super setShowsUserLocation:showsUserLocation];
// If it needs to show user location, force map view centered
// on user's current location on user location updates
self.followUserLocation = showsUserLocation;
}
}
- (void)setRegion:(MKCoordinateRegion)region
{
// If location is invalid, abort
if (!CLLocationCoordinate2DIsValid(region.center)) {
return;
}
// If new span values are nil, use old values instead
if (!region.span.latitudeDelta) {
region.span.latitudeDelta = self.region.span.latitudeDelta;
}
if (!region.span.longitudeDelta) {
region.span.longitudeDelta = self.region.span.longitudeDelta;
}
// Animate to new position
[super setRegion:region animated:YES];
}
@end

View File

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

169
React/Views/RCTMapManager.m Normal file
View File

@@ -0,0 +1,169 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTMapManager.h"
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import "RCTMap.h"
#import "UIView+React.h"
@implementation RCTConvert(CoreLocation)
+ (CLLocationCoordinate2D)CLLocationCoordinate2D:(id)json
{
json = [self NSDictionary:json];
return (CLLocationCoordinate2D){
[self double:json[@"latitude"]],
[self double:json[@"longitude"]]
};
}
@end
@implementation RCTConvert(MapKit)
+ (MKCoordinateSpan)MKCoordinateSpan:(id)json
{
json = [self NSDictionary:json];
return (MKCoordinateSpan){
[self double:json[@"latitudeDelta"]],
[self double:json[@"longitudeDelta"]]
};
}
+ (MKCoordinateRegion)MKCoordinateRegion:(id)json
{
return (MKCoordinateRegion){
[self CLLocationCoordinate2D:json],
[self MKCoordinateSpan:json]
};
}
@end
@interface RCTMapManager() <MKMapViewDelegate>
@end
@implementation RCTMapManager
- (UIView *)view
{
RCTMap *map = [[RCTMap alloc] init];
map.delegate = self;
return map;
}
RCT_EXPORT_VIEW_PROPERTY(showsUserLocation, BOOL)
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(rotateEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(pitchEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(maxDelta, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(region, MKCoordinateRegion)
#pragma mark MKMapViewDelegate
- (void)mapView:(RCTMap *)mapView didUpdateUserLocation:(MKUserLocation *)location
{
if (mapView.followUserLocation) {
MKCoordinateRegion region;
region.span.latitudeDelta = RCTMapDefaultSpan;
region.span.longitudeDelta = RCTMapDefaultSpan;
region.center = location.coordinate;
[mapView setRegion:region animated:YES];
// Move to user location only for the first time it loads up.
mapView.followUserLocation = NO;
}
}
- (void)mapView:(RCTMap *)mapView regionWillChangeAnimated:(BOOL)animated
{
[self _regionChanged:mapView];
mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval
target:self
selector:@selector(_onTick:)
userInfo:@{ @"mapView": mapView }
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes];
}
- (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(BOOL)animated
{
[mapView.regionChangeObserveTimer invalidate];
mapView.regionChangeObserveTimer = nil;
[self _regionChanged:mapView];
[self _emitRegionChangeEvent:mapView continuous:NO];
}
#pragma mark Private
- (void)_onTick:(NSTimer *)timer
{
[self _regionChanged:timer.userInfo[@"mapView"]];
}
- (void)_regionChanged:(RCTMap *)mapView
{
BOOL needZoom = NO;
CGFloat newLongitudeDelta = 0.0f;
MKCoordinateRegion region = mapView.region;
// On iOS 7, it's possible that we observe invalid locations during initialization of the map.
// Filter those out.
if (!CLLocationCoordinate2DIsValid(region.center)) {
return;
}
// Calculation on float is not 100% accurate. If user zoom to max/min and then move, it's likely the map will auto zoom to max/min from time to time.
// So let's try to make map zoom back to 99% max or 101% min so that there are some buffer that moving the map won't constantly hitting the max/min bound.
if (mapView.maxDelta > FLT_EPSILON && region.span.longitudeDelta > mapView.maxDelta) {
needZoom = YES;
newLongitudeDelta = mapView.maxDelta * (1 - RCTMapZoomBoundBuffer);
} else if (mapView.minDelta > FLT_EPSILON && region.span.longitudeDelta < mapView.minDelta) {
needZoom = YES;
newLongitudeDelta = mapView.minDelta * (1 + RCTMapZoomBoundBuffer);
}
if (needZoom) {
region.span.latitudeDelta = region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta;
region.span.longitudeDelta = newLongitudeDelta;
mapView.region = region;
}
// Continously observe region changes
[self _emitRegionChangeEvent:mapView continuous:YES];
}
- (void)_emitRegionChangeEvent:(RCTMap *)mapView continuous:(BOOL)continuous
{
MKCoordinateRegion region = mapView.region;
if (!CLLocationCoordinate2DIsValid(region.center)) {
return;
}
#define FLUSH_NAN(value) (isnan(value) ? 0 : value)
NSDictionary *event = @{
@"target": [mapView reactTag],
@"continuous": @(continuous),
@"region": @{
@"latitude": @(FLUSH_NAN(region.center.latitude)),
@"longitude": @(FLUSH_NAN(region.center.longitude)),
@"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)),
@"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)),
}
};
[self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event];
}
@end

21
React/Views/RCTNavItem.h Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@interface RCTNavItem : UIView
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *rightButtonTitle;
@property (nonatomic, copy) NSString *backButtonTitle;
@property (nonatomic, copy) UIColor *tintColor;
@property (nonatomic, copy) UIColor *barTintColor;
@property (nonatomic, copy) UIColor *titleTextColor;
@end

15
React/Views/RCTNavItem.m Normal file
View File

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

View File

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

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTNavItemManager.h"
#import "RCTConvert.h"
#import "RCTNavItem.h"
@implementation RCTNavItemManager
- (UIView *)view
{
return [[RCTNavItem alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(title, NSString)
RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString);
RCT_EXPORT_VIEW_PROPERTY(backButtonTitle, NSString);
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(titleTextColor, UIColor);
@end

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "RCTInvalidating.h"
@class RCTEventDispatcher;
@interface RCTNavigator : UIView <RCTInvalidating>
@property (nonatomic, strong) UIView *reactNavSuperviewLink;
@property (nonatomic, assign) NSInteger requestedTopOfStack;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
/**
* Schedules a JavaScript navigation and prevents `UIKit` from navigating until
* JavaScript has sent its scheduled navigation.
*
* @returns Whether or not a JavaScript driven navigation could be
* scheduled/reserved. If returning `NO`, JavaScript should usually just do
* nothing at all.
*/
- (BOOL)requestSchedulingJavaScriptNavigation;
@end

565
React/Views/RCTNavigator.m Normal file
View File

@@ -0,0 +1,565 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTNavigator.h"
#import "RCTAssert.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTNavItem.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "RCTWrapperViewController.h"
#import "UIView+React.h"
typedef NS_ENUM(NSUInteger, RCTNavigationLock) {
RCTNavigationLockNone,
RCTNavigationLockNative,
RCTNavigationLockJavaScript
};
NSInteger kNeverRequested = -1;
NSInteger kNeverProgressed = -10000;
@interface UINavigationController ()
// need to declare this since `UINavigationController` doesnt publicly declare the fact that it implements
// UINavigationBarDelegate :(
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item;
@end
// http://stackoverflow.com/questions/5115135/uinavigationcontroller-how-to-cancel-the-back-button-event
// There's no other way to do this unfortunately :(
@interface RCTNavigationController : UINavigationController <UINavigationBarDelegate>
{
dispatch_block_t _scrollCallback;
}
@property (nonatomic, assign) RCTNavigationLock navigationLock;
@end
/**
* In general, `RCTNavigator` examines `_currentViews` (which are React child
* views), and compares them to `_navigationController.viewControllers` (which
* are controlled by UIKit).
*
* It is possible for JavaScript (`_currentViews`) to "get ahead" of native
* (`navigationController.viewControllers`) and vice versa. JavaScript gets
* ahead by adding/removing React subviews. Native gets ahead by swiping back,
* or tapping the back button. In both cases, the other system is initially
* unaware. And in both cases, `RCTNavigator` helps the other side "catch up".
*
* If `RCTNavigator` sees the number of react children have changed, it
* pushes/pops accordingly. If `RCTNavigator` sees a `UIKit` driven push/pop, it
* notifies JavaScript that this has happened, and expects that JavaScript will
* eventually render more children to match `UIKit`. There's no rush for
* JavaScript to catch up. But if it does rener anything, it must catch up to
* UIKit. It cannot deviate.
*
* To implement this, we need a lock, which we store on the native thread. This
* lock allows one of the systems to push/pop views. Whoever wishes to
* "get ahead" must obtain the lock. Whoever wishes to "catch up" must obtain
* the lock. One thread may not "get ahead" or "catch up" when the other has
* the lock. Once a thread has the lock, it can only do the following:
*
* 1. If it is behind, it may only catch up.
* 2. If it is caught up or ahead, it may push or pop.
*
*
* ========= Acquiring The Lock ==========
*
* JavaScript asynchronously acquires the lock using a native hook. It might be
* rejected and receive the return value `false`.
*
* We acquire the native lock in `shouldPopItem`, which is called right before
* native tries to push/pop, but only if JavaScript doesn't already have the
* lock.
*
* ======== While JavaScript Has Lock ====
*
* When JavaScript has the lock, we have to block all `UIKit` driven pops:
*
* 1. Block back button navigation:
* - Back button will invoke `shouldPopItem`, from which we return `NO` if
* JavaScript has the lock.
* - Back button will respect the return value `NO` and not permit
* navigation.
*
* 2. Block swipe-to-go-back navigation:
* - Swipe will trigger `shouldPopItem`, but swipe won't respect our `NO`
* return value so we must disable the gesture recognizer while JavaScript
* has the lock.
*
* ======== While Native Has Lock =======
*
* We simply deny JavaScript the right to acquire the lock.
*
*
* ======== Releasing The Lock ===========
*
* Recall that the lock represents who has the right to either push/pop (or
* catch up). As soon as we recognize that the side that has locked has carried
* out what it scheduled to do, we can release the lock, but only after any
* possible animations are completed.
*
* *IF* a scheduled operation results in a push/pop (not all do), then we can
* only release the lock after the push/pop animation is complete because
* UIKit. `didMoveToNavigationController` is invoked when the view is done
* pushing/popping/animating. Native swipe-to-go-back interactions can be
* aborted, however, and you'll never see that method invoked. So just to cover
* that case, we also put an animation complete hook in
* `animateAlongsideTransition` to make sure we free the lock, in case the
* scheduled native push/pop never actually happened.
*
* For JavaScript:
* - When we see that JavaScript has "caught up" to `UIKit`, and no pushes/pops
* were needed, we can release the lock.
* - When we see that JavaScript requires *some* push/pop, it's not yet done
* carrying out what it scheduled to do. Just like with `UIKit` push/pops, we
* still have to wait for it to be done animating
* (`didMoveToNavigationController` is a suitable hook).
*
*/
@implementation RCTNavigationController
/**
* @param callback Callback that is invoked when a "scroll" interaction begins
* so that `RCTNavigator` can notify `JavaScript`.
*/
- (instancetype)initWithScrollCallback:(dispatch_block_t)callback
{
if ((self = [super initWithNibName:nil bundle:nil])) {
_scrollCallback = callback;
}
return self;
}
/**
* Invoked when either a navigation item has been popped off, or when a
* swipe-back gesture has began. The swipe-back gesture doesn't respect the
* return value of this method. The back button does. That's why we have to
* completely disable the gesture recognizer for swipe-back while JS has the
* lock.
*/
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
if (self.interactivePopGestureRecognizer.state == UIGestureRecognizerStateBegan) {
if (self.navigationLock == RCTNavigationLockNone) {
self.navigationLock = RCTNavigationLockNative;
if (_scrollCallback) {
_scrollCallback();
}
} else if (self.navigationLock == RCTNavigationLockJavaScript) {
// This should never happen because we disable/enable the gesture
// recognizer when we lock the navigation.
RCTAssert(NO, @"Should never receive gesture start while JS locks navigator");
}
} else {
if (self.navigationLock == RCTNavigationLockNone) {
// Must be coming from native interaction, lock it - it will be unlocked
// in `didMoveToNavigationController`
self.navigationLock = RCTNavigationLockNative;
if (_scrollCallback) {
_scrollCallback();
}
} else if (self.navigationLock == RCTNavigationLockJavaScript) {
// This should only occur when JS has the lock, and
// - JS is driving the pop
// - Or the back button was pressed
// TODO: We actually want to disable the backbutton while JS has the
// lock, but it's not so easy. Even returning `NO` wont' work because it
// will also block JS driven pops. We simply need to disallow a standard
// back button, and instead use a custom one that tells JS to pop to
// length (`currentReactCount` - 1).
return [super navigationBar:navigationBar shouldPopItem:item];
}
}
return [super navigationBar:navigationBar shouldPopItem:item];
}
@end
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate>
{
RCTEventDispatcher *_eventDispatcher;
NSInteger _numberOfViewControllerMovesToIgnore;
}
@property (nonatomic, assign) NSInteger previousRequestedTopOfStack;
// Previous views are only mainted in order to detect incorrect
// addition/removal of views below the `requestedTopOfStack`
@property (nonatomic, copy, readwrite) NSArray *previousViews;
@property (nonatomic, readwrite, strong) NSMutableArray *currentViews;
@property (nonatomic, readwrite, strong) RCTNavigationController *navigationController;
/**
* Display link is used to get high frequency sample rate during
* interaction/animation of view controller push/pop.
*
* - The run loop retains the displayLink.
* - `displayLink` retains its target.
* - We use `invalidate` to remove the `RCTNavigator`'s reference to the
* `displayLink` and remove the `displayLink` from the run loop.
*
*
* `displayLink`:
* --------------
*
* - Even though we could implement the `displayLink` cleanup without the
* `invalidate` hook by adding and removing it from the run loop at the
* right times (begin/end animation), we need to account for the possibility
* that the view itself is destroyed mid-interaction. So we always keep it
* added to the run loop, but start/stop it with interactions/animations. We
* remove it from the run loop when the view will be destroyed by React.
*
* +----------+ +--------------+
* | run loop o----strong--->| displayLink |
* +----------+ +--o-----------+
* | ^
* | |
* strong strong
* | |
* v |
* +---------o---+
* | RCTNavigator |
* +-------------+
*
* `dummyView`:
* ------------
* There's no easy way to get a callback that fires when the position of a
* navigation item changes. The actual layers that are moved around during the
* navigation transition are private. Our only hope is to use
* `animateAlongsideTransition`, to set a dummy view's position to transition
* anywhere from -1.0 to 1.0. We later set up a `CADisplayLink` to poll the
* `presentationLayer` of that dummy view and report the value as a "progress"
* percentage.
*
* It was critical that we added the dummy view as a subview of the
* transitionCoordinator's `containerView`, otherwise the animations would not
* work correctly when reversing the gesture direction etc. This seems to be
* undocumented behavior/requirement.
*
*/
@property (nonatomic, readonly, assign) CGFloat mostRecentProgress;
@property (nonatomic, readwrite, strong) CADisplayLink *displayLink;
@property (nonatomic, readonly, strong) NSTimer *runTimer;
@property (nonatomic, readonly, assign) NSInteger currentlyTransitioningFrom;
@property (nonatomic, readonly, assign) NSInteger currentlyTransitioningTo;
// Dummy view that we make animate with the same curve/interaction as the
// navigation animation/interaction.
@property (nonatomic, readonly, strong) UIView *dummyView;
@end
@implementation RCTNavigator
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(reportNavigationProgress:)];
_mostRecentProgress = kNeverProgressed;
_dummyView = [[UIView alloc] initWithFrame:CGRectZero];
if (_displayLink) {
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
_displayLink.paused = YES;
} else {
// It's okay to leak this on a build bot.
RCTLogWarn(@"Failed to create a display link (probably on automated build system) - using an NSTimer for AppEngine instead.");
_runTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0) target:self selector:@selector(reportNavigationProgress:) userInfo:nil repeats:YES];
}
_eventDispatcher = eventDispatcher;
_previousRequestedTopOfStack = kNeverRequested; // So that we initialize with a push.
_previousViews = @[];
_currentViews = [[NSMutableArray alloc] initWithCapacity:0];
__weak RCTNavigator *weakSelf = self;
_navigationController = [[RCTNavigationController alloc] initWithScrollCallback:^{
[weakSelf dispatchFakeScrollEvent];
}];
_navigationController.delegate = self;
RCTAssert([self requestSchedulingJavaScriptNavigation], @"Could not acquire JS navigation lock on init");
[self addSubview:_navigationController.view];
[_navigationController.view addSubview:_dummyView];
}
return self;
}
- (void)reportNavigationProgress:(CADisplayLink *)sender
{
if (_currentlyTransitioningFrom != _currentlyTransitioningTo) {
UIView *topView = _dummyView;
id presentationLayer = [topView.layer presentationLayer];
CGRect frame = [presentationLayer frame];
CGFloat nextProgress = ABS(frame.origin.x);
// Don't want to spam the bridge, when the user holds their finger still mid-navigation.
if (nextProgress == _mostRecentProgress) {
return;
}
_mostRecentProgress = nextProgress;
[_eventDispatcher sendInputEventWithName:@"topNavigationProgress" body:@{
@"fromIndex": @(_currentlyTransitioningFrom),
@"toIndex": @(_currentlyTransitioningTo),
@"progress": @(nextProgress),
@"target": self.reactTag
}];
}
}
- (void)dealloc
{
_navigationController.delegate = nil;
}
- (UIViewController *)backingViewController
{
return _navigationController;
}
/**
* See documentation about lock lifecycle. This is only here to clean up
* swipe-back abort interaction, which leaves us *no* other way to clean up
* locks aside from the animation complete hook.
*/
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
id<UIViewControllerTransitionCoordinator> tc =
navigationController.topViewController.transitionCoordinator;
__weak RCTNavigator *weakSelf = self;
[tc.containerView addSubview: _dummyView];
[tc animateAlongsideTransition: ^(id<UIViewControllerTransitionCoordinatorContext> context) {
RCTWrapperViewController *fromController =
(RCTWrapperViewController *)[context viewControllerForKey:UITransitionContextFromViewControllerKey];
RCTWrapperViewController *toController =
(RCTWrapperViewController *)[context viewControllerForKey:UITransitionContextToViewControllerKey];
NSUInteger indexOfFrom = [_currentViews indexOfObject:fromController.navItem];
NSUInteger indexOfTo = [_currentViews indexOfObject:toController.navItem];
CGFloat destination = indexOfFrom < indexOfTo ? 1.0 : -1.0;
_dummyView.frame = (CGRect){{destination}};
_currentlyTransitioningFrom = indexOfFrom;
_currentlyTransitioningTo = indexOfTo;
if (indexOfFrom != indexOfTo) {
_displayLink.paused = NO;
}
}
completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[weakSelf freeLock];
_currentlyTransitioningFrom = 0;
_currentlyTransitioningTo = 0;
_dummyView.frame = CGRectZero;
_displayLink.paused = YES;
// Reset the parallel position tracker
}];
}
- (BOOL)requestSchedulingJavaScriptNavigation
{
if (_navigationController.navigationLock == RCTNavigationLockNone) {
_navigationController.navigationLock = RCTNavigationLockJavaScript;
_navigationController.interactivePopGestureRecognizer.enabled = NO;
return YES;
}
return NO;
}
- (void)freeLock
{
_navigationController.navigationLock = RCTNavigationLockNone;
_navigationController.interactivePopGestureRecognizer.enabled = YES;
}
/**
* A React subview can be inserted/removed at any time, however if the
* `requestedTopOfStack` changes, there had better be enough subviews present
* to satisfy the push/pop.
*/
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
RCTAssert([view isKindOfClass:[RCTNavItem class]], @"RCTNavigator only accepts RCTNavItem subviews");
RCTAssert(
_navigationController.navigationLock == RCTNavigationLockJavaScript,
@"Cannot change subviews from JS without first locking."
);
[_currentViews insertObject:view atIndex:atIndex];
}
- (NSArray *)reactSubviews
{
return _currentViews;
}
- (BOOL)isValid
{
return _displayLink != nil;
}
- (void)invalidate
{
// Prevent displayLink from retaining the navigator indefinitely
[_displayLink invalidate];
_displayLink = nil;
_runTimer = nil;
}
- (void)layoutSubviews
{
[super layoutSubviews];
_navigationController.view.frame = self.bounds;
}
- (void)removeReactSubview:(UIView *)subview
{
if (_currentViews.count <= 0 || subview == _currentViews[0]) {
RCTLogError(@"Attempting to remove invalid RCT subview of RCTNavigator");
return;
}
[_currentViews removeObject:subview];
}
- (void)handleTopOfStackChanged
{
[_eventDispatcher sendInputEventWithName:@"topNavigateBack" body:@{
@"target":self.reactTag,
@"stackLength":@(_navigationController.viewControllers.count)
}];
}
- (void)dispatchFakeScrollEvent
{
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove
reactTag:self.reactTag
scrollView:nil
userData:nil];
}
/**
* Must be overridden because UIKit removes the view's superview when used
* as a navigator - it's considered outside the view hierarchy.
*/
- (UIView *)reactSuperview
{
RCTAssert(self.superview != nil, @"put reactNavSuperviewLink back");
return self.superview ? self.superview : self.reactNavSuperviewLink;
}
- (void)reactBridgeDidFinishTransaction
{
// we can't hook up the VC hierarchy in 'init' because the subviews aren't
// hooked up yet, so we do it on demand here
[self addControllerToClosestParent:_navigationController];
NSInteger viewControllerCount = _navigationController.viewControllers.count;
// The "react count" is the count of views that are visible on the navigation
// stack. There may be more beyond this - that aren't visible, and may be
// deleted/purged soon.
NSInteger previousReactCount =
_previousRequestedTopOfStack == kNeverRequested ? 0 : _previousRequestedTopOfStack + 1;
NSInteger currentReactCount = _requestedTopOfStack + 1;
BOOL jsGettingAhead =
// ----- previously caught up ------ ------ no longer caught up -------
viewControllerCount == previousReactCount && currentReactCount != viewControllerCount;
BOOL jsCatchingUp =
// --- previously not caught up ---- --------- now caught up ----------
viewControllerCount != previousReactCount && currentReactCount == viewControllerCount;
BOOL jsMakingNoProgressButNeedsToCatchUp =
// --- previously not caught up ---- ------- still the same -----------
viewControllerCount != previousReactCount && currentReactCount == previousReactCount;
BOOL jsMakingNoProgressAndDoesntNeedTo =
// --- previously caught up -------- ------- still caught up ----------
viewControllerCount == previousReactCount && currentReactCount == previousReactCount;
BOOL reactPushOne = jsGettingAhead && currentReactCount == previousReactCount + 1;
BOOL reactPopN = jsGettingAhead && currentReactCount < previousReactCount;
// We can actually recover from this situation, but it would be nice to know
// when this error happens. This simply means that JS hasn't caught up to a
// back navigation before progressing. It's likely a bug in the JS code that
// catches up/schedules navigations.
if (!(jsGettingAhead ||
jsCatchingUp ||
jsMakingNoProgressButNeedsToCatchUp ||
jsMakingNoProgressAndDoesntNeedTo)) {
RCTLogError(@"JS has only made partial progress to catch up to UIKit");
}
RCTAssert(
currentReactCount <= _currentViews.count,
@"Cannot adjust current top of stack beyond available views"
);
// Views before the previous react count must not have changed. Views greater than previousReactCount
// up to currentReactCount may have changed.
for (NSInteger i = 0; i < MIN(_currentViews.count, MIN(_previousViews.count, previousReactCount)); i++) {
RCTAssert(_currentViews[i] == _previousViews[i], @"current view should equal previous view");
}
RCTAssert(currentReactCount >= 1, @"should be at least one current view");
if (jsGettingAhead) {
if (reactPushOne) {
UIView *lastView = [_currentViews lastObject];
RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView eventDispatcher:_eventDispatcher];
vc.navigationListener = self;
_numberOfViewControllerMovesToIgnore = 1;
[_navigationController pushViewController:vc animated:(currentReactCount > 1)];
} else if (reactPopN) {
UIViewController *viewControllerToPopTo = [[_navigationController viewControllers] objectAtIndex:(currentReactCount - 1)];
_numberOfViewControllerMovesToIgnore = viewControllerCount - currentReactCount;
[_navigationController popToViewController:viewControllerToPopTo animated:YES];
} else {
RCTAssert(NO, @"Pushing or popping more than one view at a time from JS");
}
} else if (jsCatchingUp) {
[self freeLock]; // Nothing to push/pop
} else {
// Else, JS making no progress, could have been unrelated to anything nav.
return;
}
_previousViews = [_currentViews copy];
_previousRequestedTopOfStack = _requestedTopOfStack;
}
// TODO: This will likely fail when performing multiple pushes/pops. We must
// free the lock only after the *last* push/pop.
- (void)wrapperViewController:(RCTWrapperViewController *)wrapperViewController
didMoveToNavigationController:(UINavigationController *)navigationController
{
if (self.superview == nil) {
// If superview is nil, then a JS reload (Cmd+R) happened
// while a push/pop is in progress.
return;
}
RCTAssert(
(navigationController == nil || [_navigationController.viewControllers containsObject:wrapperViewController]),
@"if navigation controller is not nil, it should contain the wrapper view controller"
);
RCTAssert(_navigationController.navigationLock == RCTNavigationLockJavaScript ||
_numberOfViewControllerMovesToIgnore == 0,
@"If JS doesn't have the lock there should never be any pending transitions");
/**
* When JS has the lock we want to keep track of when the request completes
* the pending transition count hitting 0 signifies this, and should always
* remain at 0 when JS does not have the lock
*/
if (_numberOfViewControllerMovesToIgnore > 0) {
_numberOfViewControllerMovesToIgnore -= 1;
}
if (_numberOfViewControllerMovesToIgnore == 0) {
[self handleTopOfStackChanged];
[self freeLock];
}
}
@end

View File

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

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTNavigatorManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTNavigator.h"
#import "RCTSparseArray.h"
#import "RCTUIManager.h"
@implementation RCTNavigatorManager
- (UIView *)view
{
return [[RCTNavigator alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack, NSInteger)
- (NSDictionary *)customDirectEventTypes
{
return @{
@"topNavigationProgress": @{
@"registrationName": @"onNavigationProgress"
},
};
}
// TODO: remove error callbacks
- (void)requestSchedulingJavaScriptNavigation:(NSNumber *)reactTag
errorCallback:(RCTResponseSenderBlock)errorCallback
callback:(__unused RCTResponseSenderBlock)callback
{
RCT_EXPORT();
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
RCTNavigator *navigator = viewRegistry[reactTag];
if ([navigator isKindOfClass:[RCTNavigator class]]) {
BOOL wasAcquired = [navigator requestSchedulingJavaScriptNavigation];
callback(@[@(wasAcquired)]);
} else {
RCTLogError(@"Cannot set lock: %@ (tag #%@) is not an RCTNavigator", navigator, reactTag);
}
}];
}
@end

21
React/Views/RCTPicker.h Normal file
View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@interface RCTPicker : UIPickerView
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@property (nonatomic, copy) NSArray *items;
@property (nonatomic, assign) NSInteger selectedIndex;
@end

99
React/Views/RCTPicker.m Normal file
View File

@@ -0,0 +1,99 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTPicker.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTUtils.h"
#import "UIView+React.h"
const NSInteger UNINITIALIZED_INDEX = -1;
@interface RCTPicker() <UIPickerViewDataSource, UIPickerViewDelegate>
@end
@implementation RCTPicker
{
RCTEventDispatcher *_eventDispatcher;
NSArray *_items;
NSInteger _selectedIndex;
}
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if (self = [super initWithFrame:CGRectZero]) {
_eventDispatcher = eventDispatcher;
_selectedIndex = UNINITIALIZED_INDEX;
self.delegate = self;
}
return self;
}
- (void)setItems:(NSArray *)items
{
if (_items != items) {
_items = [items copy];
[self setNeedsLayout];
}
}
- (void)setSelectedIndex:(NSInteger)selectedIndex
{
if (_selectedIndex != selectedIndex) {
BOOL animated = _selectedIndex != UNINITIALIZED_INDEX; // Don't animate the initial value
_selectedIndex = selectedIndex;
dispatch_async(dispatch_get_main_queue(), ^{
[self selectRow:selectedIndex inComponent:0 animated:animated];
});
}
}
#pragma mark - UIPickerViewDataSource protocol
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return _items.count;
}
#pragma mark - UIPickerViewDelegate methods
- (NSDictionary *)itemForRow:(NSInteger)row
{
return _items[row];
}
- (id)valueForRow:(NSInteger)row
{
return [self itemForRow:row][@"value"];
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return [self itemForRow:row][@"label"];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
_selectedIndex = row;
NSDictionary *event = @{
@"target": self.reactTag,
@"newIndex": @(row),
@"newValue": [self valueForRow:row]
};
[_eventDispatcher sendInputEventWithName:@"topChange" body:event];
}
@end

View File

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

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTPickerManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTPicker.h"
@implementation RCTPickerManager
- (UIView *)view
{
return [[RCTPicker alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray)
RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
- (NSDictionary *)constantsToExport
{
RCTPicker *pv = [[RCTPicker alloc] init];
return @{
@"ComponentHeight": @(CGRectGetHeight(pv.frame)),
@"ComponentWidth": @(CGRectGetWidth(pv.frame))
};
}
@end

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, RCTPointerEvents) {
RCTPointerEventsUnspecified = 0, // Default
RCTPointerEventsNone,
RCTPointerEventsBoxNone,
RCTPointerEventsBoxOnly,
};

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIScrollView.h>
#import "RCTAutoInsetsProtocol.h"
#import "RCTScrollableProtocol.h"
#import "RCTView.h"
@protocol UIScrollViewDelegate;
@class RCTEventDispatcher;
@interface RCTScrollView : RCTView <UIScrollViewDelegate, RCTScrollableProtocol, RCTAutoInsetsProtocol>
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
/**
* The `RCTScrollView` may have at most one single subview. This will ensure
* that the scroll view's `contentSize` will be efficiently set to the size of
* the single subview's frame. That frame size will be determined somewhat
* efficiently since it will have already been computed by the off-main-thread
* layout system.
*/
@property (nonatomic, readonly) UIView *contentView;
/**
* If the `contentSize` is not specified (or is specified as {0, 0}, then the
* `contentSize` will automatically be determined by the size of the subview.
*/
@property (nonatomic, assign) CGSize contentSize;
/**
* The underlying scrollView (TODO: can we remove this?)
*/
@property (nonatomic, readonly) UIScrollView *scrollView;
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, assign) NSUInteger throttleScrollCallbackMS;
@property (nonatomic, assign) BOOL centerContent;
@property (nonatomic, copy) NSArray *stickyHeaderIndices;
@end

633
React/Views/RCTScrollView.m Normal file
View File

@@ -0,0 +1,633 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTScrollView.h"
#import <UIKit/UIKit.h>
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTUIManager.h"
#import "RCTUtils.h"
#import "UIView+React.h"
CGFloat const ZINDEX_DEFAULT = 0;
CGFloat const ZINDEX_STICKY_HEADER = 50;
/**
* Include a custom scroll view subclass because we want to limit certain
* default UIKit behaviors such as textFields automatically scrolling
* scroll views that contain them and support sticky headers.
*/
@interface RCTCustomScrollView : UIScrollView<UIGestureRecognizerDelegate>
@property (nonatomic, copy, readwrite) NSArray *stickyHeaderIndices;
@property (nonatomic, readwrite, assign) BOOL centerContent;
@end
@implementation RCTCustomScrollView
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
[self.panGestureRecognizer addTarget:self action:@selector(handleCustomPan:)];
}
return self;
}
- (UIView *)contentView
{
return ((RCTScrollView *)self.superview).contentView;
}
/**
* @return Whether or not the scroll view interaction should be blocked because
* JS was found to be the responder.
*/
- (BOOL)_shouldDisableScrollInteraction
{
// Since this may be called on every pan, we need to make sure to only climb
// the hierarchy on rare occasions.
UIView *JSResponder = [RCTUIManager JSResponder];
if (JSResponder && JSResponder != self.superview) {
BOOL superviewHasResponder = [self isDescendantOfView:JSResponder];
return superviewHasResponder;
}
return NO;
}
- (void)handleCustomPan:(UIPanGestureRecognizer *)sender
{
if ([self _shouldDisableScrollInteraction]) {
self.panGestureRecognizer.enabled = NO;
self.panGestureRecognizer.enabled = YES;
// TODO: If mid bounce, animate the scroll view to a non-bounced position
// while disabling (but only if `stopScrollInteractionIfJSHasResponder` was
// called *during* a `pan`. Currently, it will just snap into place which
// is not so bad either.
// Another approach:
// self.scrollEnabled = NO;
// self.scrollEnabled = YES;
}
}
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
// noop
}
/**
* Returning `YES` cancels touches for the "inner" `view` and causes a scroll.
* Returning `NO` causes touches to be directed to that inner view and prevents
* the scroll view from scrolling.
*
* `YES` -> Allows scrolling.
* `NO` -> Doesn't allow scrolling.
*
* By default this returns NO for all views that are UIControls and YES for
* everything else. What that does is allows scroll views to scroll even when a
* touch started inside of a `UIControl` (`UIButton` etc). For React scroll
* views, we want the default to be the same behavior as `UIControl`s so we
* return `YES` by default. But there's one case where we want to block the
* scrolling no matter what: When JS believes it has its own responder lock on
* a view that is *above* the scroll view in the hierarchy. So we abuse this
* `touchesShouldCancelInContentView` API in order to stop the scroll view from
* scrolling in this case.
*
* We are not aware of *any* other solution to the problem because alternative
* approaches require that we disable the scrollview *before* touches begin or
* move. This approach (`touchesShouldCancelInContentView`) works even if the
* JS responder is set after touches start/move because
* `touchesShouldCancelInContentView` is called as soon as the scroll view has
* been touched and dragged *just* far enough to decide to begin the "drag"
* movement of the scroll interaction. Returning `NO`, will cause the drag
* operation to fail.
*
* `touchesShouldCancelInContentView` will stop the *initialization* of a
* scroll pan gesture and most of the time this is sufficient. On rare
* occasion, the scroll gesture would have already initialized right before JS
* notifies native of the JS responder being set. In order to recover from that
* timing issue we have a fallback that kills any ongoing pan gesture that
* occurs when native is notified of a JS responder.
*
* Note: Explicitly returning `YES`, instead of relying on the default fixes
* (at least) one bug where if you have a UIControl inside a UIScrollView and
* tap on the UIControl and then start dragging (to scroll), it won't scroll.
* Chat with andras for more details.
*
* In order to have this called, you must have delaysContentTouches set to NO
* (which is the not the `UIKit` default).
*/
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
//TODO: shouldn't this call super if _shouldDisableScrollInteraction returns NO?
return ![self _shouldDisableScrollInteraction];
}
/*
* Automatically centers the content such that if the content is smaller than the
* ScrollView, we force it to be centered, but when you zoom or the content otherwise
* becomes larger than the ScrollView, there is no padding around the content but it
* can still fill the whole view.
*/
- (void)setContentOffset:(CGPoint)contentOffset
{
UIView *contentView = [self contentView];
if (contentView && _centerContent) {
CGSize subviewSize = contentView.frame.size;
CGSize scrollViewSize = self.bounds.size;
if (subviewSize.width < scrollViewSize.width) {
contentOffset.x = -(scrollViewSize.width - subviewSize.width) / 2.0;
}
if (subviewSize.height < scrollViewSize.height) {
contentOffset.y = -(scrollViewSize.height - subviewSize.height) / 2.0;
}
}
[super setContentOffset:contentOffset];
}
- (void)setBounds:(CGRect)bounds
{
[super setBounds:bounds];
[self dockClosestSectionHeader];
}
- (void)dockClosestSectionHeader
{
UIView *contentView = [self contentView];
if (_stickyHeaderIndices.count == 0 || !contentView) {
return;
}
// find the section header that needs to be docked
NSInteger firstIndexInView = [[_stickyHeaderIndices firstObject] integerValue] + 1;
CGRect scrollBounds = self.bounds;
scrollBounds.origin.x += self.contentInset.left;
scrollBounds.origin.y += self.contentInset.top;
NSInteger i = 0;
for (UIView *subview in contentView.subviews) {
CGRect rowFrame = [RCTCustomScrollView _calculateUntransformedFrame:subview];
if (CGRectIntersectsRect(scrollBounds, rowFrame)) {
firstIndexInView = i;
break;
}
i++;
}
NSInteger stickyHeaderii = 0;
for (NSNumber *stickyHeaderI in _stickyHeaderIndices) {
if ([stickyHeaderI integerValue] > firstIndexInView) {
break;
}
stickyHeaderii++;
}
stickyHeaderii = MAX(0, stickyHeaderii - 1);
// Set up transforms for the various section headers
NSInteger currentlyDockedIndex = [_stickyHeaderIndices[stickyHeaderii] integerValue];
NSInteger previouslyDockedIndex = stickyHeaderii > 0 ? [_stickyHeaderIndices[stickyHeaderii-1] integerValue] : -1;
NSInteger nextDockedIndex = (stickyHeaderii < _stickyHeaderIndices.count - 1) ?
[_stickyHeaderIndices[stickyHeaderii + 1] integerValue] : -1;
UIView *currentHeader = contentView.subviews[currentlyDockedIndex];
UIView *previousHeader = previouslyDockedIndex >= 0 ? contentView.subviews[previouslyDockedIndex] : nil;
CGRect curFrame = [RCTCustomScrollView _calculateUntransformedFrame:currentHeader];
if (previousHeader) {
// the previous header is offset to sit right above the currentlyDockedHeader's initial position
// (so it scrolls away nicely once the currentHeader locks into position)
CGRect previousFrame = [RCTCustomScrollView _calculateUntransformedFrame:previousHeader];
CGFloat yOffset = curFrame.origin.y - previousFrame.origin.y - previousFrame.size.height;
previousHeader.transform = CGAffineTransformMakeTranslation(0, yOffset);
}
UIView *nextHeader = nextDockedIndex >= 0 ? contentView.subviews[nextDockedIndex] : nil;
CGRect nextFrame = [RCTCustomScrollView _calculateUntransformedFrame:nextHeader];
if (curFrame.origin.y < scrollBounds.origin.y) {
// scrolled off (or being scrolled off) the top of the screen
CGFloat yOffset = 0;
if (nextHeader && nextFrame.origin.y < scrollBounds.origin.y + curFrame.size.height) {
// next frame is bumping me off if scrolling down (or i'm bumping the next one off if scrolling up)
yOffset = nextFrame.origin.y - curFrame.origin.y - curFrame.size.height;
} else {
// standard sticky header position
yOffset = scrollBounds.origin.y - curFrame.origin.y;
}
currentHeader.transform = CGAffineTransformMakeTranslation(0, yOffset);
currentHeader.layer.zPosition = ZINDEX_STICKY_HEADER;
} else {
// i'm the current header but in the viewport, so just scroll in normal position
currentHeader.transform = CGAffineTransformIdentity;
currentHeader.layer.zPosition = ZINDEX_DEFAULT;
}
// in our setup, 'next header' will always just scroll with the page
if (nextHeader) {
nextHeader.transform = CGAffineTransformIdentity;
nextHeader.layer.zPosition = ZINDEX_DEFAULT;
}
}
+ (CGRect)_calculateUntransformedFrame:(UIView *)view
{
CGRect frame = CGRectNull;
if (view) {
frame.size = view.bounds.size;
frame.origin = CGPointMake(view.layer.position.x - view.bounds.size.width * view.layer.anchorPoint.x, view.layer.position.y - view.bounds.size.height * view.layer.anchorPoint.y);
}
return frame;
}
@end
@implementation RCTScrollView
{
RCTEventDispatcher *_eventDispatcher;
RCTCustomScrollView *_scrollView;
UIView *_contentView;
NSTimeInterval _lastScrollDispatchTime;
NSMutableArray *_cachedChildFrames;
BOOL _allowNextScrollNoMatterWhat;
}
@synthesize nativeMainScrollDelegate = _nativeMainScrollDelegate;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
_scrollView = [[RCTCustomScrollView alloc] initWithFrame:CGRectZero];
_scrollView.delegate = self;
_scrollView.delaysContentTouches = NO;
_automaticallyAdjustContentInsets = YES;
_contentInset = UIEdgeInsetsZero;
_contentSize = CGSizeZero;
_throttleScrollCallbackMS = 0;
_lastScrollDispatchTime = CACurrentMediaTime();
_cachedChildFrames = [[NSMutableArray alloc] init];
[self addSubview:_scrollView];
}
return self;
}
- (void)setRemoveClippedSubviews:(__unused BOOL)removeClippedSubviews
{
// Does nothing
}
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
RCTAssert(_contentView == nil, @"RCTScrollView may only contain a single subview");
_contentView = view;
[_scrollView addSubview:view];
}
- (void)removeReactSubview:(UIView *)subview
{
RCTAssert(_contentView == subview, @"Attempted to remove non-existent subview");
_contentView = nil;
[subview removeFromSuperview];
}
- (NSArray *)reactSubviews
{
return _contentView ? @[_contentView] : @[];
}
- (void)setCenterContent:(BOOL)centerContent
{
_scrollView.centerContent = centerContent;
}
- (void)setStickyHeaderIndices:(NSArray *)headerIndices
{
RCTAssert(_scrollView.contentSize.width <= self.frame.size.width,
@"sticky headers are not supported with horizontal scrolled views");
_scrollView.stickyHeaderIndices = headerIndices;
}
- (void)dealloc
{
_scrollView.delegate = nil;
}
- (void)layoutSubviews
{
[super layoutSubviews];
RCTAssert(self.subviews.count == 1, @"we should only have exactly one subview");
RCTAssert([self.subviews lastObject] == _scrollView, @"our only subview should be a scrollview");
_scrollView.frame = self.bounds;
[RCTView autoAdjustInsetsForView:self
withScrollView:_scrollView
updateOffset:YES];
[self updateClippedSubviews];
}
- (void)setContentInset:(UIEdgeInsets)contentInset
{
CGPoint contentOffset = _scrollView.contentOffset;
_contentInset = contentInset;
[RCTView autoAdjustInsetsForView:self
withScrollView:_scrollView
updateOffset:NO];
_scrollView.contentOffset = contentOffset;
}
- (void)scrollToOffset:(CGPoint)offset
{
[self scrollToOffset:offset animated:YES];
}
- (void)scrollToOffset:(CGPoint)offset animated:(BOOL)animated
{
if (!CGPointEqualToPoint(_scrollView.contentOffset, offset)) {
[_scrollView setContentOffset:offset animated:animated];
}
}
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
{
[_scrollView zoomToRect:rect animated:animated];
}
#pragma mark - ScrollView delegate
#define RCT_SCROLL_EVENT_HANDLER(delegateMethod, eventName) \
- (void)delegateMethod:(UIScrollView *)scrollView \
{ \
[_eventDispatcher sendScrollEventWithType:eventName reactTag:self.reactTag scrollView:scrollView userData:nil]; \
if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) { \
[_nativeMainScrollDelegate delegateMethod:scrollView]; \
} \
}
#define RCT_FORWARD_SCROLL_EVENT(call) \
if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) { \
[_nativeMainScrollDelegate call]; \
}
RCT_SCROLL_EVENT_HANDLER(scrollViewDidEndScrollingAnimation, RCTScrollEventTypeEndDeceleration)
RCT_SCROLL_EVENT_HANDLER(scrollViewWillBeginDecelerating, RCTScrollEventTypeStartDeceleration)
RCT_SCROLL_EVENT_HANDLER(scrollViewDidEndDecelerating, RCTScrollEventTypeEndDeceleration)
RCT_SCROLL_EVENT_HANDLER(scrollViewDidZoom, RCTScrollEventTypeMove)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
[self updateClippedSubviews];
NSTimeInterval now = CACurrentMediaTime();
NSTimeInterval throttleScrollCallbackSeconds = _throttleScrollCallbackMS / 1000.0;
/**
* TODO: this logic looks wrong, and it may be because it is. Currently, if _throttleScrollCallbackMS
* is set to zero (the default), the "didScroll" event is only sent once per scroll, instead of repeatedly
* while scrolling as expected. However, if you "fix" that bug, ScrollView will generate repeated
* warnings, and behave strangely (ListView works fine however), so don't fix it unless you fix that too!
*/
if (_allowNextScrollNoMatterWhat ||
(_throttleScrollCallbackMS != 0 && throttleScrollCallbackSeconds < (now - _lastScrollDispatchTime))) {
// Calculate changed frames
NSMutableArray *updatedChildFrames = [[NSMutableArray alloc] init];
[[_contentView reactSubviews] enumerateObjectsUsingBlock:^(UIView *subview, NSUInteger idx, BOOL *stop) {
// Check if new or changed
CGRect newFrame = subview.frame;
BOOL frameChanged = NO;
if (_cachedChildFrames.count <= idx) {
frameChanged = YES;
[_cachedChildFrames addObject:[NSValue valueWithCGRect:newFrame]];
} else if (!CGRectEqualToRect(newFrame, [_cachedChildFrames[idx] CGRectValue])) {
frameChanged = YES;
_cachedChildFrames[idx] = [NSValue valueWithCGRect:newFrame];
}
// Create JS frame object
if (frameChanged) {
[updatedChildFrames addObject: @{
@"index": @(idx),
@"x": @(newFrame.origin.x),
@"y": @(newFrame.origin.y),
@"width": @(newFrame.size.width),
@"height": @(newFrame.size.height),
}];
}
}];
// If there are new frames, add them to event data
NSDictionary *userData = nil;
if (updatedChildFrames.count > 0) {
userData = @{@"updatedChildFrames": updatedChildFrames};
}
// Dispatch event
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeMove
reactTag:self.reactTag
scrollView:scrollView
userData:userData];
// Update dispatch time
_lastScrollDispatchTime = now;
_allowNextScrollNoMatterWhat = NO;
}
RCT_FORWARD_SCROLL_EVENT(scrollViewDidScroll:scrollView);
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
_allowNextScrollNoMatterWhat = YES; // Ensure next scroll event is recorded, regardless of throttle
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeStart reactTag:self.reactTag scrollView:scrollView userData:nil];
RCT_FORWARD_SCROLL_EVENT(scrollViewWillBeginDragging:scrollView);
}
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeEnd reactTag:self.reactTag scrollView:scrollView userData:nil];
RCT_FORWARD_SCROLL_EVENT(scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset);
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndDragging:scrollView willDecelerate:decelerate);
}
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeStart reactTag:self.reactTag scrollView:scrollView userData:nil];
RCT_FORWARD_SCROLL_EVENT(scrollViewWillBeginZooming:scrollView withView:view);
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
{
[_eventDispatcher sendScrollEventWithType:RCTScrollEventTypeEnd reactTag:self.reactTag scrollView:scrollView userData:nil];
RCT_FORWARD_SCROLL_EVENT(scrollViewDidEndZooming:scrollView withView:view atScale:scale);
}
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
{
if ([_nativeMainScrollDelegate respondsToSelector:_cmd]) {
return [_nativeMainScrollDelegate scrollViewShouldScrollToTop:scrollView];
}
return YES;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return _contentView;
}
#pragma mark - Setters
- (CGSize)_calculateViewportSize
{
CGSize viewportSize = self.bounds.size;
if (_automaticallyAdjustContentInsets) {
UIEdgeInsets contentInsets = [RCTView contentInsetsForView:self];
viewportSize = CGSizeMake(self.bounds.size.width - contentInsets.left - contentInsets.right,
self.bounds.size.height - contentInsets.top - contentInsets.bottom);
}
return viewportSize;
}
- (CGPoint)calculateOffsetForContentSize:(CGSize)newContentSize
{
CGPoint oldOffset = _scrollView.contentOffset;
CGPoint newOffset = oldOffset;
CGSize oldContentSize = _scrollView.contentSize;
CGSize viewportSize = [self _calculateViewportSize];
BOOL fitsinViewportY = oldContentSize.height <= viewportSize.height && newContentSize.height <= viewportSize.height;
if (newContentSize.height < oldContentSize.height && !fitsinViewportY) {
CGFloat offsetHeight = oldOffset.y + viewportSize.height;
if (oldOffset.y < 0) {
// overscrolled on top, leave offset alone
} else if (offsetHeight > oldContentSize.height) {
// overscrolled on the bottom, preserve overscroll amount
newOffset.y = MAX(0, oldOffset.y - (oldContentSize.height - newContentSize.height));
} else if (offsetHeight > newContentSize.height) {
// offset falls outside of bounds, scroll back to end of list
newOffset.y = MAX(0, newContentSize.height - viewportSize.height);
}
}
BOOL fitsinViewportX = oldContentSize.width <= viewportSize.width && newContentSize.width <= viewportSize.width;
if (newContentSize.width < oldContentSize.width && !fitsinViewportX) {
CGFloat offsetHeight = oldOffset.x + viewportSize.width;
if (oldOffset.x < 0) {
// overscrolled at the beginning, leave offset alone
} else if (offsetHeight > oldContentSize.width && newContentSize.width > viewportSize.width) {
// overscrolled at the end, preserve overscroll amount as much as possible
newOffset.x = MAX(0, oldOffset.x - (oldContentSize.width - newContentSize.width));
} else if (offsetHeight > newContentSize.width) {
// offset falls outside of bounds, scroll back to end
newOffset.x = MAX(0, newContentSize.width - viewportSize.width);
}
}
// all other cases, offset doesn't change
return newOffset;
}
/**
* Once you set the `contentSize`, to a nonzero value, it is assumed to be
* managed by you, and we'll never automatically compute the size for you,
* unless you manually reset it back to {0, 0}
*/
- (CGSize)contentSize
{
if (!CGSizeEqualToSize(_contentSize, CGSizeZero)) {
return _contentSize;
} else if (!_contentView) {
return CGSizeZero;
} else {
CGSize singleSubviewSize = _contentView.frame.size;
CGPoint singleSubviewPosition = _contentView.frame.origin;
return (CGSize){
singleSubviewSize.width + singleSubviewPosition.x,
singleSubviewSize.height + singleSubviewPosition.y
};
}
}
- (void)reactBridgeDidFinishTransaction
{
CGSize contentSize = self.contentSize;
if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) {
// When contentSize is set manually, ScrollView internals will reset
// contentOffset to {0, 0}. Since we potentially set contentSize whenever
// anything in the ScrollView updates, we workaround this issue by manually
// adjusting contentOffset whenever this happens
CGPoint newOffset = [self calculateOffsetForContentSize:contentSize];
_scrollView.contentSize = contentSize;
_scrollView.contentOffset = newOffset;
}
[_scrollView dockClosestSectionHeader];
}
// Note: setting several properties of UIScrollView has the effect of
// resetting its contentOffset to {0, 0}. To prevent this, we generate
// setters here that will record the contentOffset beforehand, and
// restore it after the property has been set.
#define RCT_SET_AND_PRESERVE_OFFSET(setter, type) \
- (void)setter:(type)value \
{ \
CGPoint contentOffset = _scrollView.contentOffset; \
[_scrollView setter:value]; \
_scrollView.contentOffset = contentOffset; \
}
RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceHorizontal, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setAlwaysBounceVertical, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setBounces, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setBouncesZoom, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setCanCancelContentTouches, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setDecelerationRate, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setDirectionalLockEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode, UIScrollViewKeyboardDismissMode)
RCT_SET_AND_PRESERVE_OFFSET(setMaximumZoomScale, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setMinimumZoomScale, CGFloat)
RCT_SET_AND_PRESERVE_OFFSET(setPagingEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setScrollEnabled, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setScrollsToTop, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setShowsHorizontalScrollIndicator, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setShowsVerticalScrollIndicator, BOOL)
RCT_SET_AND_PRESERVE_OFFSET(setZoomScale, CGFloat);
RCT_SET_AND_PRESERVE_OFFSET(setScrollIndicatorInsets, UIEdgeInsets);
#pragma mark - Forward methods and properties to underlying UIScrollView
- (BOOL)respondsToSelector:(SEL)aSelector
{
return [super respondsToSelector:aSelector] || [_scrollView respondsToSelector:aSelector];
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
[_scrollView setValue:value forKey:key];
}
- (id)valueForUndefinedKey:(NSString *)key
{
return [_scrollView valueForKey:key];
}
@end

View File

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

View File

@@ -0,0 +1,85 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTScrollViewManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTScrollView.h"
#import "RCTSparseArray.h"
#import "RCTUIManager.h"
@implementation RCTScrollViewManager
- (UIView *)view
{
return [[RCTScrollView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(alwaysBounceHorizontal, BOOL)
RCT_EXPORT_VIEW_PROPERTY(alwaysBounceVertical, BOOL)
RCT_EXPORT_VIEW_PROPERTY(bounces, BOOL)
RCT_EXPORT_VIEW_PROPERTY(bouncesZoom, BOOL)
RCT_EXPORT_VIEW_PROPERTY(canCancelContentTouches, BOOL)
RCT_EXPORT_VIEW_PROPERTY(centerContent, BOOL)
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL)
RCT_EXPORT_VIEW_PROPERTY(decelerationRate, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(directionalLockEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(keyboardDismissMode, UIScrollViewKeyboardDismissMode)
RCT_EXPORT_VIEW_PROPERTY(maximumZoomScale, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(minimumZoomScale, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(pagingEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(scrollsToTop, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL)
RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL)
RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices, NSNumberArray);
RCT_EXPORT_VIEW_PROPERTY(throttleScrollCallbackMS, double);
RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat);
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets);
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets);
RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint);
- (NSDictionary *)constantsToExport
{
return @{
@"DecelerationRate": @{
@"Normal": @(UIScrollViewDecelerationRateNormal),
@"Fast": @(UIScrollViewDecelerationRateFast),
},
@"KeyboardDismissMode": @{
@"None": @(UIScrollViewKeyboardDismissModeNone),
@"Interactive": @(UIScrollViewKeyboardDismissModeInteractive),
@"OnDrag": @(UIScrollViewKeyboardDismissModeOnDrag),
},
};
}
- (void)getContentSize:(NSNumber *)reactTag
callback:(RCTResponseSenderBlock)callback
{
RCT_EXPORT();
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
UIView *view = viewRegistry[reactTag];
if (!view) {
RCTLogError(@"Cannot find view with tag %@", reactTag);
return;
}
CGSize size = ((RCTScrollView *)view).scrollView.contentSize;
callback(@[@{
@"width" : @(size.width),
@"height" : @(size.height)
}]);
}];
}
@end

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
/**
* Contains any methods related to scrolling. Any `RCTView` that has scrolling
* features should implement these methods.
*/
@protocol RCTScrollableProtocol
@property (nonatomic, 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

159
React/Views/RCTShadowView.h Normal file
View File

@@ -0,0 +1,159 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "Layout.h"
#import "RCTViewNodeProtocol.h"
@class RCTSparseArray;
typedef NS_ENUM(NSUInteger, RCTUpdateLifecycle) {
RCTUpdateLifecycleUninitialized = 0,
RCTUpdateLifecycleComputed,
RCTUpdateLifecycleDirtied,
};
// TODO: is this redundact now?
typedef void (^RCTApplierBlock)(RCTSparseArray *);
/**
* ShadowView tree mirrors RCT view tree. Every node is highly stateful.
* 1. A node is in one of three lifecycles: uninitialized, computed, dirtied.
* 1. RCTBridge may call any of the padding/margin/width/height/top/left setters. A setter would dirty
* the node and all of its ancestors.
* 2. At the end of each Bridge transaction, we call collectUpdatedFrames:widthConstraint:heightConstraint
* at the root node to recursively lay out the entire hierarchy.
* 3. If a node is "computed" and the constraint passed from above is identical to the constraint used to
* perform the last computation, we skip laying out the subtree entirely.
*/
@interface RCTShadowView : NSObject <RCTViewNodeProtocol>
@property (nonatomic, weak, readonly) RCTShadowView *superview;
@property (nonatomic, assign, readonly) css_node_t *cssNode;
@property (nonatomic, copy) NSString *viewName;
@property (nonatomic, assign) BOOL isBGColorExplicitlySet; // Used to propagate to children
@property (nonatomic, strong) UIColor *backgroundColor; // Used to propagate to children
@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle;
/**
* isNewView - Used to track the first time the view is introduced into the hierarchy. It is initialized YES, then is
* set to NO in RCTUIManager after the layout pass is done and all frames have been extracted to be applied to the
* corresponding UIViews.
*/
@property (nonatomic, assign, getter=isNewView) BOOL newView;
/**
* Position and dimensions.
* Defaults to { 0, 0, NAN, NAN }.
*/
@property (nonatomic, assign) CGFloat top;
@property (nonatomic, assign) CGFloat left;
@property (nonatomic, assign) CGFloat bottom;
@property (nonatomic, assign) CGFloat right;
@property (nonatomic, assign) CGFloat width;
@property (nonatomic, assign) CGFloat height;
@property (nonatomic, assign) CGRect frame;
- (void)setTopLeft:(CGPoint)topLeft;
- (void)setSize:(CGSize)size;
/**
* Border. Defaults to { 0, 0, 0, 0 }.
*/
@property (nonatomic, assign) CGFloat borderTopWidth;
@property (nonatomic, assign) CGFloat borderLeftWidth;
@property (nonatomic, assign) CGFloat borderBottomWidth;
@property (nonatomic, assign) CGFloat borderRightWidth;
- (void)setBorderWidth:(CGFloat)value;
/**
* Margin. Defaults to { 0, 0, 0, 0 }.
*/
@property (nonatomic, assign) CGFloat marginTop;
@property (nonatomic, assign) CGFloat marginLeft;
@property (nonatomic, assign) CGFloat marginBottom;
@property (nonatomic, assign) CGFloat marginRight;
- (void)setMargin:(CGFloat)margin;
- (void)setMarginVertical:(CGFloat)margin;
- (void)setMarginHorizontal:(CGFloat)margin;
/**
* Padding. Defaults to { 0, 0, 0, 0 }.
*/
@property (nonatomic, assign) CGFloat paddingTop;
@property (nonatomic, assign) CGFloat paddingLeft;
@property (nonatomic, assign) CGFloat paddingBottom;
@property (nonatomic, assign) CGFloat paddingRight;
- (void)setPadding:(CGFloat)padding;
- (void)setPaddingVertical:(CGFloat)padding;
- (void)setPaddingHorizontal:(CGFloat)padding;
- (UIEdgeInsets)paddingAsInsets;
/**
* Flexbox properties. All zero/disabled by default
*/
@property (nonatomic, assign) css_flex_direction_t flexDirection;
@property (nonatomic, assign) css_justify_t justifyContent;
@property (nonatomic, assign) css_align_t alignSelf;
@property (nonatomic, assign) css_align_t alignItems;
@property (nonatomic, assign) css_position_type_t positionType;
@property (nonatomic, assign) css_wrap_type_t flexWrap;
@property (nonatomic, assign) CGFloat flex;
/**
* Calculate property changes that need to be propagated to the view.
* The applierBlocks set contains RCTApplierBlock functions that must be applied
* on the main thread in order to update the view.
*/
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties;
/**
* Calculate all views whose frame needs updating after layout has been calculated.
* The viewsWithNewFrame set contains the reactTags of the views that need updating.
*/
- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstraint:(CGSize)parentConstraint;
/**
* The following are implementation details exposed to subclasses. Do not call them directly
*/
- (void)fillCSSNode:(css_node_t *)node;
- (void)dirtyLayout;
- (BOOL)isLayoutDirty;
// TODO: is this still needed?
- (void)dirtyPropagation;
- (BOOL)isPropagationDirty;
// TODO: move this to text node?
- (void)dirtyText;
- (BOOL)isTextDirty;
- (void)setTextComputed;
/**
* Triggers a recalculation of the shadow view's layout.
*/
- (void)updateLayout;
/**
* Computes the recursive offset, meaning the sum of all descendant offsets -
* this is the sum of all positions inset from parents. This is not merely the
* sum of `top`/`left`s, as this function uses the *actual* positions of
* children, not the style specified positions - it computes this based on the
* resulting layout. It does not yet compensate for native scroll view insets or
* transforms or anchor points.
*/
- (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor;
@end

537
React/Views/RCTShadowView.m Normal file
View File

@@ -0,0 +1,537 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTShadowView.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTSparseArray.h"
#import "RCTUtils.h"
typedef void (^RCTActionBlock)(RCTShadowView *shadowViewSelf, id value);
typedef void (^RCTResetActionBlock)(RCTShadowView *shadowViewSelf);
const NSString *const RCTBackgroundColorProp = @"backgroundColor";
typedef enum {
META_PROP_LEFT,
META_PROP_TOP,
META_PROP_RIGHT,
META_PROP_BOTTOM,
META_PROP_HORIZONTAL,
META_PROP_VERTICAL,
META_PROP_ALL,
META_PROP_COUNT,
} meta_prop_t;
@implementation RCTShadowView
{
RCTUpdateLifecycle _propagationLifecycle;
RCTUpdateLifecycle _textLifecycle;
NSDictionary *_lastParentProperties;
NSMutableArray *_reactSubviews;
BOOL _recomputePadding;
BOOL _recomputeMargin;
float _paddingMetaProps[META_PROP_COUNT];
float _marginMetaProps[META_PROP_COUNT];
}
@synthesize reactTag = _reactTag;
// css_node api
static void RCTPrint(void *context)
{
RCTShadowView *shadowView = (__bridge RCTShadowView *)context;
printf("%s(%zd), ", shadowView.viewName.UTF8String, shadowView.reactTag.integerValue);
}
static css_node_t *RCTGetChild(void *context, int i)
{
RCTShadowView *shadowView = (__bridge RCTShadowView *)context;
RCTShadowView *child = [shadowView reactSubviews][i];
return child->_cssNode;
}
static bool RCTIsDirty(void *context)
{
RCTShadowView *shadowView = (__bridge RCTShadowView *)context;
return [shadowView isLayoutDirty];
}
// Enforces precedence rules, e.g. marginLeft > marginHorizontal > margin.
static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float style[CSS_POSITION_COUNT]) {
style[CSS_LEFT] = !isUndefined(metaProps[META_PROP_LEFT]) ? metaProps[META_PROP_LEFT]
: !isUndefined(metaProps[META_PROP_HORIZONTAL]) ? metaProps[META_PROP_HORIZONTAL]
: !isUndefined(metaProps[META_PROP_ALL]) ? metaProps[META_PROP_ALL]
: 0;
style[CSS_RIGHT] = !isUndefined(metaProps[META_PROP_RIGHT]) ? metaProps[META_PROP_RIGHT]
: !isUndefined(metaProps[META_PROP_HORIZONTAL]) ? metaProps[META_PROP_HORIZONTAL]
: !isUndefined(metaProps[META_PROP_ALL]) ? metaProps[META_PROP_ALL]
: 0;
style[CSS_TOP] = !isUndefined(metaProps[META_PROP_TOP]) ? metaProps[META_PROP_TOP]
: !isUndefined(metaProps[META_PROP_VERTICAL]) ? metaProps[META_PROP_VERTICAL]
: !isUndefined(metaProps[META_PROP_ALL]) ? metaProps[META_PROP_ALL]
: 0;
style[CSS_BOTTOM] = !isUndefined(metaProps[META_PROP_BOTTOM]) ? metaProps[META_PROP_BOTTOM]
: !isUndefined(metaProps[META_PROP_VERTICAL]) ? metaProps[META_PROP_VERTICAL]
: !isUndefined(metaProps[META_PROP_ALL]) ? metaProps[META_PROP_ALL]
: 0;
}
- (void)fillCSSNode:(css_node_t *)node
{
node->children_count = (int)_reactSubviews.count;
}
// The absolute stuff is so that we can take into account our absolute position when rounding in order to
// snap to the pixel grid. For example, say you have the following structure:
//
// +--------+---------+--------+
// | |+-------+| |
// | || || |
// | |+-------+| |
// +--------+---------+--------+
//
// Say the screen width is 320 pts so the three big views will get the following x bounds from our layout system:
// {0, 106.667}, {106.667, 213.333}, {213.333, 320}
//
// Assuming screen scale is 2, these numbers must be rounded to the nearest 0.5 to fit the pixel grid:
// {0, 106.5}, {106.5, 213.5}, {213.5, 320}
// You'll notice that the three widths are 106.5, 107, 106.5.
//
// This is great for the parent views but it gets trickier when we consider rounding for the subview.
//
// When we go to round the bounds for the subview in the middle, it's relative bounds are {0, 106.667}
// which gets rounded to {0, 106.5}. This will cause the subview to be one pixel smaller than it should be.
// this is why we need to pass in the absolute position in order to do the rounding relative to the screen's
// grid rather than the view's grid.
//
// After passing in the absolutePosition of {106.667, y}, we do the following calculations:
// absoluteLeft = round(absolutePosition.x + viewPosition.left) = round(106.667 + 0) = 106.5
// absoluteRight = round(absolutePosition.x + viewPosition.left + viewSize.left) + round(106.667 + 0 + 106.667) = 213.5
// width = 213.5 - 106.5 = 107
// You'll notice that this is the same width we calculated for the parent view because we've taken its position into account.
- (void)applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition
{
if (!node->layout.should_update) {
return;
}
node->layout.should_update = false;
_layoutLifecycle = RCTUpdateLifecycleComputed;
CGPoint absoluteTopLeft = {
RCTRoundPixelValue(absolutePosition.x + node->layout.position[CSS_LEFT]),
RCTRoundPixelValue(absolutePosition.y + node->layout.position[CSS_TOP])
};
CGPoint absoluteBottomRight = {
RCTRoundPixelValue(absolutePosition.x + node->layout.position[CSS_LEFT] + node->layout.dimensions[CSS_WIDTH]),
RCTRoundPixelValue(absolutePosition.y + node->layout.position[CSS_TOP] + node->layout.dimensions[CSS_HEIGHT])
};
CGRect frame = {{
RCTRoundPixelValue(node->layout.position[CSS_LEFT]),
RCTRoundPixelValue(node->layout.position[CSS_TOP]),
}, {
RCTRoundPixelValue(absoluteBottomRight.x - absoluteTopLeft.x),
RCTRoundPixelValue(absoluteBottomRight.y - absoluteTopLeft.y)
}};
if (!CGRectEqualToRect(frame, _frame)) {
_frame = frame;
[viewsWithNewFrame addObject:self];
}
absolutePosition.x += node->layout.position[CSS_LEFT];
absolutePosition.y += node->layout.position[CSS_TOP];
node->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED;
node->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED;
node->layout.position[CSS_LEFT] = 0;
node->layout.position[CSS_TOP] = 0;
for (int i = 0; i < node->children_count; ++i) {
RCTShadowView *child = (RCTShadowView *)_reactSubviews[i];
[child applyLayoutNode:node->get_child(node->context, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
}
}
- (NSDictionary *)processBackgroundColor:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties
{
if (!_isBGColorExplicitlySet) {
UIColor *parentBackgroundColor = parentProperties[RCTBackgroundColorProp];
if (parentBackgroundColor && ![_backgroundColor isEqual:parentBackgroundColor]) {
_backgroundColor = parentBackgroundColor;
[applierBlocks addObject:^(RCTSparseArray *viewRegistry) {
UIView *view = viewRegistry[_reactTag];
view.backgroundColor = parentBackgroundColor;
}];
}
}
if (_isBGColorExplicitlySet) {
// Update parent properties for children
NSMutableDictionary *properties = [NSMutableDictionary dictionaryWithDictionary:parentProperties];
CGFloat alpha = CGColorGetAlpha(_backgroundColor.CGColor);
if (alpha < 1.0 && alpha > 0.0) {
// If we see partial transparency, start propagating full transparency
properties[RCTBackgroundColorProp] = [UIColor clearColor];
} else {
properties[RCTBackgroundColorProp] = _backgroundColor;
}
return properties;
}
return parentProperties;
}
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties
{
if (_propagationLifecycle == RCTUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) {
return;
}
_propagationLifecycle = RCTUpdateLifecycleComputed;
_lastParentProperties = parentProperties;
NSDictionary *nextProps = [self processBackgroundColor:applierBlocks parentProperties:parentProperties];
for (RCTShadowView *child in _reactSubviews) {
[child collectUpdatedProperties:applierBlocks parentProperties:nextProps];
}
}
- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstraint:(CGSize)parentConstraint
{
[self fillCSSNode:_cssNode];
layoutNode(_cssNode, CSS_UNDEFINED);
[self applyLayoutNode:_cssNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:CGPointZero];
}
- (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor
{
CGFloat totalOffsetTop = 0.0;
CGFloat totalOffsetLeft = 0.0;
CGSize size = self.frame.size;
NSInteger depth = 30; // max depth to search
RCTShadowView *shadowView = self;
while (depth && shadowView && shadowView != ancestor) {
totalOffsetTop += shadowView.frame.origin.y;
totalOffsetLeft += shadowView.frame.origin.x;
shadowView = shadowView->_superview;
depth--;
}
if (ancestor != shadowView) {
return CGRectNull;
}
return (CGRect){{totalOffsetLeft, totalOffsetTop}, size};
}
- (instancetype)init
{
if ((self = [super init])) {
_frame = CGRectMake(0, 0, CSS_UNDEFINED, CSS_UNDEFINED);
for (int ii = 0; ii < META_PROP_COUNT; ii++) {
_paddingMetaProps[ii] = CSS_UNDEFINED;
_marginMetaProps[ii] = CSS_UNDEFINED;
}
_newView = YES;
_layoutLifecycle = RCTUpdateLifecycleUninitialized;
_propagationLifecycle = RCTUpdateLifecycleUninitialized;
_textLifecycle = RCTUpdateLifecycleUninitialized;
_reactSubviews = [NSMutableArray array];
_cssNode = new_css_node();
_cssNode->context = (__bridge void *)self;
_cssNode->print = RCTPrint;
_cssNode->get_child = RCTGetChild;
_cssNode->is_dirty = RCTIsDirty;
[self fillCSSNode:_cssNode];
}
return self;
}
- (BOOL)isReactRootView
{
return RCTIsReactRootView(self.reactTag);
}
- (void)dealloc
{
free_css_node(_cssNode);
}
- (void)dirtyLayout
{
if (_layoutLifecycle != RCTUpdateLifecycleDirtied) {
_layoutLifecycle = RCTUpdateLifecycleDirtied;
[_superview dirtyLayout];
}
}
- (BOOL)isLayoutDirty
{
return _layoutLifecycle != RCTUpdateLifecycleComputed;
}
- (void)dirtyPropagation
{
if (_propagationLifecycle != RCTUpdateLifecycleDirtied) {
_propagationLifecycle = RCTUpdateLifecycleDirtied;
[_superview dirtyPropagation];
}
}
- (BOOL)isPropagationDirty
{
return _propagationLifecycle != RCTUpdateLifecycleComputed;
}
- (void)dirtyText
{
if (_textLifecycle != RCTUpdateLifecycleDirtied) {
_textLifecycle = RCTUpdateLifecycleDirtied;
[_superview dirtyText];
}
}
- (BOOL)isTextDirty
{
return _textLifecycle != RCTUpdateLifecycleComputed;
}
- (void)setTextComputed
{
_textLifecycle = RCTUpdateLifecycleComputed;
}
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex
{
[_reactSubviews insertObject:subview atIndex:atIndex];
_cssNode->children_count = (int)[_reactSubviews count];
subview->_superview = self;
[self dirtyText];
[self dirtyLayout];
[self dirtyPropagation];
}
- (void)removeReactSubview:(RCTShadowView *)subview
{
[subview dirtyText];
[subview dirtyLayout];
[subview dirtyPropagation];
subview->_superview = nil;
[_reactSubviews removeObject:subview];
_cssNode->children_count = (int)[_reactSubviews count];
}
- (NSArray *)reactSubviews
{
return _reactSubviews;
}
- (RCTShadowView *)reactSuperview
{
return _superview;
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
{
for (RCTShadowView *shadowView in _reactSubviews) {
if (CGRectContainsPoint(shadowView.frame, point)) {
CGPoint relativePoint = point;
CGPoint origin = shadowView.frame.origin;
relativePoint.x -= origin.x;
relativePoint.y -= origin.y;
return [shadowView reactTagAtPoint:relativePoint];
}
}
return self.reactTag;
}
// Margin
#define RCT_MARGIN_PROPERTY(prop, metaProp) \
- (void)setMargin##prop:(CGFloat)value \
{ \
_marginMetaProps[META_PROP_##metaProp] = value; \
_recomputeMargin = YES; \
} \
- (CGFloat)margin##prop \
{ \
return _marginMetaProps[META_PROP_##metaProp]; \
}
RCT_MARGIN_PROPERTY(, ALL)
RCT_MARGIN_PROPERTY(Vertical, VERTICAL)
RCT_MARGIN_PROPERTY(Horizontal, HORIZONTAL)
RCT_MARGIN_PROPERTY(Top, TOP)
RCT_MARGIN_PROPERTY(Left, LEFT)
RCT_MARGIN_PROPERTY(Bottom, BOTTOM)
RCT_MARGIN_PROPERTY(Right, RIGHT)
// Padding
#define RCT_PADDING_PROPERTY(prop, metaProp) \
- (void)setPadding##prop:(CGFloat)value \
{ \
_paddingMetaProps[META_PROP_##metaProp] = value; \
_recomputePadding = YES; \
} \
- (CGFloat)padding##prop \
{ \
return _paddingMetaProps[META_PROP_##metaProp]; \
}
RCT_PADDING_PROPERTY(, ALL)
RCT_PADDING_PROPERTY(Vertical, VERTICAL)
RCT_PADDING_PROPERTY(Horizontal, HORIZONTAL)
RCT_PADDING_PROPERTY(Top, TOP)
RCT_PADDING_PROPERTY(Left, LEFT)
RCT_PADDING_PROPERTY(Bottom, BOTTOM)
RCT_PADDING_PROPERTY(Right, RIGHT)
- (UIEdgeInsets)paddingAsInsets
{
return (UIEdgeInsets){
_cssNode->style.padding[CSS_TOP],
_cssNode->style.padding[CSS_LEFT],
_cssNode->style.padding[CSS_BOTTOM],
_cssNode->style.padding[CSS_RIGHT]
};
}
// Border
#define RCT_BORDER_PROPERTY(prop, metaProp) \
- (void)setBorder##prop##Width:(CGFloat)value \
{ \
_cssNode->style.border[CSS_##metaProp] = value; \
[self dirtyLayout]; \
} \
- (CGFloat)border##prop##Width \
{ \
return _cssNode->style.border[META_PROP_##metaProp]; \
}
RCT_BORDER_PROPERTY(Top, TOP)
RCT_BORDER_PROPERTY(Left, LEFT)
RCT_BORDER_PROPERTY(Bottom, BOTTOM)
RCT_BORDER_PROPERTY(Right, RIGHT)
- (void)setBorderWidth:(CGFloat)value
{
for (int i = 0; i < 4; i++) {
_cssNode->style.border[i] = value;
}
[self dirtyLayout];
}
// Dimensions
#define RCT_DIMENSIONS_PROPERTY(setProp, getProp, cssProp) \
- (void)set##setProp:(CGFloat)value \
{ \
_cssNode->style.dimensions[CSS_##cssProp] = value; \
[self dirtyLayout]; \
} \
- (CGFloat)getProp \
{ \
return _cssNode->style.dimensions[CSS_##cssProp]; \
}
RCT_DIMENSIONS_PROPERTY(Width, width, WIDTH)
RCT_DIMENSIONS_PROPERTY(Height, height, HEIGHT)
// Position
#define RCT_POSITION_PROPERTY(setProp, getProp, cssProp) \
- (void)set##setProp:(CGFloat)value \
{ \
_cssNode->style.position[CSS_##cssProp] = value; \
[self dirtyLayout]; \
} \
- (CGFloat)getProp \
{ \
return _cssNode->style.position[CSS_##cssProp]; \
}
RCT_POSITION_PROPERTY(Top, top, TOP)
RCT_POSITION_PROPERTY(Right, right, RIGHT)
RCT_POSITION_PROPERTY(Bottom, bottom, BOTTOM)
RCT_POSITION_PROPERTY(Left, left, LEFT)
- (void)setFrame:(CGRect)frame
{
_cssNode->style.position[CSS_LEFT] = CGRectGetMinX(frame);
_cssNode->style.position[CSS_TOP] = CGRectGetMinY(frame);
_cssNode->style.dimensions[CSS_WIDTH] = CGRectGetWidth(frame);
_cssNode->style.dimensions[CSS_HEIGHT] = CGRectGetHeight(frame);
[self dirtyLayout];
}
- (void)setTopLeft:(CGPoint)topLeft
{
_cssNode->style.position[CSS_LEFT] = topLeft.x;
_cssNode->style.position[CSS_TOP] = topLeft.y;
[self dirtyLayout];
}
- (void)setSize:(CGSize)size
{
_cssNode->style.dimensions[CSS_WIDTH] = size.width;
_cssNode->style.dimensions[CSS_HEIGHT] = size.height;
[self dirtyLayout];
}
// Flex
#define RCT_STYLE_PROPERTY(setProp, getProp, cssProp, type) \
- (void)set##setProp:(type)value \
{ \
_cssNode->style.cssProp = value; \
[self dirtyLayout]; \
} \
- (type)getProp \
{ \
return _cssNode->style.cssProp; \
}
RCT_STYLE_PROPERTY(Flex, flex, flex, CGFloat)
RCT_STYLE_PROPERTY(FlexDirection, flexDirection, flex_direction, css_flex_direction_t)
RCT_STYLE_PROPERTY(JustifyContent, justifyContent, justify_content, css_justify_t)
RCT_STYLE_PROPERTY(AlignSelf, alignSelf, align_self, css_align_t)
RCT_STYLE_PROPERTY(AlignItems, alignItems, align_items, css_align_t)
RCT_STYLE_PROPERTY(PositionType, positionType, position_type, css_position_type_t)
RCT_STYLE_PROPERTY(FlexWrap, flexWrap, flex_wrap, css_wrap_type_t)
- (void)setBackgroundColor:(UIColor *)color
{
_backgroundColor = color;
[self dirtyPropagation];
}
- (void)updateLayout
{
if (_recomputePadding) {
RCTProcessMetaProps(_paddingMetaProps, _cssNode->style.padding);
}
if (_recomputeMargin) {
RCTProcessMetaProps(_marginMetaProps, _cssNode->style.margin);
}
if (_recomputePadding || _recomputeMargin) {
[self dirtyLayout];
}
[self fillCSSNode:_cssNode];
_recomputeMargin = NO;
_recomputePadding = NO;
}
@end

View File

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

View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTSliderManager.h"
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import "UIView+React.h"
@implementation RCTSliderManager
- (UIView *)view
{
UISlider *slider = [[UISlider alloc] init];
[slider addTarget:self action:@selector(sliderValueChanged:) forControlEvents:UIControlEventValueChanged];
[slider addTarget:self action:@selector(sliderTouchEnd:) forControlEvents:UIControlEventTouchUpInside];
return slider;
}
- (void)sliderValueChanged:(UISlider *)sender
{
NSDictionary *event = @{
@"target": sender.reactTag,
@"value": @(sender.value),
@"continuous": @YES,
};
[self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event];
}
- (void)sliderTouchEnd:(UISlider *)sender
{
NSDictionary *event = @{
@"target": sender.reactTag,
@"value": @(sender.value),
@"continuous": @NO,
};
[self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event];
}
RCT_EXPORT_VIEW_PROPERTY(value, float);
RCT_EXPORT_VIEW_PROPERTY(minimumValue, float);
RCT_EXPORT_VIEW_PROPERTY(maximumValue, float);
@end

17
React/Views/RCTSwitch.h Normal file
View File

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

22
React/Views/RCTSwitch.m Normal file
View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTSwitch.h"
#import "RCTEventDispatcher.h"
#import "UIView+React.h"
@implementation RCTSwitch
- (void)setOn:(BOOL)on animated:(BOOL)animated {
_wasOn = on;
[super setOn:on animated:animated];
}
@end

View File

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

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTSwitchManager.h"
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import "RCTSwitch.h"
#import "UIView+React.h"
@implementation RCTSwitchManager
- (UIView *)view
{
RCTSwitch *switcher = [[RCTSwitch alloc] init];
[switcher addTarget:self
action:@selector(onChange:)
forControlEvents:UIControlEventValueChanged];
return switcher;
}
- (void)onChange:(RCTSwitch *)sender
{
if (sender.wasOn != sender.on) {
[self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:@{
@"target": sender.reactTag,
@"value": @(sender.on)
}];
sender.wasOn = sender.on;
}
}
RCT_EXPORT_VIEW_PROPERTY(onTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(thumbTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(on, BOOL);
RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL);
@end

18
React/Views/RCTTabBar.h Normal file
View File

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

145
React/Views/RCTTabBar.m Normal file
View File

@@ -0,0 +1,145 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTTabBar.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTTabBarItem.h"
#import "RCTUtils.h"
#import "RCTView.h"
#import "RCTViewControllerProtocol.h"
#import "RCTWrapperViewController.h"
#import "UIView+React.h"
@interface RKCustomTabBarController : UITabBarController <RCTViewControllerProtocol>
@end
@implementation RKCustomTabBarController
@synthesize currentTopLayoutGuide = _currentTopLayoutGuide;
@synthesize currentBottomLayoutGuide = _currentBottomLayoutGuide;
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
_currentTopLayoutGuide = self.topLayoutGuide;
_currentBottomLayoutGuide = self.bottomLayoutGuide;
}
@end
@interface RCTTabBar() <UITabBarControllerDelegate>
@end
@implementation RCTTabBar
{
BOOL _tabsChanged;
RCTEventDispatcher *_eventDispatcher;
UITabBarController *_tabController;
NSMutableArray *_tabViews;
}
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
_tabViews = [[NSMutableArray alloc] init];
_tabController = [[RKCustomTabBarController alloc] init];
_tabController.delegate = self;
[self addSubview:_tabController.view];
}
return self;
}
- (UIViewController *)backingViewController
{
return _tabController;
}
- (void)dealloc
{
_tabController.delegate = nil;
}
- (NSArray *)reactSubviews
{
return _tabViews;
}
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
if (![view isKindOfClass:[RCTTabBarItem class]]) {
RCTLogError(@"subview should be of type RCTTabBarItem");
return;
}
[_tabViews insertObject:view atIndex:atIndex];
_tabsChanged = YES;
}
- (void)removeReactSubview:(UIView *)subview
{
if (_tabViews.count == 0) {
RCTLogError(@"should have at least one view to remove a subview");
return;
}
[_tabViews removeObject:subview];
_tabsChanged = YES;
}
- (void)layoutSubviews
{
[super layoutSubviews];
_tabController.view.frame = self.bounds;
}
- (void)reactBridgeDidFinishTransaction
{
// we can't hook up the VC hierarchy in 'init' because the subviews aren't
// hooked up yet, so we do it on demand here whenever a transaction has finished
[self addControllerToClosestParent:_tabController];
if (_tabsChanged) {
NSMutableArray *viewControllers = [NSMutableArray array];
for (RCTTabBarItem *tab in [self reactSubviews]) {
UIViewController *controller = tab.backingViewController;
if (!controller) {
controller = [[RCTWrapperViewController alloc] initWithContentView:tab
eventDispatcher:_eventDispatcher];
}
[viewControllers addObject:controller];
}
_tabController.viewControllers = viewControllers;
_tabsChanged = NO;
}
[[self reactSubviews] enumerateObjectsUsingBlock:^(RCTTabBarItem *tab, NSUInteger index, BOOL *stop) {
UIViewController *controller = _tabController.viewControllers[index];
controller.tabBarItem = tab.barItem;
if (tab.selected) {
_tabController.selectedViewController = controller;
}
}];
}
#pragma mark - UITabBarControllerDelegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
NSUInteger index = [tabBarController.viewControllers indexOfObject:viewController];
RCTTabBarItem *tab = [self reactSubviews][index];
[_eventDispatcher sendInputEventWithName:@"topTap" body:@{@"target": tab.reactTag}];
return NO;
}
@end

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@interface RCTTabBarItem : UIView
@property (nonatomic, copy) NSString *icon;
@property (nonatomic, assign, getter=isSelected) BOOL selected;
@property (nonatomic, readonly) UITabBarItem *barItem;
@end

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTTabBarItem.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "UIView+React.h"
@implementation RCTTabBarItem
@synthesize barItem = _barItem;
- (UITabBarItem *)barItem
{
if (!_barItem) {
_barItem = [[UITabBarItem alloc] init];
}
return _barItem;
}
- (void)setIcon:(NSString *)icon
{
static NSDictionary *systemIcons;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
systemIcons = @{
@"more": @(UITabBarSystemItemMore),
@"favorites": @(UITabBarSystemItemFavorites),
@"featured": @(UITabBarSystemItemFeatured),
@"topRated": @(UITabBarSystemItemTopRated),
@"recents": @(UITabBarSystemItemRecents),
@"contacts": @(UITabBarSystemItemContacts),
@"history": @(UITabBarSystemItemHistory),
@"bookmarks": @(UITabBarSystemItemBookmarks),
@"search": @(UITabBarSystemItemSearch),
@"downloads": @(UITabBarSystemItemDownloads),
@"mostRecent": @(UITabBarSystemItemMostRecent),
@"mostViewed": @(UITabBarSystemItemMostViewed),
};
});
// Update icon
BOOL wasSystemIcon = (systemIcons[_icon] != nil);
_icon = [icon copy];
// Check if string matches any custom images first
UIImage *image = [RCTConvert UIImage:_icon];
UITabBarItem *oldItem = _barItem;
if (image) {
// Recreate barItem if previous item was a system icon
if (wasSystemIcon) {
_barItem = nil;
self.barItem.image = image;
} else {
self.barItem.image = image;
return;
}
} else {
// Not a custom image, may be a system item?
NSNumber *systemIcon = systemIcons[icon];
if (!systemIcon) {
RCTLogError(@"The tab bar icon '%@' did not match any known image or system icon", icon);
return;
}
_barItem = [[UITabBarItem alloc] initWithTabBarSystemItem:[systemIcon integerValue] tag:oldItem.tag];
}
// Reapply previous properties
_barItem.title = oldItem.title;
_barItem.imageInsets = oldItem.imageInsets;
_barItem.selectedImage = oldItem.selectedImage;
_barItem.badgeValue = oldItem.badgeValue;
}
- (UIViewController *)backingViewController
{
return self.superview.backingViewController;
}
@end

View File

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

View File

@@ -0,0 +1,32 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTTabBarItemManager.h"
#import "RCTConvert.h"
#import "RCTTabBarItem.h"
@implementation RCTTabBarItemManager
- (UIView *)view
{
return [[RCTTabBarItem alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(selected, BOOL);
RCT_EXPORT_VIEW_PROPERTY(icon, NSString);
RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage);
RCT_REMAP_VIEW_PROPERTY(badgeValue, barItem.badgeValue, NSString);
RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem)
{
view.barItem.title = json ? [RCTConvert NSString:json] : defaultView.barItem.title;
view.barItem.imageInsets = [view.barItem.title length] ? UIEdgeInsetsZero : (UIEdgeInsets){6, 0, -6, 0};
}
@end

View File

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

View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTTabBarManager.h"
#import "RCTBridge.h"
#import "RCTTabBar.h"
@implementation RCTTabBarManager
@synthesize bridge = _bridge;
- (UIView *)view
{
return [[RCTTabBar alloc] initWithEventDispatcher:_bridge.eventDispatcher];
}
@end

View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@interface RCTTextField : UITextField
@property (nonatomic, assign) BOOL caretHidden;
@property (nonatomic, assign) BOOL autoCorrect;
@property (nonatomic, assign) UIEdgeInsets paddingEdgeInsets; // TODO: contentInset
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@end

132
React/Views/RCTTextField.m Normal file
View File

@@ -0,0 +1,132 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTTextField.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTUtils.h"
#import "UIView+React.h"
@implementation RCTTextField
{
RCTEventDispatcher *_eventDispatcher;
NSMutableArray *_reactSubviews;
BOOL _jsRequestingFirstResponder;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
[self addTarget:self action:@selector(_textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
[self addTarget:self action:@selector(_textFieldBeginEditing) forControlEvents:UIControlEventEditingDidBegin];
[self addTarget:self action:@selector(_textFieldEndEditing) forControlEvents:UIControlEventEditingDidEnd];
[self addTarget:self action:@selector(_textFieldSubmitEditing) forControlEvents:UIControlEventEditingDidEndOnExit];
_reactSubviews = [[NSMutableArray alloc] init];
self.returnKeyType = UIReturnKeyDone;
}
return self;
}
- (NSArray *)reactSubviews
{
// TODO: do we support subviews of textfield in React?
// In any case, we should have a better approach than manually
// maintaining array in each view subclass like this
return _reactSubviews;
}
- (void)removeReactSubview:(UIView *)subview
{
// TODO: this is a bit broken - if the TextField inserts any of
// its own views below or between React's, the indices won't match
[_reactSubviews removeObject:subview];
[subview removeFromSuperview];
}
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
// TODO: this is a bit broken - if the TextField inserts any of
// its own views below or between React's, the indices won't match
[_reactSubviews insertObject:view atIndex:atIndex];
[super insertSubview:view atIndex:atIndex];
}
- (CGRect)caretRectForPosition:(UITextPosition *)position
{
if (_caretHidden) {
return CGRectZero;
}
return [super caretRectForPosition:position];
}
- (CGRect)textRectForBounds:(CGRect)bounds
{
CGRect rect = [super textRectForBounds:bounds];
return UIEdgeInsetsInsetRect(rect, _paddingEdgeInsets);
}
- (CGRect)editingRectForBounds:(CGRect)bounds
{
return [self textRectForBounds:bounds];
}
- (void)setAutoCorrect:(BOOL)autoCorrect
{
self.autocorrectionType = (autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo);
}
- (BOOL)autoCorrect
{
return self.autocorrectionType == UITextAutocorrectionTypeYes;
}
#define RCT_TEXT_EVENT_HANDLER(delegateMethod, eventName) \
- (void)delegateMethod \
{ \
[_eventDispatcher sendTextEventWithType:eventName \
reactTag:self.reactTag \
text:self.text]; \
}
RCT_TEXT_EVENT_HANDLER(_textFieldDidChange, RCTTextEventTypeChange)
RCT_TEXT_EVENT_HANDLER(_textFieldBeginEditing, RCTTextEventTypeFocus)
RCT_TEXT_EVENT_HANDLER(_textFieldEndEditing, RCTTextEventTypeEnd)
RCT_TEXT_EVENT_HANDLER(_textFieldSubmitEditing, RCTTextEventTypeSubmit)
// TODO: we should support shouldChangeTextInRect (see UITextFieldDelegate)
- (BOOL)becomeFirstResponder
{
_jsRequestingFirstResponder = YES; // TODO: is this still needed?
BOOL result = [super becomeFirstResponder];
_jsRequestingFirstResponder = NO;
return result;
}
- (BOOL)resignFirstResponder
{
BOOL result = [super resignFirstResponder];
if (result)
{
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
reactTag:self.reactTag
text:self.text];
}
return result;
}
- (BOOL)canBecomeFirstResponder
{
return _jsRequestingFirstResponder;
}
@end

View File

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

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTTextFieldManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTShadowView.h"
#import "RCTSparseArray.h"
#import "RCTTextField.h"
@implementation RCTTextFieldManager
- (UIView *)view
{
return [[RCTTextField alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(caretHidden, BOOL)
RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
RCT_EXPORT_VIEW_PROPERTY(clearButtonMode, UITextFieldViewMode)
RCT_EXPORT_VIEW_PROPERTY(keyboardType, UIKeyboardType)
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType, UITextAutocapitalizationType)
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextField)
{
view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
}
RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, RCTTextField)
{
view.font = [RCTConvert UIFont:view.font withWeight:json]; // defaults to normal
}
RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, RCTTextField)
{
view.font = [RCTConvert UIFont:view.font withStyle:json]; // defaults to normal
}
RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextField)
{
view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName];
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
((RCTTextField *)viewRegistry[reactTag]).paddingEdgeInsets = padding;
};
}
@end

View File

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

Some files were not shown because too many files have changed in this diff Show More