mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-24 04:16:00 +08:00
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:
43
React/Base/RCTAssert.h
Normal file
43
React/Base/RCTAssert.h
Normal 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
19
React/Base/RCTAssert.m
Normal 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
92
React/Base/RCTBridge.h
Normal 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
826
React/Base/RCTBridge.m
Normal 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
|
||||
67
React/Base/RCTBridgeModule.h
Normal file
67
React/Base/RCTBridgeModule.h
Normal 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
29
React/Base/RCTCache.h
Normal 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
231
React/Base/RCTCache.m
Normal 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
256
React/Base/RCTConvert.h
Normal 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
770
React/Base/RCTConvert.m
Normal 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
19
React/Base/RCTDevMenu.h
Normal 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
84
React/Base/RCTDevMenu.m
Normal 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
|
||||
73
React/Base/RCTEventDispatcher.h
Normal file
73
React/Base/RCTEventDispatcher.h
Normal 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
|
||||
126
React/Base/RCTEventDispatcher.m
Normal file
126
React/Base/RCTEventDispatcher.m
Normal 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
|
||||
23
React/Base/RCTInvalidating.h
Normal file
23
React/Base/RCTInvalidating.h
Normal 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
|
||||
31
React/Base/RCTJSMethodRegistrar.h
Normal file
31
React/Base/RCTJSMethodRegistrar.h
Normal 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
|
||||
42
React/Base/RCTJavaScriptExecutor.h
Normal file
42
React/Base/RCTJavaScriptExecutor.h
Normal 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
|
||||
35
React/Base/RCTKeyCommands.h
Normal file
35
React/Base/RCTKeyCommands.h
Normal 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
123
React/Base/RCTKeyCommands.m
Normal 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
65
React/Base/RCTLog.h
Normal 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
93
React/Base/RCTLog.m
Normal 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
25
React/Base/RCTRedBox.h
Normal 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
316
React/Base/RCTRedBox.m
Normal 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
70
React/Base/RCTRootView.h
Normal 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
352
React/Base/RCTRootView.m
Normal 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
|
||||
38
React/Base/RCTSparseArray.h
Normal file
38
React/Base/RCTSparseArray.h
Normal 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
123
React/Base/RCTSparseArray.m
Normal 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
|
||||
22
React/Base/RCTTouchHandler.h
Normal file
22
React/Base/RCTTouchHandler.h
Normal 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
|
||||
358
React/Base/RCTTouchHandler.m
Normal file
358
React/Base/RCTTouchHandler.m
Normal 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
58
React/Base/RCTUtils.h
Normal 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
228
React/Base/RCTUtils.m
Normal 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;
|
||||
}
|
||||
28
React/Executors/RCTContextExecutor.h
Normal file
28
React/Executors/RCTContextExecutor.h
Normal 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
|
||||
301
React/Executors/RCTContextExecutor.m
Normal file
301
React/Executors/RCTContextExecutor.m
Normal 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
|
||||
42
React/Executors/RCTWebViewExecutor.h
Normal file
42
React/Executors/RCTWebViewExecutor.h
Normal 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
|
||||
192
React/Executors/RCTWebViewExecutor.m
Normal file
192
React/Executors/RCTWebViewExecutor.m
Normal 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
817
React/Layout/Layout.c
Normal 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
148
React/Layout/Layout.h
Normal 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
|
||||
16
React/Modules/RCTAlertManager.h
Normal file
16
React/Modules/RCTAlertManager.h
Normal 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
|
||||
114
React/Modules/RCTAlertManager.m
Normal file
114
React/Modules/RCTAlertManager.m
Normal 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
|
||||
14
React/Modules/RCTAppState.h
Normal file
14
React/Modules/RCTAppState.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface RCTAppState : NSObject<RCTBridgeModule>
|
||||
|
||||
@end
|
||||
90
React/Modules/RCTAppState.m
Normal file
90
React/Modules/RCTAppState.m
Normal 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
|
||||
31
React/Modules/RCTAsyncLocalStorage.h
Normal file
31
React/Modules/RCTAsyncLocalStorage.h
Normal 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
|
||||
299
React/Modules/RCTAsyncLocalStorage.m
Normal file
299
React/Modules/RCTAsyncLocalStorage.m
Normal 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
|
||||
24
React/Modules/RCTExceptionsManager.h
Normal file
24
React/Modules/RCTExceptionsManager.h
Normal 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
|
||||
50
React/Modules/RCTExceptionsManager.m
Normal file
50
React/Modules/RCTExceptionsManager.m
Normal 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
|
||||
19
React/Modules/RCTSourceCode.h
Normal file
19
React/Modules/RCTSourceCode.h
Normal 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
|
||||
28
React/Modules/RCTSourceCode.m
Normal file
28
React/Modules/RCTSourceCode.m
Normal 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
|
||||
16
React/Modules/RCTStatusBarManager.h
Normal file
16
React/Modules/RCTStatusBarManager.h
Normal 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
|
||||
74
React/Modules/RCTStatusBarManager.m
Normal file
74
React/Modules/RCTStatusBarManager.m
Normal 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
17
React/Modules/RCTTiming.h
Normal 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
221
React/Modules/RCTTiming.m
Normal 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
|
||||
66
React/Modules/RCTUIManager.h
Normal file
66
React/Modules/RCTUIManager.h
Normal 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
1397
React/Modules/RCTUIManager.m
Normal file
File diff suppressed because it is too large
Load Diff
647
React/React.xcodeproj/project.pbxproj
Normal file
647
React/React.xcodeproj/project.pbxproj
Normal 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 */;
|
||||
}
|
||||
18
React/Views/RCTAnimationType.h
Normal file
18
React/Views/RCTAnimationType.h
Normal 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,
|
||||
};
|
||||
20
React/Views/RCTAutoInsetsProtocol.h
Normal file
20
React/Views/RCTAutoInsetsProtocol.h
Normal 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
|
||||
14
React/Views/RCTDatePickerManager.h
Normal file
14
React/Views/RCTDatePickerManager.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTDatePickerManager : RCTViewManager
|
||||
|
||||
@end
|
||||
71
React/Views/RCTDatePickerManager.m
Normal file
71
React/Views/RCTDatePickerManager.m
Normal 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
27
React/Views/RCTMap.h
Normal 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
112
React/Views/RCTMap.m
Normal 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
|
||||
14
React/Views/RCTMapManager.h
Normal file
14
React/Views/RCTMapManager.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTMapManager : RCTViewManager
|
||||
|
||||
@end
|
||||
169
React/Views/RCTMapManager.m
Normal file
169
React/Views/RCTMapManager.m
Normal 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
21
React/Views/RCTNavItem.h
Normal 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
15
React/Views/RCTNavItem.m
Normal 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
|
||||
|
||||
15
React/Views/RCTNavItemManager.h
Normal file
15
React/Views/RCTNavItemManager.h
Normal 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
|
||||
|
||||
29
React/Views/RCTNavItemManager.m
Normal file
29
React/Views/RCTNavItemManager.m
Normal 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
|
||||
33
React/Views/RCTNavigator.h
Normal file
33
React/Views/RCTNavigator.h
Normal 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
565
React/Views/RCTNavigator.m
Normal 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
|
||||
15
React/Views/RCTNavigatorManager.h
Normal file
15
React/Views/RCTNavigatorManager.h
Normal 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
|
||||
|
||||
54
React/Views/RCTNavigatorManager.m
Normal file
54
React/Views/RCTNavigatorManager.m
Normal 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
21
React/Views/RCTPicker.h
Normal 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
99
React/Views/RCTPicker.m
Normal 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
|
||||
14
React/Views/RCTPickerManager.h
Normal file
14
React/Views/RCTPickerManager.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTPickerManager : RCTViewManager
|
||||
|
||||
@end
|
||||
35
React/Views/RCTPickerManager.m
Normal file
35
React/Views/RCTPickerManager.m
Normal 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
|
||||
17
React/Views/RCTPointerEvents.h
Normal file
17
React/Views/RCTPointerEvents.h
Normal 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,
|
||||
};
|
||||
50
React/Views/RCTScrollView.h
Normal file
50
React/Views/RCTScrollView.h
Normal 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
633
React/Views/RCTScrollView.m
Normal 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
|
||||
15
React/Views/RCTScrollViewManager.h
Normal file
15
React/Views/RCTScrollViewManager.h
Normal 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
|
||||
|
||||
85
React/Views/RCTScrollViewManager.m
Normal file
85
React/Views/RCTScrollViewManager.m
Normal 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
|
||||
25
React/Views/RCTScrollableProtocol.h
Normal file
25
React/Views/RCTScrollableProtocol.h
Normal 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
159
React/Views/RCTShadowView.h
Normal 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
537
React/Views/RCTShadowView.m
Normal 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
|
||||
14
React/Views/RCTSliderManager.h
Normal file
14
React/Views/RCTSliderManager.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTSliderManager : RCTViewManager
|
||||
|
||||
@end
|
||||
52
React/Views/RCTSliderManager.m
Normal file
52
React/Views/RCTSliderManager.m
Normal 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
17
React/Views/RCTSwitch.h
Normal 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
22
React/Views/RCTSwitch.m
Normal 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
|
||||
14
React/Views/RCTSwitchManager.h
Normal file
14
React/Views/RCTSwitchManager.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTSwitchManager : RCTViewManager
|
||||
|
||||
@end
|
||||
46
React/Views/RCTSwitchManager.m
Normal file
46
React/Views/RCTSwitchManager.m
Normal 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
18
React/Views/RCTTabBar.h
Normal 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
145
React/Views/RCTTabBar.m
Normal 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
|
||||
18
React/Views/RCTTabBarItem.h
Normal file
18
React/Views/RCTTabBarItem.h
Normal 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
|
||||
90
React/Views/RCTTabBarItem.m
Normal file
90
React/Views/RCTTabBarItem.m
Normal 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
|
||||
14
React/Views/RCTTabBarItemManager.h
Normal file
14
React/Views/RCTTabBarItemManager.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTTabBarItemManager : RCTViewManager
|
||||
|
||||
@end
|
||||
32
React/Views/RCTTabBarItemManager.m
Normal file
32
React/Views/RCTTabBarItemManager.m
Normal 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
|
||||
14
React/Views/RCTTabBarManager.h
Normal file
14
React/Views/RCTTabBarManager.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTTabBarManager : RCTViewManager
|
||||
|
||||
@end
|
||||
24
React/Views/RCTTabBarManager.m
Normal file
24
React/Views/RCTTabBarManager.m
Normal 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
|
||||
22
React/Views/RCTTextField.h
Normal file
22
React/Views/RCTTextField.h
Normal 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
132
React/Views/RCTTextField.m
Normal 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
|
||||
15
React/Views/RCTTextFieldManager.h
Normal file
15
React/Views/RCTTextFieldManager.h
Normal 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
|
||||
|
||||
60
React/Views/RCTTextFieldManager.m
Normal file
60
React/Views/RCTTextFieldManager.m
Normal 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
|
||||
14
React/Views/RCTUIActivityIndicatorViewManager.h
Normal file
14
React/Views/RCTUIActivityIndicatorViewManager.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTUIActivityIndicatorViewManager : RCTViewManager
|
||||
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user