mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-28 20:25:33 +08:00
Updates from Wed 29 Apr
This commit is contained in:
@@ -31,6 +31,8 @@
|
||||
NSString *const RCTReloadNotification = @"RCTReloadNotification";
|
||||
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
|
||||
|
||||
dispatch_queue_t const RCTJSThread = nil;
|
||||
|
||||
/**
|
||||
* Must be kept in sync with `MessageQueue.js`.
|
||||
*/
|
||||
@@ -189,6 +191,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
||||
superclass = class_getSuperclass(superclass);
|
||||
}
|
||||
}
|
||||
free(classes);
|
||||
}
|
||||
|
||||
});
|
||||
@@ -353,7 +356,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
|
||||
#define RCT_CONVERT_CASE(_value, _type) \
|
||||
case _value: { \
|
||||
_type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \
|
||||
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
|
||||
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
|
||||
break; \
|
||||
}
|
||||
@@ -375,12 +378,27 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
RCT_CONVERT_CASE('B', BOOL)
|
||||
RCT_CONVERT_CASE('@', id)
|
||||
RCT_CONVERT_CASE('^', void *)
|
||||
case '{':
|
||||
RCTAssert(NO, @"Argument %zd of %C[%@ %@] is defined as %@, however RCT_EXPORT_METHOD() "
|
||||
"does not currently support struct-type arguments.", i - 2,
|
||||
[reactMethodName characterAtIndex:0], _moduleClassName,
|
||||
objCMethodName, argumentName);
|
||||
break;
|
||||
|
||||
case '{': {
|
||||
[argumentBlocks addObject:^(RCTBridge *bridge, NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) {
|
||||
NSUInteger size;
|
||||
NSGetSizeAndAlignment(argumentType, &size, NULL);
|
||||
void *returnValue = malloc(size);
|
||||
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
|
||||
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
|
||||
[_invocation setTarget:[RCTConvert class]];
|
||||
[_invocation setSelector:selector];
|
||||
[_invocation setArgument:&json atIndex:2];
|
||||
[_invocation invoke];
|
||||
[_invocation getReturnValue:returnValue];
|
||||
|
||||
[invocation setArgument:returnValue atIndex:index];
|
||||
|
||||
free(returnValue);
|
||||
}];
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
defaultCase(argumentType);
|
||||
}
|
||||
@@ -436,6 +454,10 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
||||
RCT_SIMPLE_CASE('d', double, doubleValue)
|
||||
RCT_SIMPLE_CASE('B', BOOL, boolValue)
|
||||
|
||||
case '{':
|
||||
RCTLogMustFix(@"Cannot convert JSON to struct %s", argumentType);
|
||||
break;
|
||||
|
||||
default:
|
||||
defaultCase(argumentType);
|
||||
}
|
||||
@@ -650,6 +672,8 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
|
||||
*/
|
||||
static NSMutableDictionary *RCTLocalModuleIDs;
|
||||
static NSMutableDictionary *RCTLocalMethodIDs;
|
||||
static NSMutableArray *RCTLocalModuleNames;
|
||||
static NSMutableArray *RCTLocalMethodNames;
|
||||
static NSDictionary *RCTLocalModulesConfig()
|
||||
{
|
||||
static NSMutableDictionary *localModules;
|
||||
@@ -658,6 +682,8 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||
|
||||
RCTLocalModuleIDs = [[NSMutableDictionary alloc] init];
|
||||
RCTLocalMethodIDs = [[NSMutableDictionary alloc] init];
|
||||
RCTLocalModuleNames = [[NSMutableArray alloc] init];
|
||||
RCTLocalMethodNames = [[NSMutableArray alloc] init];
|
||||
|
||||
localModules = [[NSMutableDictionary alloc] init];
|
||||
for (NSString *moduleDotMethod in RCTJSMethods()) {
|
||||
@@ -689,6 +715,8 @@ static NSDictionary *RCTLocalModulesConfig()
|
||||
// Add module and method lookup
|
||||
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
|
||||
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
|
||||
[RCTLocalModuleNames addObject:moduleName];
|
||||
[RCTLocalMethodNames addObject:methodName];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -795,6 +823,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
_bundleURL = bundleURL;
|
||||
_moduleProvider = block;
|
||||
_launchOptions = [launchOptions copy];
|
||||
|
||||
[self setUp];
|
||||
[self bindKeys];
|
||||
}
|
||||
@@ -872,6 +901,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
dispatch_queue_t queue = [module methodQueue];
|
||||
if (queue) {
|
||||
_queuesByID[moduleID] = queue;
|
||||
} else {
|
||||
_queuesByID[moduleID] = [NSNull null];
|
||||
}
|
||||
}
|
||||
}];
|
||||
@@ -1047,7 +1078,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
#pragma mark - RCTBridge methods
|
||||
|
||||
/**
|
||||
* Like JS::call, for objective-c.
|
||||
* Public. Can be invoked from any thread.
|
||||
*/
|
||||
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
|
||||
{
|
||||
@@ -1058,12 +1089,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
|
||||
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
|
||||
|
||||
if (!_loading) {
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, args ?: @[]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, args ?: @[]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1081,10 +1110,17 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
|
||||
if (!_loading) {
|
||||
#if BATCHED_BRIDGE
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
dispatch_block_t block = ^{
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
};
|
||||
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
|
||||
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
|
||||
} else {
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
@@ -1128,33 +1164,93 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
|
||||
#pragma mark - Payload Generation
|
||||
|
||||
- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID
|
||||
{
|
||||
id queue = _queuesByID[moduleID];
|
||||
if (queue == [NSNull null]) {
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
|
||||
} else {
|
||||
dispatch_async(queue ?: _methodQueue, block);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by enqueueJSCall from any thread, or from _immediatelyCallTimer,
|
||||
* on the JS thread, but only in non-batched mode.
|
||||
*/
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
{
|
||||
#if BATCHED_BRIDGE
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
if ([module isEqualToString:@"RCTEventEmitter"]) {
|
||||
for (NSDictionary *call in _scheduledCalls) {
|
||||
if ([call[@"module"] isEqualToString:module] && [call[@"method"] isEqualToString:method] && [call[@"args"][0] isEqualToString:args[0]]) {
|
||||
[_scheduledCalls removeObject:call];
|
||||
__weak NSMutableArray *weakScheduledCalls = _scheduledCalls;
|
||||
__weak RCTSparseArray *weakScheduledCallbacks = _scheduledCallbacks;
|
||||
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
NSMutableArray *scheduledCalls = weakScheduledCalls;
|
||||
RCTSparseArray *scheduledCallbacks = weakScheduledCallbacks;
|
||||
if (!scheduledCalls || !scheduledCallbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event deduping
|
||||
*
|
||||
* Right now we make a lot of assumptions about the arguments structure
|
||||
* so just iterate if it's a `callFunctionReturnFlushedQueue()`
|
||||
*/
|
||||
if ([method isEqualToString:@"callFunctionReturnFlushedQueue"]) {
|
||||
NSString *moduleName = RCTLocalModuleNames[[args[0] integerValue]];
|
||||
/**
|
||||
* Keep going if it any event emmiter, e.g. RCT(Device|NativeApp)?EventEmitter
|
||||
*/
|
||||
if ([moduleName hasSuffix:@"EventEmitter"]) {
|
||||
for (NSDictionary *call in [scheduledCalls copy]) {
|
||||
NSArray *callArgs = call[@"args"];
|
||||
/**
|
||||
* If it's the same module && method call on the bridge &&
|
||||
* the same EventEmitter module && method
|
||||
*/
|
||||
if (
|
||||
[call[@"module"] isEqualToString:module] &&
|
||||
[call[@"method"] isEqualToString:method] &&
|
||||
[callArgs[0] isEqual:args[0]] &&
|
||||
[callArgs[1] isEqual:args[1]]
|
||||
) {
|
||||
/**
|
||||
* args[2] contains the actual arguments for the event call, where
|
||||
* args[2][0] is the target for RCTEventEmitter or the eventName
|
||||
* for the other EventEmitters
|
||||
* if RCTEventEmitter we need to compare args[2][1] that will be
|
||||
* the eventName
|
||||
*/
|
||||
if (
|
||||
[args[2][0] isEqual:callArgs[2][0]] &&
|
||||
([moduleName isEqualToString:@"RCTEventEmitter"] ? [args[2][1] isEqual:callArgs[2][1]] : YES)
|
||||
) {
|
||||
[scheduledCalls removeObject:call];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id call = @{
|
||||
@"module": module,
|
||||
@"method": method,
|
||||
@"args": args,
|
||||
@"context": context ?: @0,
|
||||
};
|
||||
id call = @{
|
||||
@"module": module,
|
||||
@"method": method,
|
||||
@"args": args,
|
||||
@"context": context ?: @0,
|
||||
};
|
||||
|
||||
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||
_scheduledCallbacks[args[0]] = call;
|
||||
} else {
|
||||
[_scheduledCalls addObject:call];
|
||||
}
|
||||
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||
scheduledCallbacks[args[0]] = call;
|
||||
} else {
|
||||
[scheduledCalls addObject:call];
|
||||
}
|
||||
|
||||
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
|
||||
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
@@ -1235,10 +1331,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
|
||||
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
|
||||
if ([module respondsToSelector:@selector(batchDidComplete)]) {
|
||||
dispatch_queue_t queue = _queuesByID[moduleID];
|
||||
dispatch_async(queue ?: _methodQueue, ^{
|
||||
[self dispatchBlock:^{
|
||||
[module batchDidComplete];
|
||||
});
|
||||
} forModule:moduleID];
|
||||
}
|
||||
}];
|
||||
}
|
||||
@@ -1273,8 +1368,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
}
|
||||
|
||||
__weak RCTBridge *weakSelf = self;
|
||||
dispatch_queue_t queue = _queuesByID[moduleID];
|
||||
dispatch_async(queue ?: _methodQueue, ^{
|
||||
[self dispatchBlock:^{
|
||||
RCTProfileBeginEvent();
|
||||
__strong RCTBridge *strongSelf = weakSelf;
|
||||
|
||||
@@ -1303,7 +1397,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||
@"method": method.JSMethodName,
|
||||
@"selector": NSStringFromSelector(method.selector),
|
||||
});
|
||||
});
|
||||
} forModule:@(moduleID)];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,16 @@
|
||||
*/
|
||||
typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
||||
|
||||
/**
|
||||
* This constant can be returned from +methodQueue to force module
|
||||
* methods to be called on the JavaScript thread. This can have serious
|
||||
* implications for performance, so only use this if you're sure it's what
|
||||
* you need.
|
||||
*
|
||||
* NOTE: RCTJSThread is not a real libdispatch queue
|
||||
*/
|
||||
extern const dispatch_queue_t RCTJSThread;
|
||||
|
||||
/**
|
||||
* Provides the interface needed to register a bridge module.
|
||||
*/
|
||||
|
||||
@@ -49,11 +49,11 @@ RCT_CONVERTER(NSString *, NSString, description)
|
||||
});
|
||||
NSNumber *number = [formatter numberFromString:json];
|
||||
if (!number) {
|
||||
RCTLogError(@"JSON String '%@' could not be interpreted as a number", json);
|
||||
RCTLogConvertError(json, "a number");
|
||||
}
|
||||
return number;
|
||||
} else if (json && json != [NSNull null]) {
|
||||
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json classForCoder]);
|
||||
RCTLogConvertError(json, "a number");
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@@ -66,30 +66,38 @@ RCT_CONVERTER(NSString *, NSString, description)
|
||||
|
||||
+ (NSURL *)NSURL:(id)json
|
||||
{
|
||||
if (!json || json == (id)kCFNull) {
|
||||
NSString *path = [self NSString:json];
|
||||
if (!path.length) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json);
|
||||
return nil;
|
||||
}
|
||||
@try { // NSURL has a history of crashing with bad input, so let's be safe
|
||||
|
||||
NSString *path = json;
|
||||
if ([path isAbsolutePath])
|
||||
{
|
||||
NSURL *URL = [NSURL URLWithString:path];
|
||||
if (URL.scheme) { // Was a well-formed absolute URL
|
||||
return URL;
|
||||
}
|
||||
|
||||
// Check if it has a scheme
|
||||
if ([path rangeOfString:@"[a-zA-Z][a-zA-Z._-]+:" options:NSRegularExpressionSearch].location == 0) {
|
||||
path = [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
URL = [NSURL URLWithString:path];
|
||||
if (URL) {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
|
||||
// Assume that it's a local path
|
||||
path = [path stringByRemovingPercentEncoding];
|
||||
if (![path isAbsolutePath]) {
|
||||
path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:path];
|
||||
}
|
||||
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;
|
||||
@catch (__unused NSException *e) {
|
||||
RCTLogConvertError(json, "a valid URL");
|
||||
return nil;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSURLRequest *)NSURLRequest:(id)json
|
||||
@@ -112,11 +120,12 @@ RCT_CONVERTER(NSString *, NSString, description)
|
||||
});
|
||||
NSDate *date = [formatter dateFromString:json];
|
||||
if (!date) {
|
||||
RCTLogError(@"JSON String '%@' could not be interpreted as a date. Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
|
||||
RCTLogError(@"JSON String '%@' could not be interpreted as a date. "
|
||||
"Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
|
||||
}
|
||||
return date;
|
||||
} else if (json && json != [NSNull null]) {
|
||||
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a date", json, [json classForCoder]);
|
||||
RCTLogConvertError(json, "a date");
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@@ -698,50 +707,65 @@ static BOOL RCTFontIsCondensed(UIFont *font)
|
||||
const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular;
|
||||
const CGFloat RCTDefaultFontSize = 14;
|
||||
|
||||
// Get existing properties
|
||||
// Initialize properties to defaults
|
||||
CGFloat fontSize = RCTDefaultFontSize;
|
||||
RCTFontWeight fontWeight = RCTDefaultFontWeight;
|
||||
NSString *familyName = RCTDefaultFontFamily;
|
||||
BOOL isItalic = NO;
|
||||
BOOL isCondensed = NO;
|
||||
RCTFontWeight fontWeight = RCTDefaultFontWeight;
|
||||
|
||||
if (font) {
|
||||
family = font.familyName;
|
||||
familyName = font.familyName ?: RCTDefaultFontFamily;
|
||||
fontSize = font.pointSize ?: RCTDefaultFontSize;
|
||||
fontWeight = RCTWeightOfFont(font);
|
||||
isItalic = RCTFontIsItalic(font);
|
||||
isCondensed = RCTFontIsCondensed(font);
|
||||
}
|
||||
|
||||
// Get font size
|
||||
fontSize = [self CGFloat:size] ?: fontSize;
|
||||
|
||||
// Get font family
|
||||
familyName = [self NSString:family] ?: familyName;
|
||||
|
||||
// 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 font weight
|
||||
if (weight) {
|
||||
fontWeight = [self RCTFontWeight:weight];
|
||||
}
|
||||
|
||||
// Get closest match
|
||||
UIFont *bestMatch = font;
|
||||
CGFloat closestWeight = font ? RCTWeightOfFont(font) : INFINITY;
|
||||
// Gracefully handle being given a font name rather than font family, for
|
||||
// example: "Helvetica Light Oblique" rather than just "Helvetica".
|
||||
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;
|
||||
fontWeight = RCTWeightOfFont(font);
|
||||
isItalic = RCTFontIsItalic(font);
|
||||
isCondensed = RCTFontIsCondensed(font);
|
||||
} else {
|
||||
// Not a valid font or family
|
||||
RCTLogError(@"Unrecognized font family '%@'", familyName);
|
||||
familyName = RCTDefaultFontFamily;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the closest font that matches the given weight for the fontFamily
|
||||
UIFont *bestMatch = [UIFont fontWithName:font.fontName size: fontSize];
|
||||
CGFloat closestWeight;
|
||||
|
||||
if (font && [font.familyName isEqualToString: familyName]) {
|
||||
closestWeight = RCTWeightOfFont(font);
|
||||
} else {
|
||||
closestWeight = INFINITY;
|
||||
}
|
||||
|
||||
for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) {
|
||||
UIFont *match = [UIFont fontWithName:name size:fontSize];
|
||||
if (isItalic == RCTFontIsItalic(match) &&
|
||||
|
||||
@@ -50,7 +50,7 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
|
||||
|
||||
/**
|
||||
* Send a user input event. The body dictionary must contain a "target"
|
||||
* parameter, representing the react tag of the view sending the event
|
||||
* parameter, representing the React tag of the view sending the event
|
||||
*/
|
||||
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent);
|
||||
- (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");
|
||||
@"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]];
|
||||
|
||||
@@ -49,6 +49,15 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
|
||||
*/
|
||||
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Special case for Timers + ContextExecutor - instead of the default
|
||||
* if jsthread then call else dispatch call on jsthread
|
||||
* ensure the call is made async on the jsthread
|
||||
*/
|
||||
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block;
|
||||
|
||||
@end
|
||||
|
||||
static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";
|
||||
|
||||
@@ -10,47 +10,15 @@
|
||||
#import "RCTJavaScriptLoader.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTSourceCode.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#define NO_REMOTE_MODULE @"Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading."
|
||||
#define NO_LOCAL_BUNDLE @"Could not load local bundle %@. Ensure file exists."
|
||||
|
||||
#define CACHE_DIR @"RCTJSBundleCache"
|
||||
|
||||
#pragma mark - Application Engine
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Add window resize rotation events matching the DOM API.
|
||||
* - Device pixel ration hooks.
|
||||
* - Source maps.
|
||||
*/
|
||||
@implementation RCTJavaScriptLoader
|
||||
{
|
||||
__weak RCTBridge *_bridge;
|
||||
}
|
||||
|
||||
/**
|
||||
* `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore
|
||||
* engine in its own dedicated thread.
|
||||
*
|
||||
* TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one
|
||||
* additional GCD dispatch per frame and likely makes it so that other UIThread
|
||||
* operations don't delay the dispatch (so we can begin working in JS much
|
||||
* faster.) Event handling must still be sent via a GCD dispatch, of course.
|
||||
*
|
||||
* We must add the display link to two runloops in order to get setTimeouts to
|
||||
* fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`)
|
||||
* TODO: We can invent a `requestAnimationFrame` and
|
||||
* `requestAvailableAnimationFrame` to control if callbacks can be fired during
|
||||
* an animation.
|
||||
* http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink
|
||||
*
|
||||
*/
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
@@ -61,92 +29,86 @@
|
||||
|
||||
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
|
||||
{
|
||||
if (scriptURL == nil) {
|
||||
// Sanitize the script URL
|
||||
scriptURL = [RCTConvert NSURL:scriptURL.absoluteString];
|
||||
|
||||
if (!scriptURL ||
|
||||
([scriptURL isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:scriptURL.path])) {
|
||||
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"No script URL provided"
|
||||
NSLocalizedDescriptionKey: scriptURL ? [NSString stringWithFormat:@"Script at '%@' could not be found.", scriptURL] : @"No script URL provided"
|
||||
}];
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if ([scriptURL isFileURL]) {
|
||||
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
|
||||
NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length];
|
||||
|
||||
if (![localPath hasPrefix:bundlePath]) {
|
||||
NSString *absolutePath = [NSString stringWithFormat:@"%@/%@", bundlePath, localPath];
|
||||
scriptURL = [NSURL fileURLWithPath:absolutePath];
|
||||
}
|
||||
}
|
||||
|
||||
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler:
|
||||
^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
|
||||
// Handle general request errors
|
||||
if (error) {
|
||||
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
||||
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: desc,
|
||||
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
||||
NSUnderlyingErrorKey: error,
|
||||
};
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:error.code
|
||||
userInfo:userInfo];
|
||||
}
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
// Handle general request errors
|
||||
if (error) {
|
||||
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
||||
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: desc,
|
||||
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
||||
NSUnderlyingErrorKey: error,
|
||||
};
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:error.code
|
||||
userInfo:userInfo];
|
||||
}
|
||||
onComplete(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];
|
||||
// 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]] &&
|
||||
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
|
||||
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
|
||||
for (NSDictionary *err in errorDetails[@"errors"]) {
|
||||
[fakeStack addObject: @{
|
||||
@"methodName": err[@"description"] ?: @"",
|
||||
@"file": err[@"filename"] ?: @"",
|
||||
@"lineNumber": err[@"lineNumber"] ?: @0
|
||||
}];
|
||||
}
|
||||
userInfo = @{
|
||||
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
|
||||
@"stack": fakeStack,
|
||||
};
|
||||
} else {
|
||||
userInfo = @{NSLocalizedDescriptionKey: rawText};
|
||||
}
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:[(NSHTTPURLResponse *)response statusCode]
|
||||
userInfo:userInfo];
|
||||
// Handle HTTP errors
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
|
||||
NSDictionary *userInfo;
|
||||
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
|
||||
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
|
||||
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
|
||||
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
|
||||
for (NSDictionary *err in errorDetails[@"errors"]) {
|
||||
[fakeStack addObject: @{
|
||||
@"methodName": err[@"description"] ?: @"",
|
||||
@"file": err[@"filename"] ?: @"",
|
||||
@"lineNumber": err[@"lineNumber"] ?: @0
|
||||
}];
|
||||
}
|
||||
userInfo = @{
|
||||
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
|
||||
@"stack": fakeStack,
|
||||
};
|
||||
} else {
|
||||
userInfo = @{NSLocalizedDescriptionKey: rawText};
|
||||
}
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:[(NSHTTPURLResponse *)response statusCode]
|
||||
userInfo:userInfo];
|
||||
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||
sourceCodeModule.scriptURL = scriptURL;
|
||||
sourceCodeModule.scriptText = rawText;
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||
sourceCodeModule.scriptURL = scriptURL;
|
||||
sourceCodeModule.scriptText = rawText;
|
||||
|
||||
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
onComplete(scriptError);
|
||||
});
|
||||
}];
|
||||
}];
|
||||
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
onComplete(scriptError);
|
||||
});
|
||||
}];
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
||||
@@ -23,16 +23,6 @@
|
||||
#import "RCTWebViewExecutor.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
/**
|
||||
* HACK(t6568049) This should be removed soon, hiding to prevent people from
|
||||
* relying on it
|
||||
*/
|
||||
@interface RCTBridge (RCTRootView)
|
||||
|
||||
- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTUIManager (RCTRootView)
|
||||
|
||||
- (NSNumber *)allocateRootTag;
|
||||
@@ -120,11 +110,11 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
/**
|
||||
* Every root view that is created must have a unique react tag.
|
||||
* Every root view that is created must have a unique React tag.
|
||||
* Numbering of these tags goes from 1, 11, 21, 31, etc
|
||||
*
|
||||
* NOTE: Since the bridge persists, the RootViews might be reused, so now
|
||||
* the react tag is assigned every time we load new content.
|
||||
* the React tag is assigned every time we load new content.
|
||||
*/
|
||||
[_contentView removeFromSuperview];
|
||||
_contentView = [[UIView alloc] initWithFrame:self.bounds];
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
/**
|
||||
* Arrays managed in parallel tracking native touch object along with the
|
||||
* native view that was touched, and the react touch data dictionary.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -46,3 +46,6 @@ RCT_EXTERN BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector);
|
||||
// TODO(#6472857): create NSErrors and automatically convert them over the bridge.
|
||||
RCT_EXTERN NSDictionary *RCTMakeError(NSString *message, id toStringify, NSDictionary *extraData);
|
||||
RCT_EXTERN NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary *extraData);
|
||||
|
||||
// Returns YES if React is running in a test environment
|
||||
RCT_EXTERN BOOL RCTRunningInTestEnvironment(void);
|
||||
|
||||
@@ -35,8 +35,7 @@ id RCTJSONParse(NSString *jsonString, NSError **error)
|
||||
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);
|
||||
RCTLogError(@"RCTJSONParse received invalid UTF8 data");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
@@ -201,3 +200,13 @@ NSDictionary *RCTMakeAndLogError(NSString *message, id toStringify, NSDictionary
|
||||
RCTLogError(@"\nError: %@", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
BOOL RCTRunningInTestEnvironment(void)
|
||||
{
|
||||
static BOOL _isTestEnvironment = NO;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_isTestEnvironment = (NSClassFromString(@"SenTestCase") != nil || NSClassFromString(@"XCTest") != nil);
|
||||
});
|
||||
return _isTestEnvironment;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,6 @@
|
||||
* You probably don't want to use this; use -init instead.
|
||||
*/
|
||||
- (instancetype)initWithJavaScriptThread:(NSThread *)javaScriptThread
|
||||
globalContextRef:(JSGlobalContextRef)context;
|
||||
globalContextRef:(JSGlobalContextRef)context NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
||||
|
||||
@@ -55,6 +55,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CFRunLoopStop([[NSRunLoop currentRunLoop] getCFRunLoop]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTContextExecutor
|
||||
@@ -74,12 +79,12 @@
|
||||
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) {
|
||||
JSStringRef messageRef = JSValueToStringCopy(context, arguments[0], exception);
|
||||
if (!messageRef) {
|
||||
return JSValueMakeUndefined(context);
|
||||
}
|
||||
NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string);
|
||||
JSStringRelease(string);
|
||||
NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, messageRef);
|
||||
JSStringRelease(messageRef);
|
||||
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
|
||||
@@ -89,14 +94,11 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object,
|
||||
range:(NSRange){0, message.length}
|
||||
withTemplate:@"[$4$5] \t$2"];
|
||||
|
||||
// TODO: it would be good if log level was sent as a param, instead of this hack
|
||||
RCTLogLevel level = RCTLogLevelInfo;
|
||||
if ([message rangeOfString:@"error" options:NSCaseInsensitiveSearch].length) {
|
||||
level = RCTLogLevelError;
|
||||
} else if ([message rangeOfString:@"warning" options:NSCaseInsensitiveSearch].length) {
|
||||
level = RCTLogLevelWarning;
|
||||
if (argumentCount > 1) {
|
||||
level = MAX(level, JSValueToNumber(context, arguments[1], exception) - 1);
|
||||
}
|
||||
_RCTLogFormat(level, NULL, -1, @"%@", message);
|
||||
RCTGetLogFunction()(level, nil, nil, message);
|
||||
}
|
||||
|
||||
return JSValueMakeUndefined(context);
|
||||
@@ -156,15 +158,12 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
|
||||
- (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];
|
||||
});
|
||||
NSThread *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];
|
||||
}
|
||||
@@ -172,6 +171,9 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
- (instancetype)initWithJavaScriptThread:(NSThread *)javaScriptThread
|
||||
globalContextRef:(JSGlobalContextRef)context
|
||||
{
|
||||
RCTAssert(javaScriptThread != nil,
|
||||
@"Can't initialize RCTContextExecutor without a javaScriptThread");
|
||||
|
||||
if ((self = [super init])) {
|
||||
_javaScriptThread = javaScriptThread;
|
||||
__weak RCTContextExecutor *weakSelf = self;
|
||||
@@ -305,17 +307,30 @@ static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
|
||||
}
|
||||
onComplete(error);
|
||||
}
|
||||
}), @"js_call", (@{ @"url": sourceURL }))];
|
||||
}), @"js_call", (@{ @"url": sourceURL.absoluteString }))];
|
||||
}
|
||||
|
||||
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
|
||||
{
|
||||
if ([NSThread currentThread] != _javaScriptThread) {
|
||||
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
|
||||
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
|
||||
} else {
|
||||
block();
|
||||
}
|
||||
if ([NSThread currentThread] != _javaScriptThread) {
|
||||
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
|
||||
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
|
||||
} else {
|
||||
block();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block
|
||||
{
|
||||
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
|
||||
onThread:_javaScriptThread
|
||||
withObject:block
|
||||
waitUntilDone:NO];
|
||||
}
|
||||
|
||||
- (void)_runBlock:(dispatch_block_t)block
|
||||
{
|
||||
block();
|
||||
}
|
||||
|
||||
- (void)injectJSONText:(NSString *)script
|
||||
|
||||
@@ -110,7 +110,7 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
return RCTJSThread;
|
||||
}
|
||||
|
||||
- (BOOL)isValid
|
||||
@@ -131,8 +131,6 @@ RCT_IMPORT_METHOD(RCTJSTimers, callTimers)
|
||||
|
||||
- (void)startTimers
|
||||
{
|
||||
RCTAssertMainThread();
|
||||
|
||||
if (![self isValid] || _timers.count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -661,7 +661,7 @@ RCT_EXPORT_METHOD(manageChildren:(NSNumber *)containerReactTag
|
||||
{
|
||||
id<RCTViewNodeProtocol> container = registry[containerReactTag];
|
||||
RCTAssert(moveFromIndices.count == moveToIndices.count, @"moveFromIndices had size %tu, moveToIndices had size %tu", moveFromIndices.count, moveToIndices.count);
|
||||
RCTAssert(addChildReactTags.count == addAtIndices.count, @"there should be at least one react child to add");
|
||||
RCTAssert(addChildReactTags.count == addAtIndices.count, @"there should be at least one React child to add");
|
||||
|
||||
// Removes (both permanent and temporary moves) are using "before" indices
|
||||
NSArray *permanentlyRemovedChildren = [self _childrenToRemoveFromContainer:container atIndices:removeAtIndices];
|
||||
@@ -918,7 +918,7 @@ RCT_EXPORT_METHOD(measure:(NSNumber *)reactTag
|
||||
}
|
||||
|
||||
// TODO: this doesn't work because sometimes view is inside a modal window
|
||||
// RCTAssert([rootView isReactRootView], @"React view is not inside a react root view");
|
||||
// RCTAssert([rootView isReactRootView], @"React view is not inside a React root view");
|
||||
|
||||
// By convention, all coordinates, whether they be touch coordinates, or
|
||||
// measurement coordinates are with respect to the root view.
|
||||
@@ -995,18 +995,17 @@ RCT_EXPORT_METHOD(measureLayoutRelativeToParent:(NSNumber *)reactTag
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of computed offset layouts in a dictionary form. The layouts are of any react subviews
|
||||
* Returns an array of computed offset layouts in a dictionary form. The layouts are of any React subviews
|
||||
* that are immediate descendants to the parent view found within a specified rect. The dictionary result
|
||||
* contains left, top, width, height and an index. The index specifies the position among the other subviews.
|
||||
* Only layouts for views that are within the rect passed in are returned. Invokes the error callback if the
|
||||
* passed in parent view does not exist. Invokes the supplied callback with the array of computed layouts.
|
||||
*/
|
||||
RCT_EXPORT_METHOD(measureViewsInRect:(id)rectJSON
|
||||
RCT_EXPORT_METHOD(measureViewsInRect:(CGRect)rect
|
||||
parentView:(NSNumber *)reactTag
|
||||
errorCallback:(RCTResponseSenderBlock)errorCallback
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
CGRect rect = [RCTConvert CGRect:rectJSON];
|
||||
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
|
||||
if (!shadowView) {
|
||||
RCTLogError(@"Attempting to measure view that does not exist (tag #%@)", reactTag);
|
||||
@@ -1102,9 +1101,8 @@ RCT_EXPORT_METHOD(scrollWithoutAnimationTo:(NSNumber *)reactTag
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(zoomToRect:(NSNumber *)reactTag
|
||||
withRect:(id)rectJSON)
|
||||
withRect:(CGRect)rect)
|
||||
{
|
||||
CGRect rect = [RCTConvert CGRect:rectJSON];
|
||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||
UIView *view = viewRegistry[reactTag];
|
||||
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
/* 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 */; };
|
||||
131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */; };
|
||||
131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */; };
|
||||
13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */; };
|
||||
13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */; };
|
||||
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; };
|
||||
@@ -33,7 +35,7 @@
|
||||
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 */; };
|
||||
13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.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 */; };
|
||||
@@ -84,6 +86,10 @@
|
||||
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>"; };
|
||||
131B6AF01AF1093D00FFC3E0 /* RCTSegmentedControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSegmentedControl.h; sourceTree = "<group>"; };
|
||||
131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControl.m; sourceTree = "<group>"; };
|
||||
131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSegmentedControlManager.h; sourceTree = "<group>"; };
|
||||
131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControlManager.m; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -136,8 +142,8 @@
|
||||
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>"; };
|
||||
13B080181A69489C00A75B9A /* RCTActivityIndicatorViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTActivityIndicatorViewManager.h; sourceTree = "<group>"; };
|
||||
13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActivityIndicatorViewManager.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>"; };
|
||||
@@ -290,6 +296,10 @@
|
||||
58114A141AAE854800E7D092 /* RCTPickerManager.h */,
|
||||
58114A151AAE854800E7D092 /* RCTPickerManager.m */,
|
||||
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */,
|
||||
131B6AF01AF1093D00FFC3E0 /* RCTSegmentedControl.h */,
|
||||
131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */,
|
||||
131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */,
|
||||
131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */,
|
||||
13B07FF61A6947C200A75B9A /* RCTScrollView.h */,
|
||||
13B07FF71A6947C200A75B9A /* RCTScrollView.m */,
|
||||
13B07FF81A6947C200A75B9A /* RCTScrollViewManager.h */,
|
||||
@@ -317,8 +327,8 @@
|
||||
13B080151A69489C00A75B9A /* RCTTextField.m */,
|
||||
13B080161A69489C00A75B9A /* RCTTextFieldManager.h */,
|
||||
13B080171A69489C00A75B9A /* RCTTextFieldManager.m */,
|
||||
13B080181A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.h */,
|
||||
13B080191A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m */,
|
||||
13B080181A69489C00A75B9A /* RCTActivityIndicatorViewManager.h */,
|
||||
13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */,
|
||||
13E0674F1A70F44B002CDEE1 /* RCTView.h */,
|
||||
13E067501A70F44B002CDEE1 /* RCTView.m */,
|
||||
13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */,
|
||||
@@ -489,6 +499,7 @@
|
||||
13B0801E1A69489C00A75B9A /* RCTTextField.m in Sources */,
|
||||
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
|
||||
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,
|
||||
131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */,
|
||||
830A229E1A66C68A008503DA /* RCTRootView.m in Sources */,
|
||||
13B07FF01A69327A00A75B9A /* RCTExceptionsManager.m in Sources */,
|
||||
83CBBA5A1A601E9000E9B192 /* RCTRedBox.m in Sources */,
|
||||
@@ -499,7 +510,7 @@
|
||||
14F4D38B1AE1B7E40049C042 /* RCTProfile.m in Sources */,
|
||||
14F3620D1AABD06A001CE568 /* RCTSwitch.m in Sources */,
|
||||
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */,
|
||||
13B080201A69489C00A75B9A /* RCTUIActivityIndicatorViewManager.m in Sources */,
|
||||
13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */,
|
||||
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */,
|
||||
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */,
|
||||
13B080061A6947C200A75B9A /* RCTScrollViewManager.m in Sources */,
|
||||
@@ -529,6 +540,7 @@
|
||||
58114A161AAE854800E7D092 /* RCTPicker.m in Sources */,
|
||||
137327E81AA5CF210034F82E /* RCTTabBarItem.m in Sources */,
|
||||
13E067551A70F44B002CDEE1 /* RCTShadowView.m in Sources */,
|
||||
131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */,
|
||||
58114A171AAE854800E7D092 /* RCTPickerManager.m in Sources */,
|
||||
13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */,
|
||||
830BA4551A8E3BDA00D53203 /* RCTCache.m in Sources */,
|
||||
|
||||
20
React/Views/RCTActivityIndicatorViewManager.h
Normal file
20
React/Views/RCTActivityIndicatorViewManager.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 "RCTViewManager.h"
|
||||
|
||||
@interface RCTConvert (UIActivityIndicatorView)
|
||||
|
||||
+ (UIActivityIndicatorViewStyle)UIActivityIndicatorViewStyle:(id)json;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTActivityIndicatorViewManager : RCTViewManager
|
||||
|
||||
@end
|
||||
@@ -7,35 +7,37 @@
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTUIActivityIndicatorViewManager.h"
|
||||
#import "RCTActivityIndicatorViewManager.h"
|
||||
|
||||
#import "RCTConvert.h"
|
||||
|
||||
@implementation RCTConvert (UIActivityIndicatorView)
|
||||
|
||||
// NOTE: It's pointless to support UIActivityIndicatorViewStyleGray
|
||||
// as we can set the color to any arbitrary value that we want to
|
||||
|
||||
RCT_ENUM_CONVERTER(UIActivityIndicatorViewStyle, (@{
|
||||
@"white-large": @(UIActivityIndicatorViewStyleWhiteLarge),
|
||||
@"large-white": @(UIActivityIndicatorViewStyleWhiteLarge),
|
||||
@"white": @(UIActivityIndicatorViewStyleWhite),
|
||||
@"gray": @(UIActivityIndicatorViewStyleGray),
|
||||
@"large": @(UIActivityIndicatorViewStyleWhiteLarge),
|
||||
@"small": @(UIActivityIndicatorViewStyleWhite),
|
||||
}), UIActivityIndicatorViewStyleWhiteLarge, integerValue)
|
||||
|
||||
@end
|
||||
|
||||
@implementation RCTUIActivityIndicatorViewManager
|
||||
@implementation RCTActivityIndicatorViewManager
|
||||
|
||||
RCT_EXPORT_MODULE(UIActivityIndicatorViewManager)
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[UIActivityIndicatorView alloc] init];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(activityIndicatorViewStyle, UIActivityIndicatorViewStyle)
|
||||
RCT_EXPORT_VIEW_PROPERTY(color, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(hidesWhenStopped, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(size, activityIndicatorViewStyle, UIActivityIndicatorViewStyle)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(animating, BOOL, UIActivityIndicatorView)
|
||||
{
|
||||
BOOL animating = json ? [json boolValue] : [defaultView isAnimating];
|
||||
BOOL animating = json ? [RCTConvert BOOL:json] : [defaultView isAnimating];
|
||||
if (animating != [view isAnimating]) {
|
||||
if (animating) {
|
||||
[view startAnimating];
|
||||
@@ -45,14 +47,4 @@ RCT_CUSTOM_VIEW_PROPERTY(animating, BOOL, UIActivityIndicatorView)
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)constantsToExport
|
||||
{
|
||||
return
|
||||
@{
|
||||
@"StyleWhite": @(UIActivityIndicatorViewStyleWhite),
|
||||
@"StyleWhiteLarge": @(UIActivityIndicatorViewStyleWhiteLarge),
|
||||
@"StyleGray": @(UIActivityIndicatorViewStyleGray),
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -60,7 +60,7 @@ NSInteger kNeverProgressed = -10000;
|
||||
* 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
|
||||
* 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
|
||||
@@ -474,7 +474,7 @@ NSInteger kNeverProgressed = -10000;
|
||||
RCTLogError(@"Cannot adjust current top of stack beyond available views");
|
||||
}
|
||||
|
||||
// Views before the previous react count must not have changed. Views greater than previousReactCount
|
||||
// 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++) {
|
||||
if (_currentViews[i] != _previousViews[i]) {
|
||||
|
||||
20
React/Views/RCTSegmentedControl.h
Normal file
20
React/Views/RCTSegmentedControl.h
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// RCTSegmentedControl.h
|
||||
// React
|
||||
//
|
||||
// Created by Clay Allsopp on 3/31/15.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class RCTEventDispatcher;
|
||||
|
||||
@interface RCTSegmentedControl : UISegmentedControl
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@property (nonatomic, copy) NSArray *values;
|
||||
@property (nonatomic, assign) NSInteger selectedIndex;
|
||||
|
||||
@end
|
||||
57
React/Views/RCTSegmentedControl.m
Normal file
57
React/Views/RCTSegmentedControl.m
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// RCTSegmentedControl.m
|
||||
// React
|
||||
//
|
||||
// Created by Clay Allsopp on 3/31/15.
|
||||
// Copyright (c) 2015 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
#import "RCTSegmentedControl.h"
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
@implementation RCTSegmentedControl
|
||||
{
|
||||
RCTEventDispatcher *_eventDispatcher;
|
||||
}
|
||||
|
||||
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
||||
{
|
||||
if ((self = [super initWithFrame:CGRectZero])) {
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_selectedIndex = self.selectedSegmentIndex;
|
||||
[self addTarget:self action:@selector(onChange:)
|
||||
forControlEvents:UIControlEventValueChanged];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setValues:(NSArray *)values
|
||||
{
|
||||
_values = [values copy];
|
||||
[self removeAllSegments];
|
||||
for (NSString *value in values) {
|
||||
[self insertSegmentWithTitle:value atIndex:self.numberOfSegments animated:NO];
|
||||
}
|
||||
super.selectedSegmentIndex = _selectedIndex;
|
||||
}
|
||||
|
||||
- (void)setSelectedIndex:(NSInteger)selectedIndex
|
||||
{
|
||||
_selectedIndex = selectedIndex;
|
||||
super.selectedSegmentIndex = selectedIndex;
|
||||
}
|
||||
|
||||
- (void)onChange:(UISegmentedControl *)sender
|
||||
{
|
||||
NSDictionary *event = @{
|
||||
@"target": self.reactTag,
|
||||
@"value": [self titleForSegmentAtIndex:sender.selectedSegmentIndex],
|
||||
@"selectedSegmentIndex": @(sender.selectedSegmentIndex)
|
||||
};
|
||||
[_eventDispatcher sendInputEventWithName:@"topChange" body:event];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -9,6 +9,6 @@
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTUIActivityIndicatorViewManager : RCTViewManager
|
||||
@interface RCTSegmentedControlManager : RCTViewManager
|
||||
|
||||
@end
|
||||
39
React/Views/RCTSegmentedControlManager.m
Normal file
39
React/Views/RCTSegmentedControlManager.m
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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 "RCTSegmentedControlManager.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTSegmentedControl.h"
|
||||
|
||||
@implementation RCTSegmentedControlManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTSegmentedControl alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(values, NSStringArray)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
|
||||
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(momentary, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL)
|
||||
|
||||
- (NSDictionary *)constantsToExport
|
||||
{
|
||||
RCTSegmentedControl *view = [[RCTSegmentedControl alloc] init];
|
||||
return @{
|
||||
@"ComponentHeight": @(view.intrinsicContentSize.height),
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -17,6 +17,7 @@
|
||||
@property (nonatomic, assign) BOOL autoCorrect;
|
||||
@property (nonatomic, assign) BOOL selectTextOnFocus;
|
||||
@property (nonatomic, assign) UIEdgeInsets contentInset;
|
||||
@property (nonatomic, strong) UIColor *placeholderTextColor;
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
|
||||
@@ -42,6 +42,30 @@
|
||||
}
|
||||
}
|
||||
|
||||
static void RCTUpdatePlaceholder(RCTTextField *self)
|
||||
{
|
||||
if (self.placeholder.length > 0 && self.placeholderTextColor) {
|
||||
self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder
|
||||
attributes:@{
|
||||
NSForegroundColorAttributeName : self.placeholderTextColor
|
||||
}];
|
||||
} else if (self.placeholder.length) {
|
||||
self.attributedPlaceholder = [[NSAttributedString alloc] initWithString:self.placeholder];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor
|
||||
{
|
||||
_placeholderTextColor = placeholderTextColor;
|
||||
RCTUpdatePlaceholder(self);
|
||||
}
|
||||
|
||||
- (void)setPlaceholder:(NSString *)placeholder
|
||||
{
|
||||
super.placeholder = placeholder;
|
||||
RCTUpdatePlaceholder(self);
|
||||
}
|
||||
|
||||
- (NSArray *)reactSubviews
|
||||
{
|
||||
// TODO: do we support subviews of textfield in React?
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#import "RCTTextFieldManager.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTShadowView.h"
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTTextField.h"
|
||||
@@ -28,6 +27,7 @@ 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(placeholderTextColor, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(clearButtonMode, UITextFieldViewMode)
|
||||
RCT_REMAP_VIEW_PROPERTY(clearTextOnFocus, clearsOnBeginEditing, BOOL)
|
||||
|
||||
@@ -13,13 +13,6 @@
|
||||
|
||||
#import "RCTPointerEvents.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, RCTBorderSide) {
|
||||
RCTBorderSideTop,
|
||||
RCTBorderSideRight,
|
||||
RCTBorderSideBottom,
|
||||
RCTBorderSideLeft
|
||||
};
|
||||
|
||||
@protocol RCTAutoInsetsProtocol;
|
||||
|
||||
@interface RCTView : UIView
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
#import "RCTAutoInsetsProtocol.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
static const RCTBorderSide RCTBorderSideCount = 4;
|
||||
static void *RCTViewCornerRadiusKVOContext = &RCTViewCornerRadiusKVOContext;
|
||||
|
||||
static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
|
||||
{
|
||||
@@ -30,6 +31,10 @@ static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
|
||||
return nil;
|
||||
}
|
||||
|
||||
static BOOL RCTEllipseGetIntersectionsWithLine(CGRect ellipseBoundingRect, CGPoint p1, CGPoint p2, CGPoint intersections[2]);
|
||||
static CGPathRef RCTPathCreateWithRoundedRect(CGRect rect, CGFloat topLeftRadiusX, CGFloat topLeftRadiusY, CGFloat topRightRadiusX, CGFloat topRightRadiusY, CGFloat bottomLeftRadiusX, CGFloat bottomLeftRadiusY, CGFloat bottomRightRadiusX, CGFloat bottomRightRadiusY, const CGAffineTransform *transform);
|
||||
static void RCTPathAddEllipticArc(CGMutablePathRef path, const CGAffineTransform *m, CGFloat x, CGFloat y, CGFloat xRadius, CGFloat yRadius, CGFloat startAngle, CGFloat endAngle, bool clockwise);
|
||||
|
||||
@implementation UIView (RCTViewUnmounting)
|
||||
|
||||
- (void)react_remountAllSubviews
|
||||
@@ -107,8 +112,39 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||
@implementation RCTView
|
||||
{
|
||||
NSMutableArray *_reactSubviews;
|
||||
CAShapeLayer *_borderLayers[RCTBorderSideCount];
|
||||
CGFloat _borderWidths[RCTBorderSideCount];
|
||||
UIColor *_backgroundColor;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if ((self = [super initWithFrame:frame])) {
|
||||
_borderWidth = -1;
|
||||
_borderTopWidth = -1;
|
||||
_borderRightWidth = -1;
|
||||
_borderBottomWidth = -1;
|
||||
_borderLeftWidth = -1;
|
||||
|
||||
_backgroundColor = [super backgroundColor];
|
||||
[super setBackgroundColor:[UIColor clearColor]];
|
||||
|
||||
[self.layer addObserver:self forKeyPath:@"cornerRadius" options:0 context:RCTViewCornerRadiusKVOContext];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self.layer removeObserver:self forKeyPath:@"cornerRadius" context:RCTViewCornerRadiusKVOContext];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||
{
|
||||
if (context == RCTViewCornerRadiusKVOContext) {
|
||||
[self.layer setNeedsDisplay];
|
||||
} else {
|
||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabel
|
||||
@@ -381,189 +417,353 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
||||
if (_reactSubviews) {
|
||||
[self updateClippedSubviews];
|
||||
}
|
||||
|
||||
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
|
||||
if (_borderLayers[side]) [self updatePathForShapeLayerForSide:side];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSublayersOfLayer:(CALayer *)layer
|
||||
{
|
||||
[super layoutSublayersOfLayer:layer];
|
||||
#pragma mark - Borders
|
||||
|
||||
const CGRect bounds = layer.bounds;
|
||||
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
|
||||
_borderLayers[side].frame = bounds;
|
||||
}
|
||||
- (UIColor *)backgroundColor
|
||||
{
|
||||
return _backgroundColor;
|
||||
}
|
||||
|
||||
- (BOOL)getTrapezoidPoints:(CGPoint[4])outPoints forSide:(RCTBorderSide)side
|
||||
- (void)setBackgroundColor:(UIColor *)backgroundColor
|
||||
{
|
||||
const CGRect bounds = self.layer.bounds;
|
||||
const CGFloat minX = CGRectGetMinX(bounds);
|
||||
const CGFloat maxX = CGRectGetMaxX(bounds);
|
||||
const CGFloat minY = CGRectGetMinY(bounds);
|
||||
const CGFloat maxY = CGRectGetMaxY(bounds);
|
||||
|
||||
#define BW(SIDE) [self borderWidthForSide:RCTBorderSide##SIDE]
|
||||
|
||||
switch (side) {
|
||||
case RCTBorderSideRight:
|
||||
outPoints[0] = CGPointMake(maxX - BW(Right), maxY - BW(Bottom));
|
||||
outPoints[1] = CGPointMake(maxX - BW(Right), minY + BW(Top));
|
||||
outPoints[2] = CGPointMake(maxX, minY);
|
||||
outPoints[3] = CGPointMake(maxX, maxY);
|
||||
break;
|
||||
case RCTBorderSideBottom:
|
||||
outPoints[0] = CGPointMake(minX + BW(Left), maxY - BW(Bottom));
|
||||
outPoints[1] = CGPointMake(maxX - BW(Right), maxY - BW(Bottom));
|
||||
outPoints[2] = CGPointMake(maxX, maxY);
|
||||
outPoints[3] = CGPointMake(minX, maxY);
|
||||
break;
|
||||
case RCTBorderSideLeft:
|
||||
outPoints[0] = CGPointMake(minX + BW(Left), minY + BW(Top));
|
||||
outPoints[1] = CGPointMake(minX + BW(Left), maxY - BW(Bottom));
|
||||
outPoints[2] = CGPointMake(minX, maxY);
|
||||
outPoints[3] = CGPointMake(minX, minY);
|
||||
break;
|
||||
case RCTBorderSideTop:
|
||||
outPoints[0] = CGPointMake(maxX - BW(Right), minY + BW(Top));
|
||||
outPoints[1] = CGPointMake(minX + BW(Left), minY + BW(Top));
|
||||
outPoints[2] = CGPointMake(minX, minY);
|
||||
outPoints[3] = CGPointMake(maxX, minY);
|
||||
break;
|
||||
if ([_backgroundColor isEqual:backgroundColor]) {
|
||||
return;
|
||||
}
|
||||
|
||||
return YES;
|
||||
_backgroundColor = backgroundColor;
|
||||
[self.layer setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (CAShapeLayer *)createShapeLayerIfNotExistsForSide:(RCTBorderSide)side
|
||||
- (UIImage *)generateBorderImage:(out CGRect *)contentsCenter
|
||||
{
|
||||
CAShapeLayer *borderLayer = _borderLayers[side];
|
||||
if (!borderLayer) {
|
||||
borderLayer = [CAShapeLayer layer];
|
||||
borderLayer.fillColor = self.layer.borderColor;
|
||||
[self.layer addSublayer:borderLayer];
|
||||
_borderLayers[side] = borderLayer;
|
||||
const CGFloat maxRadius = MIN(self.bounds.size.height, self.bounds.size.width) / 2.0;
|
||||
const CGFloat radius = MAX(0, MIN(self.layer.cornerRadius, maxRadius));
|
||||
|
||||
const CGFloat borderWidth = MAX(0, _borderWidth);
|
||||
const CGFloat topWidth = _borderTopWidth >= 0 ? _borderTopWidth : borderWidth;
|
||||
const CGFloat rightWidth = _borderRightWidth >= 0 ? _borderRightWidth : borderWidth;
|
||||
const CGFloat bottomWidth = _borderBottomWidth >= 0 ? _borderBottomWidth : borderWidth;
|
||||
const CGFloat leftWidth = _borderLeftWidth >= 0 ? _borderLeftWidth : borderWidth;
|
||||
|
||||
const CGFloat topRadius = MAX(0, radius - topWidth);
|
||||
const CGFloat rightRadius = MAX(0, radius - rightWidth);
|
||||
const CGFloat bottomRadius = MAX(0, radius - bottomWidth);
|
||||
const CGFloat leftRadius = MAX(0, radius - leftWidth);
|
||||
|
||||
const UIEdgeInsets edgeInsets = UIEdgeInsetsMake(topWidth + topRadius, leftWidth + leftRadius, bottomWidth + bottomRadius, rightWidth + rightRadius);
|
||||
const CGSize size = CGSizeMake(edgeInsets.left + 1 + edgeInsets.right, edgeInsets.top + 1 + edgeInsets.bottom);
|
||||
|
||||
UIScreen *screen = self.window.screen ?: [UIScreen mainScreen];
|
||||
UIGraphicsBeginImageContextWithOptions(size, NO, screen.scale * 2);
|
||||
|
||||
CGContextRef ctx = UIGraphicsGetCurrentContext();
|
||||
const CGRect rect = {CGPointZero, size};
|
||||
CGPathRef path = CGPathCreateWithRoundedRect(rect, radius, radius, NULL);
|
||||
|
||||
if (_backgroundColor) {
|
||||
CGContextSaveGState(ctx);
|
||||
|
||||
CGContextAddPath(ctx, path);
|
||||
CGContextSetFillColorWithColor(ctx, _backgroundColor.CGColor);
|
||||
CGContextFillPath(ctx);
|
||||
|
||||
CGContextRestoreGState(ctx);
|
||||
}
|
||||
return borderLayer;
|
||||
}
|
||||
|
||||
- (void)updatePathForShapeLayerForSide:(RCTBorderSide)side
|
||||
{
|
||||
CAShapeLayer *borderLayer = [self createShapeLayerIfNotExistsForSide:side];
|
||||
|
||||
CGPoint trapezoidPoints[4];
|
||||
[self getTrapezoidPoints:trapezoidPoints forSide:side];
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGPathAddLines(path, NULL, trapezoidPoints, 4);
|
||||
CGPathCloseSubpath(path);
|
||||
borderLayer.path = path;
|
||||
CGContextAddPath(ctx, path);
|
||||
CGPathRelease(path);
|
||||
}
|
||||
|
||||
- (void)updateBorderLayers
|
||||
{
|
||||
BOOL widthsAndColorsSame = YES;
|
||||
CGFloat width = _borderWidths[0];
|
||||
CGColorRef color = _borderLayers[0].fillColor;
|
||||
for (RCTBorderSide side = 1; side < RCTBorderSideCount; side++) {
|
||||
CAShapeLayer *layer = _borderLayers[side];
|
||||
if (_borderWidths[side] != width || (layer && !CGColorEqualToColor(layer.fillColor, color))) {
|
||||
widthsAndColorsSame = NO;
|
||||
break;
|
||||
}
|
||||
if (radius > 0 && topWidth > 0 && rightWidth > 0 && bottomWidth > 0 && leftWidth > 0) {
|
||||
const UIEdgeInsets insetEdgeInsets = UIEdgeInsetsMake(topWidth, leftWidth, bottomWidth, rightWidth);
|
||||
const CGRect insetRect = UIEdgeInsetsInsetRect(rect, insetEdgeInsets);
|
||||
CGPathRef insetPath = RCTPathCreateWithRoundedRect(insetRect, leftRadius, topRadius, rightRadius, topRadius, leftRadius, bottomRadius, rightRadius, bottomRadius, NULL);
|
||||
CGContextAddPath(ctx, insetPath);
|
||||
CGPathRelease(insetPath);
|
||||
}
|
||||
if (widthsAndColorsSame) {
|
||||
|
||||
// Set main layer border
|
||||
if (width) {
|
||||
_borderWidth = self.layer.borderWidth = width;
|
||||
}
|
||||
if (color) {
|
||||
self.layer.borderColor = color;
|
||||
}
|
||||
|
||||
// Remove border layers
|
||||
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
|
||||
[_borderLayers[side] removeFromSuperlayer];
|
||||
_borderLayers[side] = nil;
|
||||
}
|
||||
CGContextEOClip(ctx);
|
||||
|
||||
BOOL hasEqualColor = !_borderTopColor && !_borderRightColor && !_borderBottomColor && !_borderLeftColor;
|
||||
BOOL hasEqualBorder = _borderWidth >= 0 && _borderTopWidth < 0 && _borderRightWidth < 0 && _borderBottomWidth < 0 && _borderLeftWidth < 0;
|
||||
if (radius <= 0 && hasEqualBorder && hasEqualColor) {
|
||||
CGContextSetStrokeColorWithColor(ctx, _borderColor);
|
||||
CGContextSetLineWidth(ctx, 2 * _borderWidth);
|
||||
CGContextClipToRect(ctx, rect);
|
||||
CGContextStrokeRect(ctx, rect);
|
||||
} else if (radius <= 0 && hasEqualColor) {
|
||||
CGContextSetFillColorWithColor(ctx, _borderColor);
|
||||
CGContextAddRect(ctx, rect);
|
||||
const CGRect insetRect = UIEdgeInsetsInsetRect(rect, edgeInsets);
|
||||
CGContextAddRect(ctx, insetRect);
|
||||
CGContextEOFillPath(ctx);
|
||||
} else {
|
||||
BOOL didSet = NO;
|
||||
CGPoint topLeft;
|
||||
if (topRadius > 0 && leftRadius > 0) {
|
||||
CGPoint points[2];
|
||||
RCTEllipseGetIntersectionsWithLine(CGRectMake(leftWidth, topWidth, 2 * leftRadius, 2 * topRadius), CGPointMake(0, 0), CGPointMake(leftWidth, topWidth), points);
|
||||
if (!isnan(points[1].x) && !isnan(points[1].y)) {
|
||||
topLeft = points[1];
|
||||
didSet = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear main layer border
|
||||
self.layer.borderWidth = 0;
|
||||
if (!didSet) {
|
||||
topLeft = CGPointMake(leftWidth, topWidth);
|
||||
}
|
||||
|
||||
// Set up border layers
|
||||
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
|
||||
[self updatePathForShapeLayerForSide:side];
|
||||
didSet = NO;
|
||||
CGPoint bottomLeft;
|
||||
if (bottomRadius > 0 && leftRadius > 0) {
|
||||
CGPoint points[2];
|
||||
RCTEllipseGetIntersectionsWithLine(CGRectMake(leftWidth, (size.height - bottomWidth) - 2 * bottomRadius, 2 * leftRadius, 2 * bottomRadius), CGPointMake(0, size.height), CGPointMake(leftWidth, size.height - bottomWidth), points);
|
||||
if (!isnan(points[1].x) && !isnan(points[1].y)) {
|
||||
bottomLeft = points[1];
|
||||
didSet = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (!didSet) {
|
||||
bottomLeft = CGPointMake(leftWidth, size.height - bottomWidth);
|
||||
}
|
||||
|
||||
didSet = NO;
|
||||
CGPoint topRight;
|
||||
if (topRadius > 0 && rightRadius > 0) {
|
||||
CGPoint points[2];
|
||||
RCTEllipseGetIntersectionsWithLine(CGRectMake((size.width - rightWidth) - 2 * rightRadius, topWidth, 2 * rightRadius, 2 * topRadius), CGPointMake(size.width, 0), CGPointMake(size.width - rightWidth, topWidth), points);
|
||||
if (!isnan(points[0].x) && !isnan(points[0].y)) {
|
||||
topRight = points[0];
|
||||
didSet = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (!didSet) {
|
||||
topRight = CGPointMake(size.width - rightWidth, topWidth);
|
||||
}
|
||||
|
||||
didSet = NO;
|
||||
CGPoint bottomRight;
|
||||
if (bottomRadius > 0 && rightRadius > 0) {
|
||||
CGPoint points[2];
|
||||
RCTEllipseGetIntersectionsWithLine(CGRectMake((size.width - rightWidth) - 2 * rightRadius, (size.height - bottomWidth) - 2 * bottomRadius, 2 * rightRadius, 2 * bottomRadius), CGPointMake(size.width, size.height), CGPointMake(size.width - rightWidth, size.height - bottomWidth), points);
|
||||
if (!isnan(points[0].x) && !isnan(points[0].y)) {
|
||||
bottomRight = points[0];
|
||||
didSet = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (!didSet) {
|
||||
bottomRight = CGPointMake(size.width - rightWidth, size.height - bottomWidth);
|
||||
}
|
||||
|
||||
// RIGHT
|
||||
if (rightWidth > 0) {
|
||||
CGContextSaveGState(ctx);
|
||||
|
||||
const CGPoint points[] = {
|
||||
CGPointMake(size.width, 0),
|
||||
topRight,
|
||||
bottomRight,
|
||||
CGPointMake(size.width, size.height),
|
||||
};
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, _borderRightColor ?: _borderColor);
|
||||
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
|
||||
CGContextFillPath(ctx);
|
||||
|
||||
CGContextRestoreGState(ctx);
|
||||
}
|
||||
|
||||
// BOTTOM
|
||||
if (bottomWidth > 0) {
|
||||
CGContextSaveGState(ctx);
|
||||
|
||||
const CGPoint points[] = {
|
||||
CGPointMake(0, size.height),
|
||||
bottomLeft,
|
||||
bottomRight,
|
||||
CGPointMake(size.width, size.height),
|
||||
};
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, _borderBottomColor ?: _borderColor);
|
||||
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
|
||||
CGContextFillPath(ctx);
|
||||
|
||||
CGContextRestoreGState(ctx);
|
||||
}
|
||||
|
||||
// LEFT
|
||||
if (leftWidth > 0) {
|
||||
CGContextSaveGState(ctx);
|
||||
|
||||
const CGPoint points[] = {
|
||||
CGPointMake(0, 0),
|
||||
topLeft,
|
||||
bottomLeft,
|
||||
CGPointMake(0, size.height),
|
||||
};
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, _borderLeftColor ?: _borderColor);
|
||||
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
|
||||
CGContextFillPath(ctx);
|
||||
|
||||
CGContextRestoreGState(ctx);
|
||||
}
|
||||
|
||||
// TOP
|
||||
if (topWidth > 0) {
|
||||
CGContextSaveGState(ctx);
|
||||
|
||||
const CGPoint points[] = {
|
||||
CGPointMake(0, 0),
|
||||
topLeft,
|
||||
topRight,
|
||||
CGPointMake(size.width, 0),
|
||||
};
|
||||
|
||||
CGContextSetFillColorWithColor(ctx, _borderTopColor ?: _borderColor);
|
||||
CGContextAddLines(ctx, points, sizeof(points)/sizeof(*points));
|
||||
CGContextFillPath(ctx);
|
||||
|
||||
CGContextRestoreGState(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
*contentsCenter = CGRectMake(edgeInsets.left / size.width, edgeInsets.top / size.height, 1.0 / size.width, 1.0 / size.height);
|
||||
return [image resizableImageWithCapInsets:edgeInsets];
|
||||
}
|
||||
|
||||
- (CGFloat)borderWidthForSide:(RCTBorderSide)side
|
||||
- (void)displayLayer:(CALayer *)layer
|
||||
{
|
||||
return _borderWidths[side] ?: _borderWidth;
|
||||
}
|
||||
CGRect contentsCenter;
|
||||
UIImage *image = [self generateBorderImage:&contentsCenter];
|
||||
|
||||
- (void)setBorderWidth:(CGFloat)width forSide:(RCTBorderSide)side
|
||||
{
|
||||
_borderWidths[side] = width;
|
||||
[self updateBorderLayers];
|
||||
}
|
||||
if (RCTRunningInTestEnvironment()) {
|
||||
const CGSize size = self.bounds.size;
|
||||
UIGraphicsBeginImageContextWithOptions(size, NO, image.scale);
|
||||
[image drawInRect:(CGRect){CGPointZero, size}];
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
|
||||
#define BORDER_WIDTH(SIDE) \
|
||||
- (CGFloat)border##SIDE##Width { return [self borderWidthForSide:RCTBorderSide##SIDE]; } \
|
||||
- (void)setBorder##SIDE##Width:(CGFloat)width { [self setBorderWidth:width forSide:RCTBorderSide##SIDE]; }
|
||||
|
||||
BORDER_WIDTH(Top)
|
||||
BORDER_WIDTH(Right)
|
||||
BORDER_WIDTH(Bottom)
|
||||
BORDER_WIDTH(Left)
|
||||
|
||||
- (CGColorRef)borderColorForSide:(RCTBorderSide)side
|
||||
{
|
||||
return _borderLayers[side].fillColor ?: self.layer.borderColor;
|
||||
}
|
||||
|
||||
- (void)setBorderColor:(CGColorRef)color forSide:(RCTBorderSide)side
|
||||
{
|
||||
[self createShapeLayerIfNotExistsForSide:side].fillColor = color;
|
||||
[self updateBorderLayers];
|
||||
}
|
||||
|
||||
#define BORDER_COLOR(SIDE) \
|
||||
- (CGColorRef)border##SIDE##Color { return [self borderColorForSide:RCTBorderSide##SIDE]; } \
|
||||
- (void)setBorder##SIDE##Color:(CGColorRef)color { [self setBorderColor:color forSide:RCTBorderSide##SIDE]; }
|
||||
|
||||
BORDER_COLOR(Top)
|
||||
BORDER_COLOR(Right)
|
||||
BORDER_COLOR(Bottom)
|
||||
BORDER_COLOR(Left)
|
||||
|
||||
- (void)setBorderWidth:(CGFloat)borderWidth
|
||||
{
|
||||
_borderWidth = borderWidth;
|
||||
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
|
||||
_borderWidths[side] = borderWidth;
|
||||
contentsCenter = CGRectMake(0, 0, 1, 1);
|
||||
}
|
||||
[self updateBorderLayers];
|
||||
|
||||
layer.contents = (id)image.CGImage;
|
||||
layer.contentsCenter = contentsCenter;
|
||||
layer.contentsScale = image.scale;
|
||||
layer.magnificationFilter = kCAFilterNearest;
|
||||
}
|
||||
|
||||
- (void)setBorderColor:(CGColorRef)borderColor
|
||||
{
|
||||
self.layer.borderColor = borderColor;
|
||||
for (RCTBorderSide side = 0; side < RCTBorderSideCount; side++) {
|
||||
_borderLayers[side].fillColor = borderColor;
|
||||
#pragma mark Border Color
|
||||
|
||||
#define setBorderColor(side) \
|
||||
- (void)setBorder##side##Color:(CGColorRef)border##side##Color \
|
||||
{ \
|
||||
if (CGColorEqualToColor(_border##side##Color, border##side##Color)) { \
|
||||
return; \
|
||||
} \
|
||||
_border##side##Color = border##side##Color; \
|
||||
[self.layer setNeedsDisplay]; \
|
||||
}
|
||||
[self updateBorderLayers];
|
||||
}
|
||||
|
||||
- (CGColorRef)borderColor
|
||||
{
|
||||
return self.layer.borderColor;
|
||||
}
|
||||
setBorderColor()
|
||||
setBorderColor(Top)
|
||||
setBorderColor(Right)
|
||||
setBorderColor(Bottom)
|
||||
setBorderColor(Left)
|
||||
|
||||
#pragma mark - Border Width
|
||||
|
||||
#define setBorderWidth(side) \
|
||||
- (void)setBorder##side##Width:(CGFloat)border##side##Width \
|
||||
{ \
|
||||
if (_border##side##Width == border##side##Width) { \
|
||||
return; \
|
||||
} \
|
||||
_border##side##Width = border##side##Width; \
|
||||
[self.layer setNeedsDisplay]; \
|
||||
}
|
||||
|
||||
setBorderWidth()
|
||||
setBorderWidth(Top)
|
||||
setBorderWidth(Right)
|
||||
setBorderWidth(Bottom)
|
||||
setBorderWidth(Left)
|
||||
|
||||
@end
|
||||
|
||||
static void RCTPathAddEllipticArc(CGMutablePathRef path, const CGAffineTransform *m, CGFloat x, CGFloat y, CGFloat xRadius, CGFloat yRadius, CGFloat startAngle, CGFloat endAngle, bool clockwise)
|
||||
{
|
||||
CGFloat xScale = 1, yScale = 1, radius = 0;
|
||||
if (xRadius != 0) {
|
||||
xScale = 1;
|
||||
yScale = yRadius / xRadius;
|
||||
radius = xRadius;
|
||||
} else if (yRadius != 0) {
|
||||
xScale = xRadius / yRadius;
|
||||
yScale = 1;
|
||||
radius = yRadius;
|
||||
}
|
||||
|
||||
CGAffineTransform t = CGAffineTransformMakeTranslation(x, y);
|
||||
t = CGAffineTransformScale(t, xScale, yScale);
|
||||
if (m != NULL) {
|
||||
t = CGAffineTransformConcat(t, *m);
|
||||
}
|
||||
|
||||
CGPathAddArc(path, &t, 0, 0, radius, startAngle, endAngle, clockwise);
|
||||
}
|
||||
|
||||
static CGPathRef RCTPathCreateWithRoundedRect(CGRect rect, CGFloat topLeftRadiusX, CGFloat topLeftRadiusY, CGFloat topRightRadiusX, CGFloat topRightRadiusY, CGFloat bottomLeftRadiusX, CGFloat bottomLeftRadiusY, CGFloat bottomRightRadiusX, CGFloat bottomRightRadiusY, const CGAffineTransform *transform)
|
||||
{
|
||||
const CGFloat minX = CGRectGetMinX(rect);
|
||||
const CGFloat minY = CGRectGetMinY(rect);
|
||||
const CGFloat maxX = CGRectGetMaxX(rect);
|
||||
const CGFloat maxY = CGRectGetMaxY(rect);
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
RCTPathAddEllipticArc(path, transform, minX + topLeftRadiusX, minY + topLeftRadiusY, topLeftRadiusX, topLeftRadiusY, M_PI, 3 * M_PI_2, false);
|
||||
RCTPathAddEllipticArc(path, transform, maxX - topRightRadiusX, minY + topRightRadiusY, topRightRadiusX, topRightRadiusY, 3 * M_PI_2, 0, false);
|
||||
RCTPathAddEllipticArc(path, transform, maxX - bottomRightRadiusX, maxY - bottomRightRadiusY, bottomRightRadiusX, bottomRightRadiusY, 0, M_PI_2, false);
|
||||
RCTPathAddEllipticArc(path, transform, minX + bottomLeftRadiusX, maxY - bottomLeftRadiusY, bottomLeftRadiusX, bottomLeftRadiusY, M_PI_2, M_PI, false);
|
||||
CGPathCloseSubpath(path);
|
||||
return path;
|
||||
}
|
||||
|
||||
static BOOL RCTEllipseGetIntersectionsWithLine(CGRect ellipseBoundingRect, CGPoint p1, CGPoint p2, CGPoint intersections[2])
|
||||
{
|
||||
const CGFloat ellipseCenterX = CGRectGetMidX(ellipseBoundingRect);
|
||||
const CGFloat ellipseCenterY = CGRectGetMidY(ellipseBoundingRect);
|
||||
|
||||
// ellipseBoundingRect.origin.x -= ellipseCenterX;
|
||||
// ellipseBoundingRect.origin.y -= ellipseCenterY;
|
||||
|
||||
p1.x -= ellipseCenterX;
|
||||
p1.y -= ellipseCenterY;
|
||||
|
||||
p2.x -= ellipseCenterX;
|
||||
p2.y -= ellipseCenterY;
|
||||
|
||||
const CGFloat m = (p2.y - p1.y) / (p2.x - p1.x);
|
||||
const CGFloat a = ellipseBoundingRect.size.width / 2;
|
||||
const CGFloat b = ellipseBoundingRect.size.height / 2;
|
||||
const CGFloat c = p1.y - m * p1.x;
|
||||
const CGFloat A = (b * b + a * a * m * m);
|
||||
const CGFloat B = 2 * a * a * c * m;
|
||||
const CGFloat D = sqrt((a * a * (b * b - c * c)) / A + pow(B / (2 * A), 2));
|
||||
|
||||
const CGFloat x_ = -B / (2 * A);
|
||||
const CGFloat x1 = x_ + D;
|
||||
const CGFloat x2 = x_ - D;
|
||||
const CGFloat y1 = m * x1 + c;
|
||||
const CGFloat y2 = m * x2 + c;
|
||||
|
||||
intersections[0] = CGPointMake(x1 + ellipseCenterX, y1 + ellipseCenterY);
|
||||
intersections[1] = CGPointMake(x2 + ellipseCenterX, y2 + ellipseCenterY);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -164,7 +164,9 @@ RCT_EXPORT_SHADOW_PROPERTY(borderTopWidth, CGFloat);
|
||||
RCT_EXPORT_SHADOW_PROPERTY(borderRightWidth, CGFloat);
|
||||
RCT_EXPORT_SHADOW_PROPERTY(borderBottomWidth, CGFloat);
|
||||
RCT_EXPORT_SHADOW_PROPERTY(borderLeftWidth, CGFloat);
|
||||
RCT_EXPORT_SHADOW_PROPERTY(borderWidth, CGFloat);
|
||||
RCT_CUSTOM_SHADOW_PROPERTY(borderWidth, CGFloat, RCTShadowView) {
|
||||
[view setBorderWidth:[RCTConvert CGFloat:json]];
|
||||
}
|
||||
|
||||
RCT_EXPORT_SHADOW_PROPERTY(marginTop, CGFloat);
|
||||
RCT_EXPORT_SHADOW_PROPERTY(marginRight, CGFloat);
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
@end
|
||||
|
||||
// TODO: this is kinda dumb - let's come up with a
|
||||
// better way of identifying root react views please!
|
||||
// better way of identifying root React views please!
|
||||
static inline BOOL RCTIsReactRootView(NSNumber *reactTag) {
|
||||
return reactTag.integerValue % 10 == 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user