Merge pull request #2541 from sahrens/Updates_from_Thu_Sep_3rd

Updates from Thu September 3rd
This commit is contained in:
Spencer Ahrens 2015-09-03 17:51:41 -07:00
commit 304989e3b8
94 changed files with 7570 additions and 1092 deletions

View File

@ -26,12 +26,12 @@ var styles = StyleSheet.create({
},
border1: {
borderWidth: 10,
borderColor: 'brown',
borderColor: '#a52a2a',
},
borderRadius: {
borderWidth: 10,
borderRadius: 10,
borderColor: 'cyan',
borderColor: '#00ffff',
},
border2: {
borderWidth: 10,
@ -134,6 +134,7 @@ exports.examples = [
{
title: 'Custom Borders',
description: 'border*Width & border*Color',
platform: 'ios',
render() {
return <View style={[styles.box, styles.border5]} />;
}
@ -141,6 +142,7 @@ exports.examples = [
{
title: 'Custom Borders',
description: 'border*Width & border*Color',
platform: 'ios',
render() {
return <View style={[styles.box, styles.border6]} />;
}
@ -148,6 +150,7 @@ exports.examples = [
{
title: 'Custom Borders',
description: 'borderRadius & clipping',
platform: 'ios',
render() {
return (
<View style={[styles.box, styles.border7]}>

View File

@ -14,11 +14,13 @@
* @providesModule ExampleTypes
* @flow
*/
'use strict';
export type Example = {
title: string,
render: () => ?ReactElement<any, any, any>,
description?: string,
platform?: string;
};
export type ExampleModule = {

View File

@ -97,6 +97,7 @@ exports.examples = [
<NetworkImageExample source={{uri: 'http://TYPO_ERROR_facebook.github.io/react/img/logo_og.png'}} />
);
},
platform: 'ios',
},
{
title: 'Image Download Progress',
@ -105,6 +106,7 @@ exports.examples = [
<NetworkImageExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/>
);
},
platform: 'ios',
},
{
title: 'Border Color',
@ -122,6 +124,7 @@ exports.examples = [
</View>
);
},
platform: 'ios',
},
{
title: 'Border Width',
@ -139,6 +142,7 @@ exports.examples = [
</View>
);
},
platform: 'ios',
},
{
title: 'Border Radius',
@ -146,17 +150,12 @@ exports.examples = [
return (
<View style={styles.horizontal}>
<Image
style={[styles.base, styles.background, {borderRadius: 5}]}
source={smallImage}
style={[styles.base, {borderRadius: 5}]}
source={fullImage}
/>
<Image
style={[
styles.base,
styles.background,
styles.leftMargin,
{borderRadius: 19}
]}
source={smallImage}
style={[styles.base, styles.leftMargin, {borderRadius: 19}]}
source={fullImage}
/>
</View>
);
@ -245,19 +244,19 @@ exports.examples = [
<View style={styles.horizontal}>
<Image
source={require('image!uie_thumb_normal')}
style={[styles.icon, {tintColor: '#5ac8fa' }]}
style={[styles.icon, {borderRadius: 5, tintColor: '#5ac8fa' }]}
/>
<Image
source={require('image!uie_thumb_normal')}
style={[styles.icon, styles.leftMargin, {tintColor: '#4cd964' }]}
style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#4cd964' }]}
/>
<Image
source={require('image!uie_thumb_normal')}
style={[styles.icon, styles.leftMargin, {tintColor: '#ff2d55' }]}
style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#ff2d55' }]}
/>
<Image
source={require('image!uie_thumb_normal')}
style={[styles.icon, styles.leftMargin, {tintColor: '#8e8e93' }]}
style={[styles.icon, styles.leftMargin, {borderRadius: 5, tintColor: '#8e8e93' }]}
/>
</View>
<Text style={styles.sectionText}>
@ -266,19 +265,19 @@ exports.examples = [
<View style={styles.horizontal}>
<Image
source={smallImage}
style={[styles.base, {tintColor: '#5ac8fa' }]}
style={[styles.base, {borderRadius: 5, tintColor: '#5ac8fa' }]}
/>
<Image
source={smallImage}
style={[styles.base, styles.leftMargin, {tintColor: '#4cd964' }]}
style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#4cd964' }]}
/>
<Image
source={smallImage}
style={[styles.base, styles.leftMargin, {tintColor: '#ff2d55' }]}
style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#ff2d55' }]}
/>
<Image
source={smallImage}
style={[styles.base, styles.leftMargin, {tintColor: '#8e8e93' }]}
style={[styles.base, styles.leftMargin, {borderRadius: 5, tintColor: '#8e8e93' }]}
/>
</View>
</View>
@ -336,6 +335,7 @@ exports.examples = [
render: function() {
return <ImageCapInsetsExample />;
},
platform: 'ios',
},
];

View File

@ -85,8 +85,7 @@ var LayoutEventExample = React.createClass({
return (
<View style={this.state.containerStyle}>
<Text>
onLayout events are called on mount and whenever layout is updated,
including after layout animations complete.{' '}
layout events are called on mount and whenever layout is recalculated. Note that the layout event will typically be received <Text style={styles.italicText}>before</Text> the layout has updated on screen, especially when using layout animations.{' '}
<Text style={styles.pressText} onPress={this.animateViewLayout}>
Press here to change layout.
</Text>
@ -136,6 +135,9 @@ var styles = StyleSheet.create({
pressText: {
fontWeight: 'bold',
},
italicText: {
fontStyle: 'italic',
},
});
exports.title = 'Layout Events';

View File

@ -54,6 +54,8 @@
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; };
834C36EC1AF8DED70019C93C /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 834C36D21AF8DA610019C93C /* libRCTSettings.a */; };
83636F8F1B53F22C009F943E /* RCTUIManagerScenarioTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */; };
8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */; };
8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = 8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */; };
83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */; };
D85B829E1AB6D5D7003F4FE2 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D85B829C1AB6D5CE003F4FE2 /* libRCTVibration.a */; };
/* End PBXBuildFile section */
@ -217,6 +219,9 @@
357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
58005BE41ABA80530062E044 /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = ../../Libraries/RCTTest/RCTTest.xcodeproj; sourceTree = "<group>"; };
83636F8E1B53F22C009F943E /* RCTUIManagerScenarioTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUIManagerScenarioTests.m; sourceTree = "<group>"; };
8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderTests.m; sourceTree = "<group>"; };
8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageLoaderHelpers.m; sourceTree = "<group>"; };
8385CF051B8747A000C6273E /* RCTImageLoaderHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTImageLoaderHelpers.h; sourceTree = "<group>"; };
83A936C71B7E0F08005B9C36 /* RCTConvert_UIColorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert_UIColorTests.m; sourceTree = "<group>"; };
D85B82911AB6D5CE003F4FE2 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = ../../Libraries/Vibration/RCTVibration.xcodeproj; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -365,6 +370,9 @@
1497CFA81B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m */,
1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
8385CF051B8747A000C6273E /* RCTImageLoaderHelpers.h */,
8385CF031B87479200C6273E /* RCTImageLoaderHelpers.m */,
8385CEF41B873B5C00C6273E /* RCTImageLoaderTests.m */,
144D21231B2204C5006DB32B /* RCTImageUtilTests.m */,
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */,
13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */,
@ -811,6 +819,8 @@
83A936C81B7E0F08005B9C36 /* RCTConvert_UIColorTests.m in Sources */,
13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */,
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
8385CF041B87479200C6273E /* RCTImageLoaderHelpers.m in Sources */,
8385CEF51B873B5C00C6273E /* RCTImageLoaderTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -57,7 +57,7 @@
expectErrorRegex:@"because shouldThrow"];
}
- (void)DISABLED_testTimers // #8192477
- (void)testTimers
{
[_runner runTest:_cmd module:@"TimersTest"];
}

View File

@ -23,7 +23,7 @@
#define RUN_RUNLOOP_WHILE(CONDITION) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:0.1]; \
NSDate *timeout = [[NSDate date] dateByAddingTimeInterval:5]; \
while ((CONDITION) && [timeout timeIntervalSinceNow] > 0) { \
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; \
} \
@ -141,7 +141,7 @@ RCT_EXPORT_METHOD(test:(__unused NSString *)a
XCTAssertNil(weakMethod, @"RCTModuleMethod should have been deallocated");
}
- (void)DISABLED_testJavaScriptExecutorIsDeallocated // flaky: #8195866
- (void)testJavaScriptExecutorIsDeallocated
{
__weak id<RCTJavaScriptExecutor> weakExecutor;
@autoreleasepool {
@ -157,7 +157,7 @@ RCT_EXPORT_METHOD(test:(__unused NSString *)a
XCTAssertNil(weakExecutor, @"JavaScriptExecutor should have been released");
}
- (void)disabled_testJavaScriptContextIsDeallocated
- (void)testJavaScriptContextIsDeallocated
{
__weak id weakContext;
@autoreleasepool {

View File

@ -0,0 +1,47 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import "RCTImageLoader.h"
typedef BOOL (^RCTImageURLLoaderCanLoadImageURLHandler)(NSURL *requestURL);
typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(NSURL *imageURL, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler);
@interface RCTConcreteImageURLLoader : NSObject <RCTImageURLLoader>
- (instancetype)initWithPriority:(float)priority
canLoadImageURLHandler:(RCTImageURLLoaderCanLoadImageURLHandler)canLoadImageURLHandler
loadImageURLHandler:(RCTImageURLLoaderLoadImageURLHandler)loadImageURLHandler;
@end
typedef BOOL (^RCTImageDecoderCanDecodeImageDataHandler)(NSData *imageData);
typedef RCTImageLoaderCancellationBlock (^RCTImageDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
@interface RCTConcreteImageDecoder : NSObject <RCTImageDecoder>
- (instancetype)initWithPriority:(float)priority
canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler
decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler;
@end
#define _RCTDefineImageHandler(SUPERCLASS, CLASS_NAME) \
@interface CLASS_NAME : SUPERCLASS @end \
@implementation CLASS_NAME RCT_EXPORT_MODULE() @end
#define RCTDefineImageURLLoader(CLASS_NAME) \
_RCTDefineImageHandler(RCTConcreteImageURLLoader, CLASS_NAME)
#define RCTDefineImageDecoder(CLASS_NAME) \
_RCTDefineImageHandler(RCTConcreteImageDecoder, CLASS_NAME)

View File

@ -0,0 +1,105 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import "RCTImageLoaderHelpers.h"
@implementation RCTConcreteImageURLLoader
{
RCTImageURLLoaderCanLoadImageURLHandler _canLoadImageURLHandler;
RCTImageURLLoaderLoadImageURLHandler _loadImageURLHandler;
float _priority;
}
+ (NSString *)moduleName
{
return nil;
}
- (instancetype)init
{
return nil;
}
- (instancetype)initWithPriority:(float)priority canLoadImageURLHandler:(RCTImageURLLoaderCanLoadImageURLHandler)canLoadImageURLHandler loadImageURLHandler:(RCTImageURLLoaderLoadImageURLHandler)loadImageURLHandler
{
if ((self = [super init])) {
_canLoadImageURLHandler = [canLoadImageURLHandler copy];
_loadImageURLHandler = [loadImageURLHandler copy];
_priority = priority;
}
return self;
}
- (BOOL)canLoadImageURL:(NSURL *)requestURL
{
return _canLoadImageURLHandler(requestURL);
}
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
return _loadImageURLHandler(imageURL, size, scale, resizeMode, progressHandler, completionHandler);
}
- (float)imageLoaderPriority
{
return _priority;
}
@end
@implementation RCTConcreteImageDecoder
{
RCTImageDecoderCanDecodeImageDataHandler _canDecodeImageDataHandler;
RCTImageDecoderDecodeImageDataHandler _decodeImageDataHandler;
float _priority;
}
+ (NSString *)moduleName
{
return nil;
}
- (instancetype)init
{
return nil;
}
- (instancetype)initWithPriority:(float)priority canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler
{
if ((self = [super init])) {
_canDecodeImageDataHandler = [canDecodeImageDataHandler copy];
_decodeImageDataHandler = [decodeImageDataHandler copy];
_priority = priority;
}
return self;
}
- (BOOL)canDecodeImageData:(NSData *)imageData
{
return _canDecodeImageDataHandler(imageData);
}
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
return _decodeImageDataHandler(imageData, size, scale, resizeMode, completionHandler);
}
- (float)imageDecoderPriority
{
return _priority;
}
@end

View File

@ -0,0 +1,146 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <XCTest/XCTest.h>
#import "RCTBridge.h"
#import "RCTImageLoader.h"
#import "RCTImageLoaderHelpers.h"
unsigned char blackGIF[] = {
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b
};
RCTDefineImageURLLoader(RCTImageLoaderTestsURLLoader1)
RCTDefineImageURLLoader(RCTImageLoaderTestsURLLoader2)
RCTDefineImageDecoder(RCTImageLoaderTestsDecoder1)
RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
@interface RCTImageLoaderTests : XCTestCase
@end
@implementation RCTImageLoaderTests
- (void)testImageLoading
{
UIImage *image = [UIImage new];
id<RCTImageURLLoader> loader = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
return YES;
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
progressHandler(1, 1);
completionHandler(nil, image);
return nil;
}];
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader, imageLoader]; } launchOptions:nil];
[imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:UIViewContentModeScaleAspectFit progressBlock:^(int64_t progress, int64_t total) {
XCTAssertEqual(progress, 1);
XCTAssertEqual(total, 1);
} completionBlock:^(NSError *loadError, id loadedImage) {
XCTAssertEqualObjects(loadedImage, image);
XCTAssertNil(loadError);
}];
}
- (void)testImageLoaderUsesImageURLLoaderWithHighestPriority
{
UIImage *image = [UIImage new];
id<RCTImageURLLoader> loader1 = [[RCTImageLoaderTestsURLLoader1 alloc] initWithPriority:1.0 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
return YES;
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderProgressBlock progressHandler, RCTImageLoaderCompletionBlock completionHandler) {
progressHandler(1, 1);
completionHandler(nil, image);
return nil;
}];
id<RCTImageURLLoader> loader2 = [[RCTImageLoaderTestsURLLoader2 alloc] initWithPriority:0.5 canLoadImageURLHandler:^BOOL(__unused NSURL *requestURL) {
return YES;
} loadImageURLHandler:^RCTImageLoaderCancellationBlock(__unused NSURL *imageURL, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderProgressBlock progressHandler, __unused RCTImageLoaderCompletionBlock completionHandler) {
XCTFail(@"Should not have used loader2");
return nil;
}];
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[loader1, loader2, imageLoader]; } launchOptions:nil];
[imageLoader loadImageWithTag:@"http://facebook.github.io/react/img/logo_og.png" size:CGSizeMake(100, 100) scale:1.0 resizeMode:UIViewContentModeScaleAspectFit progressBlock:^(int64_t progress, int64_t total) {
XCTAssertEqual(progress, 1);
XCTAssertEqual(total, 1);
} completionBlock:^(NSError *loadError, id loadedImage) {
XCTAssertEqualObjects(loadedImage, image);
XCTAssertNil(loadError);
}];
}
- (void)testImageDecoding
{
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
UIImage *image = [[UIImage alloc] initWithData:data];
id<RCTImageDecoder> decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
XCTAssertEqualObjects(imageData, data);
completionHandler(nil, image);
return nil;
}];
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder, imageLoader]; } launchOptions:nil];
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) {
XCTAssertEqualObjects(decodedImage, image);
XCTAssertNil(decodeError);
}];
XCTAssertNil(cancelBlock);
}
- (void)testImageLoaderUsesImageDecoderWithHighestPriority
{
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
UIImage *image = [[UIImage alloc] initWithData:data];
id<RCTImageDecoder> decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
XCTAssertEqualObjects(imageData, data);
completionHandler(nil, image);
return nil;
}];
id<RCTImageDecoder> decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) {
XCTFail(@"Should not have used decoder2");
return nil;
}];
RCTImageLoader *imageLoader = [RCTImageLoader new];
NS_VALID_UNTIL_END_OF_SCOPE RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[decoder1, decoder2, imageLoader]; } launchOptions:nil];
RCTImageLoaderCancellationBlock cancelBlock = [imageLoader decodeImageData:data size:CGSizeMake(1, 1) scale:1.0 resizeMode:UIViewContentModeScaleToFill completionBlock:^(NSError *decodeError, id decodedImage) {
XCTAssertEqualObjects(decodedImage, image);
XCTAssertNil(decodeError);
}];
XCTAssertNil(cancelBlock);
}
@end

View File

@ -17,6 +17,9 @@
'use strict';
var React = require('react-native');
var {
Platform,
} = React;
var ReactNative = require('ReactNative');
var UIExplorerBlock = require('./UIExplorerBlock');
var UIExplorerPage = require('./UIExplorerPage');
@ -36,6 +39,12 @@ var createExamplePage = function(title: ?string, exampleModule: ExampleModule)
},
getBlock: function(example: Example, i) {
if (example.platform) {
if (Platform.OS !== example.platform) {
return;
}
example.title += ' (' + example.platform + ' only)';
}
// Hack warning: This is a hack because the www UI explorer requires
// renderComponent to be called.
var originalRender = React.render;

View File

@ -1,5 +1,3 @@
//#include "config.h"
#include "JSCLegacyProfiler.h"
#include "APICast.h"

View File

@ -1,112 +1,101 @@
HEADER_PATHS := `find ./tmp/JavaScriptCore -name '*.h' | xargs -I{} dirname {} | uniq | xargs -I{} echo "-I {}"`
SDK_VERSION=$(shell plutil -convert json -o - /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/SDKSettings.plist | awk -f parseSDKVersion.awk)
HEADER_PATHS := `find download/JavaScriptCore -name '*.h' | xargs -I{} dirname {} | uniq | xargs -I{} echo "-I {}"`
SDK_PATH = /Applications/Xcode.app/Contents/Developer/Platforms/$1.platform/Developer/SDKs/$1.sdk
SDK_VERSION = $(shell plutil -convert json -o - $(call SDK_PATH,iPhoneOS)/SDKSettings.plist | awk -f parseSDKVersion.awk)
CERT ?= "iPhone Developer"
ARCHS = x86_64 arm64 armv7 i386
PLATFORM = \
if [[ "$*" = "x86_64" || "$*" = "i386" ]]; then \
PLATFORM=iPhoneSimulator; \
else \
PLATFORM=iPhoneOS; \
fi;
SYSROOT = -isysroot $(call SDK_PATH,$${PLATFORM})
IOS8_LIBS = download/WebCore/WebCore-7600.1.25 download/WTF/WTF-7600.1.24 download/JavaScriptCore/JavaScriptCore-7600.1.17 download/JavaScriptCore/JavaScriptCore-7600.1.17/Bytecodes.h libyajl.a
ios8: RCTJSCProfiler.ios8.dylib /tmp/RCTJSCProfiler
ifneq ($(SDK_VERSION), 8)
all:
$(error "Expected to be compiled with iOS SDK version 8, found $(SDK_VERSION)")
else
cp $^
endif
ios8: prepare build generate
/tmp/JSCProfiler:
mkdir -p $@
prepare: clean create download
.PRECIOUS: RCTJSCProfiler.ios8.dylib
RCTJSCProfiler.ios8.dylib: RCTJSCProfiler_unsigned.ios8.dylib
cp $< $@
codesign -f -s ${CERT} $@ || rm $@
build: x86_64 arm64 armv7
.PRECIOUS: RCTJSCProfiler_unsigned.ios8.dylib
RCTJSCProfiler_unsigned.ios8.dylib: $(patsubst %,RCTJSCProfiler_%.ios8.dylib,$(ARCHS))
lipo -create -output $@ $^
generate: lipo codesign
.PRECIOUS: RCTJSCProfiler_%.ios8.dylib
RCTJSCProfiler_%.ios8.dylib: $(IOS8_LIBS)
$(PLATFORM) \
clang -w -dynamiclib -o RCTJSCProfiler_$*.ios8.dylib -std=c++11 \
-arch $* \
-install_name RCTJSCProfiler.ios8.dylib \
-include ./download/JavaScriptCore/JavaScriptCore-7600.1.17/config.h \
-I download \
-I download/WebCore/WebCore-7600.1.25/icu \
-I download/WTF/WTF-7600.1.24 \
-I download/yajl-2.1.0/build/yajl-2.1.0/include \
-DNDEBUG=1\
-miphoneos-version-min=8.0 \
$(SYSROOT) \
$(HEADER_PATHS) \
-undefined dynamic_lookup \
JSCLegacyProfiler.mm libyajl.a
clean:
@rm -rf tmp/ /tmp/RCTJSCProfiler
.PRECIOUS: %/Bytecodes.h
%/Bytecodes.h:
python $*/generate-bytecode-files --bytecodes_h $@ $*/bytecode/BytecodeList.json
lipo:
lipo -create -output /tmp/RCTJSCProfiler/RCTJSCProfiler.ios8.dylib ./tmp/RCTJSCProfiler_x86_64 ./tmp/RCTJSCProfiler_arm64 ./tmp/RCTJSCProfiler_armv7
.PRECIOUS: libyajl.a
libyajl.a: $(patsubst %,libyajl_%.a,$(ARCHS))
lipo -create $^ -output $@
codesign:
codesign -f -s ${CERT} /tmp/RCTJSCProfiler/RCTJSCProfiler.ios8.dylib
create:
mkdir -p ./tmp /tmp/RCTJSCProfiler/ ./tmp/CoreFoundation ./tmp/Foundation
for file in ./tmp/CoreFoundation/CFUserNotification.h ./tmp/CoreFoundation/CFXMLNode.h ./tmp/CoreFoundation/CFXMLParser.h ./tmp/Foundation/Foundation.h; do echo '' > "$$file"; done
download: wtf jsc webcore yajl
wtf:
curl -o tmp/WTF.tar.gz http://www.opensource.apple.com/tarballs/WTF/WTF-7600.1.24.tar.gz
tar -zxvf tmp/WTF.tar.gz -C tmp
jsc:
curl -o tmp/JSC.tar.gz http://www.opensource.apple.com/tarballs/JavaScriptCore/JavaScriptCore-7600.1.17.tar.gz
tar -zxvf tmp/JSC.tar.gz -C tmp
mv ./tmp/JavaScriptCore-7600.1.17 ./tmp/JavaScriptCore
python ./tmp/JavaScriptCore/generate-bytecode-files --bytecodes_h ./tmp/JavaScriptCore/Bytecodes.h ./tmp/JavaScriptCore/bytecode/BytecodeList.json
webcore:
curl -o tmp/WebCore.tar.gz http://www.opensource.apple.com/tarballs/WebCore/WebCore-7600.1.25.tar.gz
tar -zxvf tmp/WebCore.tar.gz -C tmp
yajl:
curl -o tmp/yajl.tar.gz https://codeload.github.com/lloyd/yajl/tar.gz/2.1.0
tar -zxvf tmp/yajl.tar.gz -C tmp
mkdir -p ./tmp/yajl-2.1.0/build && cd ./tmp/yajl-2.1.0/build && cmake .. && make
echo `find . -name '*.c'`
cd ./tmp/yajl-2.1.0/src && \
clang -arch arm64 -arch armv7 -std=c99 \
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/ \
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/machine \
.PRECIOUS: libyajl_%.a
libyajl_%.a: download/yajl-2.1.0
$(PLATFORM) \
cd download/yajl-2.1.0/src; \
clang -arch $(*F) -std=c99 \
-miphoneos-version-min=8.0 \
$(SYSROOT) \
-I ../build/yajl-2.1.0/include \
-c `find . -name '*.c'`
libtool -static -o ./tmp/yajl.a `find ./tmp/yajl-2.1.0/src/ -name '*.o'`
find download/yajl-2.1.0/src/ -name '*.o' -exec libtool -static -o $@ {} +
x86_64:
clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_x86_64 -std=c++11 \
-install_name RCTJSCProfiler.ios8.dylib \
-include ./tmp/JavaScriptCore/config.h \
-I ./tmp \
-I ./tmp/WebCore-7600.1.25/icu \
-I ./tmp/WTF-7600.1.24 \
-I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \
-DNDEBUG=1\
-miphoneos-version-min=8.0 \
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib \
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/system \
${HEADER_PATHS} \
-undefined dynamic_lookup \
./JSCLegacyProfiler.mm ./tmp/yajl-2.1.0/build/yajl-2.1.0/lib/libyajl_s.a
.PRECIOUS: download/yajl-2.1.0
download/yajl-2.1.0: download/yajl-2.1.0.tar.gz
tar -zxvf $< -C download
mkdir -p download/yajl-2.1.0/build && cd download/yajl-2.1.0/build && cmake ..
arm64:
echo $(HEADER_PATHS)
clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_arm64 -std=c++11 \
-install_name RCTJSCProfiler.ios8.dylib \
-arch arm64 \
-include ./tmp/JavaScriptCore/config.h \
-I ./tmp \
-I ./tmp/WebCore-7600.1.25/icu \
-I ./tmp/WTF-7600.1.24 \
-I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include \
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/machine \
-DNDEBUG=1\
-miphoneos-version-min=8.0 \
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib \
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/system \
${HEADER_PATHS} \
-undefined dynamic_lookup \
./JSCLegacyProfiler.mm ./tmp/yajl.a
.PRECIOUS: download/yajl-2.1.0.tar.gz
download/yajl-2.1.0.tar.gz:
mkdir -p `dirname $@`
curl -o $@ https://codeload.github.com/lloyd/yajl/tar.gz/2.1.0
armv7:
clang -w -dynamiclib -o ./tmp/RCTJSCProfiler_armv7 -std=c++11 \
-install_name RCTJSCProfiler.ios8.dylib \
-arch armv7 \
-include ./tmp/JavaScriptCore/config.h \
-I ./tmp \
-I ./tmp/WebCore-7600.1.25/icu \
-I ./tmp/WTF-7600.1.24 \
-I ./tmp/yajl-2.1.0/build/yajl-2.1.0/include \
-I /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include \
-DNDEBUG=1\
-miphoneos-version-min=8.0 \
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib \
-L /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/system \
${HEADER_PATHS} \
-undefined dynamic_lookup \
./JSCLegacyProfiler.mm ./tmp/yajl.a
.PRECIOUS: download/%
download/%: download/%.tar.gz
tar -zxvf $< -C `dirname $@`
.PRECIOUS: %.tar.gz
%.tar.gz:
mkdir -p `dirname $@`
curl -o $@ http://www.opensource.apple.com/tarballs/$(patsubst download/%,%,$@)
.PHONY: clean
clean:
@rm -rf $(wildcard *.dylib)
@rm -rf $(wildcard *.a)
@rm -rf download

View File

@ -69,10 +69,7 @@ class Easing {
* http://tiny.cc/elastic_b_1 (default bounciness = 1)
* http://tiny.cc/elastic_b_3 (bounciness = 3)
*/
static elastic(bounciness: number): (t: number) => number {
if (arguments.length === 0) {
bounciness = 1;
}
static elastic(bounciness: number = 1): (t: number) => number {
var p = bounciness * Math.PI;
return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p);
};

View File

@ -30,6 +30,12 @@ var NativeModules = {
customBubblingEventTypes: {},
customDirectEventTypes: {},
Dimensions: {},
RCTModalFullscreenView: {
Constants: {},
},
RCTScrollView: {
Constants: {},
},
},
AsyncLocalStorage: {
getItem: jest.genMockFunction(),
@ -44,6 +50,13 @@ var NativeModules = {
appVersion: '0',
buildVersion: '0',
},
ModalFullscreenViewManager: {},
AlertManager: {
alertWithArgs: jest.genMockFunction(),
},
Pasteboard: {
setPasteboardString: jest.genMockFunction(),
},
};
module.exports = NativeModules;

View File

@ -277,7 +277,9 @@ if (Platform.OS === 'android') {
uiViewClassName: 'RCTMap',
});
} else {
var RCTMap = requireNativeComponent('RCTMap', MapView);
var RCTMap = requireNativeComponent('RCTMap', MapView, {
nativeOnly: {onChange: true, onPress: true}
});
}
module.exports = MapView;

View File

@ -107,6 +107,8 @@ var styles = StyleSheet.create({
},
});
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS, {
nativeOnly: { onChange: true },
});
module.exports = SliderIOS;

View File

@ -108,6 +108,8 @@ var styles = StyleSheet.create({
},
});
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS);
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS, {
nativeOnly: { onChange: true }
});
module.exports = SwitchIOS;

View File

@ -211,6 +211,7 @@ var TouchableHighlight = React.createClass({
accessible={true}
ref={UNDERLAY_REF}
style={this.state.underlayStyle}
onLayout={this.props.onLayout}
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
onResponderGrant={this.touchableHandleResponderGrant}

View File

@ -158,6 +158,7 @@ var TouchableOpacity = React.createClass({
accessible={true}
style={[this.props.style, {opacity: this.state.anim}]}
testID={this.props.testID}
onLayout={this.props.onLayout}
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
onResponderGrant={this.touchableHandleResponderGrant}

View File

@ -44,7 +44,15 @@ var TouchableWithoutFeedback = React.createClass({
onPress: React.PropTypes.func,
onPressIn: React.PropTypes.func,
onPressOut: React.PropTypes.func,
/**
* Invoked on mount and layout changes with
*
* `{nativeEvent: {layout: {x, y, width, height}}}`
*/
onLayout: React.PropTypes.func,
onLongPress: React.PropTypes.func,
/**
* Delay in ms, from the start of the touch, before onPressIn is called.
*/
@ -113,6 +121,7 @@ var TouchableWithoutFeedback = React.createClass({
return (React: any).cloneElement(onlyChild(this.props.children), {
accessible: this.props.accessible !== false,
testID: this.props.testID,
onLayout: this.props.onLayout,
onStartShouldSetResponder: this.touchableHandleStartShouldSetResponder,
onResponderTerminationRequest: this.touchableHandleResponderTerminationRequest,
onResponderGrant: this.touchableHandleResponderGrant,

View File

@ -185,6 +185,10 @@ var View = React.createClass({
* Invoked on mount and layout changes with
*
* {nativeEvent: { layout: {x, y, width, height}}}.
*
* This event is fired immediately once the layout has been calculated, but
* the new layout may not yet be reflected on the screen at the time the
* event is received, especially if a layout animation is in progress.
*/
onLayout: PropTypes.func,

View File

@ -226,7 +226,13 @@ var WebView = React.createClass({
},
});
var RCTWebView = requireNativeComponent('RCTWebView', WebView);
var RCTWebView = requireNativeComponent('RCTWebView', WebView, {
nativeOnly: {
onLoadingStart: true,
onLoadingError: true,
onLoadingFinish: true,
},
});
var styles = StyleSheet.create({
container: {

View File

@ -7,8 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTURLRequestHandler.h"
#import "RCTImageLoader.h"
@interface RCTImageRequestHandler : NSObject <RCTURLRequestHandler>
@interface RCTAssetBundleImageLoader : NSObject <RCTImageURLLoader>
@end

View File

@ -0,0 +1,80 @@
/**
* 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 "RCTAssetBundleImageLoader.h"
#import "RCTUtils.h"
@implementation RCTAssetBundleImageLoader
RCT_EXPORT_MODULE()
- (NSString *)imageNameForRequestURL:(NSURL *)requestURL
{
if (!requestURL.fileURL) {
return nil;
}
NSString *resourcesPath = [NSBundle mainBundle].resourcePath;
NSString *requestPath = requestURL.absoluteURL.path;
if (requestPath.length < resourcesPath.length + 1) {
return nil;
}
return [requestPath substringFromIndex:resourcesPath.length + 1];
}
- (BOOL)canLoadImageURL:(NSURL *)requestURL
{
NSString *imageName = [self imageNameForRequestURL:requestURL];
if (!imageName.length) {
return NO;
}
if ([[NSBundle mainBundle] URLForResource:imageName withExtension:nil] ||
[[NSBundle mainBundle] URLForResource:imageName withExtension:@"png"]) {
return YES;
}
return imageName.pathComponents.count == 1 && !imageName.pathExtension.length;
}
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
NSString *imageName = [self imageNameForRequestURL:imageURL];
__block BOOL cancelled = NO;
dispatch_async(dispatch_get_main_queue(), ^{
if (cancelled) {
return;
}
UIImage *image = [UIImage imageNamed:imageName];
if (image) {
if (progressHandler) {
progressHandler(1, 1);
}
if (completionHandler) {
completionHandler(nil, image);
}
} else {
if (completionHandler) {
NSString *message = [NSString stringWithFormat:@"Could not find image named %@", imageName];
completionHandler(RCTErrorWithMessage(message), nil);
}
}
});
return ^{
cancelled = YES;
};
}
@end

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTImageLoader.h"
@interface RCTAssetsLibraryImageLoader : NSObject <RCTImageURLLoader>
@end
@interface RCTBridge (RCTAssetsLibraryImageLoader)
/**
* The shared Assets Library image loader
*/
@property (nonatomic, readonly) RCTAssetsLibraryImageLoader *assetsLibraryImageLoader;
/**
* The shared asset library instance.
*/
@property (nonatomic, readonly) ALAssetsLibrary *assetsLibrary;
@end

View File

@ -0,0 +1,155 @@
/**
* 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 "RCTAssetsLibraryImageLoader.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <ImageIO/ImageIO.h>
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTImageLoader.h"
#import "RCTImageUtils.h"
#import "RCTUtils.h"
static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void);
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, CGSize size, CGFloat scale, UIViewContentMode resizeMode, NSError **error);
@implementation RCTAssetsLibraryImageLoader
{
ALAssetsLibrary *_assetsLibrary;
}
RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
- (ALAssetsLibrary *)assetsLibrary
{
return _assetsLibrary ?: (_assetsLibrary = [ALAssetsLibrary new]);
}
#pragma mark - RCTImageLoader
- (BOOL)canLoadImageURL:(NSURL *)requestURL
{
return [requestURL.scheme.lowercaseString isEqualToString:@"assets-library"];
}
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
[[self assetsLibrary] assetForURL:imageURL resultBlock:^(ALAsset *asset) {
if (asset) {
// ALAssetLibrary API is async and will be multi-threaded. Loading a few full
// resolution images at once will spike the memory up to store the image data,
// and might trigger memory warnings and/or OOM crashes.
// To improve this, process the loaded asset in a serial queue.
dispatch_async(RCTAssetsLibraryImageLoaderQueue(), ^{
// Also make sure the image is released immediately after it's used so it
// doesn't spike the memory up during the process.
@autoreleasepool {
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
ALAssetRepresentation *representation = [asset defaultRepresentation];
UIImage *image;
NSError *error = nil;
if (useMaximumSize) {
image = [UIImage imageWithCGImage:representation.fullResolutionImage
scale:scale
orientation:(UIImageOrientation)representation.orientation];
} else {
image = RCTScaledImageForAsset(representation, size, scale, resizeMode, &error);
}
completionHandler(error, image);
}
});
} else {
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageURL];
NSError *error = RCTErrorWithMessage(errorText);
completionHandler(error, nil);
}
} failureBlock:^(NSError *loadError) {
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageURL, loadError];
NSError *error = RCTErrorWithMessage(errorText);
completionHandler(error, nil);
}];
return ^{};
}
@end
@implementation RCTBridge (RCTAssetsLibraryImageLoader)
- (RCTAssetsLibraryImageLoader *)assetsLibraryImageLoader
{
return self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])];
}
- (ALAssetsLibrary *)assetsLibrary
{
return [self.assetsLibraryImageLoader assetsLibrary];
}
@end
static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void)
{
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.facebook.RCTAssetsLibraryImageLoader", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
// Why use a custom scaling method? Greater efficiency, reduced memory overhead:
// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, CGSize size, CGFloat scale, UIViewContentMode resizeMode, NSError **error)
{
NSUInteger length = (NSUInteger)representation.size;
NSMutableData *data = [NSMutableData dataWithLength:length];
if (![representation getBytes:data.mutableBytes
fromOffset:0
length:length
error:error]) {
return nil;
}
CGSize sourceSize = representation.dimensions;
CGSize targetSize = RCTTargetSize(sourceSize, representation.scale,
size, scale, resizeMode, NO);
NSDictionary *options = @{
(id)kCGImageSourceShouldAllowFloat: @YES,
(id)kCGImageSourceCreateThumbnailWithTransform: @YES,
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
(id)kCGImageSourceThumbnailMaxPixelSize: @(MAX(targetSize.width, targetSize.height) * scale)
};
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
if (sourceRef) {
CFRelease(sourceRef);
}
if (imageRef) {
UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale
orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
return image;
}
return nil;
}

View File

@ -14,6 +14,7 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "RCTAssetsLibraryImageLoader.h"
#import "RCTBridge.h"
#import "RCTImageLoader.h"
#import "RCTLog.h"

View File

@ -7,9 +7,8 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <QuartzCore/QuartzCore.h>
#import "RCTImageLoader.h"
extern CAKeyframeAnimation *RCTGIFImageWithData(NSData *data);
extern CAKeyframeAnimation *RCTGIFImageWithFileURL(NSURL *URL);
@interface RCTGIFImageDecoder : NSObject <RCTImageDecoder>
@end

View File

@ -7,16 +7,29 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTGIFImage.h"
#import "RCTGIFImageDecoder.h"
#import "RCTLog.h"
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>
#import <QuartzCore/QuartzCore.h>
static CAKeyframeAnimation *RCTGIFImageWithImageSource(CGImageSourceRef imageSource)
#import "RCTUtils.h"
@implementation RCTGIFImageDecoder
RCT_EXPORT_MODULE()
- (BOOL)canDecodeImageData:(NSData *)imageData
{
if (!UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)) {
return nil;
}
char header[7] = {};
[imageData getBytes:header length:6];
return !strcmp(header, "GIF87a") || !strcmp(header, "GIF89a");
}
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(imageSource, NULL);
NSUInteger loopCount = [properties[(id)kCGImagePropertyGIFDictionary][(id)kCGImagePropertyGIFLoopCount] unsignedIntegerValue];
@ -48,6 +61,7 @@ static CAKeyframeAnimation *RCTGIFImageWithImageSource(CGImageSourceRef imageSou
delays[i] = delayTime;
images[i] = (__bridge_transfer id)image;
}
CFRelease(imageSource);
NSMutableArray *keyTimes = [NSMutableArray arrayWithCapacity:delays.count];
NSTimeInterval runningDuration = 0;
@ -64,34 +78,9 @@ static CAKeyframeAnimation *RCTGIFImageWithImageSource(CGImageSourceRef imageSou
animation.keyTimes = keyTimes;
animation.values = images;
animation.duration = duration;
return animation;
completionHandler(nil, animation);
return nil;
}
CAKeyframeAnimation *RCTGIFImageWithData(NSData *data)
{
if (data.length == 0) {
return nil;
}
CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)data, NULL);
CAKeyframeAnimation *animation = RCTGIFImageWithImageSource(imageSource);
CFRelease(imageSource);
return animation;
}
CAKeyframeAnimation *RCTGIFImageWithFileURL(NSURL *URL)
{
if (!URL) {
return nil;
}
if (!URL.fileURL) {
RCTLogError(@"Loading remote image URLs synchronously is a really bad idea.");
return nil;
}
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)URL, NULL);
CAKeyframeAnimation *animation = RCTGIFImageWithImageSource(imageSource);
CFRelease(imageSource);
return animation;
}
@end

View File

@ -9,8 +9,7 @@
/* Begin PBXBuildFile section */
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTImageView.m */; };
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.m */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */; };
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */; };
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 137620341B31C53500677FF0 /* RCTImagePickerManager.m */; };
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */; };
@ -18,6 +17,9 @@
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; };
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */; };
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */; };
83DDA1571B8DCA5800892A1C /* RCTAssetBundleImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -37,10 +39,8 @@
1304D5A81AA8C4A30002E2BE /* RCTImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageView.m; sourceTree = "<group>"; };
1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageViewManager.h; sourceTree = "<group>"; };
1304D5AA1AA8C4A30002E2BE /* RCTImageViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageViewManager.m; sourceTree = "<group>"; };
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImage.h; sourceTree = "<group>"; };
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = "<group>"; };
1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = "<group>"; };
1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = "<group>"; };
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTGIFImageDecoder.h; sourceTree = "<group>"; };
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImageDecoder.m; sourceTree = "<group>"; };
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageUtils.h; sourceTree = "<group>"; };
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = "<group>"; };
137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = "<group>"; };
@ -56,6 +56,12 @@
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = "<group>"; };
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetsLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetsLibraryImageLoader.m; sourceTree = "<group>"; };
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPhotoLibraryImageLoader.h; sourceTree = "<group>"; };
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPhotoLibraryImageLoader.m; sourceTree = "<group>"; };
83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAssetBundleImageLoader.h; sourceTree = "<group>"; };
83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAssetBundleImageLoader.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -72,10 +78,14 @@
58B511541A9E6B3D00147676 = {
isa = PBXGroup;
children = (
83DDA1551B8DCA5800892A1C /* RCTAssetBundleImageLoader.h */,
83DDA1561B8DCA5800892A1C /* RCTAssetBundleImageLoader.m */,
8312EAEC1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.h */,
8312EAED1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m */,
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */,
143879341AAD238D00F088A5 /* RCTCameraRollManager.m */,
1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */,
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */,
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */,
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */,
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */,
354631661B69857700AA0B86 /* RCTImageEditingManager.h */,
@ -84,8 +94,6 @@
143879371AAD32A300F088A5 /* RCTImageLoader.m */,
137620331B31C53500677FF0 /* RCTImagePickerManager.h */,
137620341B31C53500677FF0 /* RCTImagePickerManager.m */,
1345A8371B26592900583190 /* RCTImageRequestHandler.h */,
1345A8381B26592900583190 /* RCTImageRequestHandler.m */,
1304D5A71AA8C4A30002E2BE /* RCTImageView.h */,
1304D5A81AA8C4A30002E2BE /* RCTImageView.m */,
1304D5A91AA8C4A30002E2BE /* RCTImageViewManager.h */,
@ -94,6 +102,8 @@
35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */,
134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
8312EAEF1B85F071001867A2 /* RCTPhotoLibraryImageLoader.h */,
8312EAF01B85F071001867A2 /* RCTPhotoLibraryImageLoader.m */,
58B5115E1A9E6B3D00147676 /* Products */,
);
indentWidth = 2;
@ -164,17 +174,19 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8312EAEE1B85EB7C001867A2 /* RCTAssetsLibraryImageLoader.m in Sources */,
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
8312EAF11B85F071001867A2 /* RCTPhotoLibraryImageLoader.m in Sources */,
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
137620351B31C53500677FF0 /* RCTImagePickerManager.m in Sources */,
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */,
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */,
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */,
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */,
1304D5AB1AA8C4A30002E2BE /* RCTImageView.m in Sources */,
134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,
83DDA1571B8DCA5800892A1C /* RCTAssetBundleImageLoader.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -7,25 +7,10 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTImageLoader.h"
@interface RCTImageDownloader : NSObject <RCTBridgeModule>
/**
* Downloads an image and decompresses it a the size specified. The compressed
* image will be cached in memory and to disk. Note that the callback block
* will not be executed on the same thread you called the method from, nor on
* the main thread. Returns a token that can be used to cancel the download.
*/
- (RCTImageLoaderCancellationBlock)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
completionBlock:(RCTImageLoaderCompletionBlock)block;
@interface RCTImageDownloader : NSObject <RCTImageURLLoader>
@end

View File

@ -9,7 +9,7 @@
#import "RCTImageDownloader.h"
#import "RCTGIFImage.h"
#import "RCTImageLoader.h"
#import "RCTImageUtils.h"
#import "RCTLog.h"
#import "RCTNetworking.h"
@ -25,16 +25,6 @@
RCT_EXPORT_MODULE()
+ (RCTImageDownloader *)sharedInstance
{
static RCTImageDownloader *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [RCTImageDownloader new];
});
return sharedInstance;
}
- (instancetype)init
{
if ((self = [super init])) {
@ -44,14 +34,23 @@ RCT_EXPORT_MODULE()
return self;
}
- (BOOL)canLoadImageURL:(NSURL *)requestURL
{
// Have to exclude 'file://' from the main bundle, otherwise this would conflict with RCTAssetBundleImageLoader
return
[requestURL.scheme compare:@"http" options:NSCaseInsensitiveSearch range:NSMakeRange(0, 4)] == NSOrderedSame ||
([requestURL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame && ![requestURL.path hasPrefix:[NSBundle mainBundle].resourcePath]) ||
[requestURL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame;
}
/**
* Downloads a block of raw data and returns it. Note that the callback block
* will not be executed on the same thread you called the method from, nor on
* the main thread. Returns a token that can be used to cancel the download.
*/
- (RCTImageLoaderCancellationBlock)downloadDataForURL:(NSURL *)url
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
progressHandler:(RCTImageLoaderProgressBlock)progressBlock
completionHandler:(RCTImageLoaderCompletionBlock)completionBlock
{
if (![_bridge respondsToSelector:NSSelectorFromString(@"networking")]) {
RCTLogError(@"You need to import the RCTNetworking library in order to download remote images.");
@ -99,49 +98,89 @@ RCT_EXPORT_MODULE()
return ^{ [task cancel]; };
}
- (RCTImageLoaderCancellationBlock)downloadImageForURL:(NSURL *)url
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
scale = scale ?: RCTScreenScale();
if ([imageURL.scheme.lowercaseString hasPrefix:@"http"]) {
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
return [self downloadDataForURL:url progressBlock:progressBlock completionBlock:^(NSError *error, id data) {
if (!data || error) {
completionBlock(error, nil);
return;
}
if ([url.path.lowercaseString hasSuffix:@".gif"]) {
id image = RCTGIFImageWithData(data);
if (!image && !error) {
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", url];
error = RCTErrorWithMessage(errorMessage);
__weak RCTImageDownloader *weakSelf = self;
RCTImageLoaderCancellationBlock downloadCancel = [self downloadDataForURL:imageURL progressHandler:progressHandler completionHandler:^(NSError *error, NSData *imageData) {
if (error) {
completionHandler(error, nil);
} else {
decodeCancel = [weakSelf.bridge.imageLoader decodeImageData:imageData size:size scale:scale resizeMode:resizeMode completionBlock:completionHandler];
}
completionBlock(error, image);
return;
}
}];
UIImage *image = [UIImage imageWithData:data scale:scale];
if (image && !CGSizeEqualToSize(size, CGSizeZero)) {
return ^{
downloadCancel();
// Get destination size
CGSize targetSize = RCTTargetSize(image.size, image.scale,
size, scale, resizeMode, NO);
if (decodeCancel) {
decodeCancel();
}
};
} else if ([imageURL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame) {
__block BOOL cancelled = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (cancelled) {
return;
}
// Decompress image at required size
BOOL opaque = !RCTImageHasAlpha(image.CGImage);
UIGraphicsBeginImageContextWithOptions(targetSize, opaque, scale);
[image drawInRect:(CGRect){CGPointZero, targetSize}];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
// Normally -dataWithContentsOfURL: would be bad but this is a data URL.
NSData *data = [NSData dataWithContentsOfURL:imageURL];
completionBlock(nil, image);
}];
UIImage *image = [UIImage imageWithData:data];
if (image) {
if (progressHandler) {
progressHandler(1, 1);
}
if (completionHandler) {
completionHandler(nil, image);
}
} else {
if (completionHandler) {
NSString *message = [NSString stringWithFormat:@"Invalid image data for URL: %@", imageURL];
completionHandler(RCTErrorWithMessage(message), nil);
}
}
});
return ^{
cancelled = YES;
};
} else if ([imageURL.scheme isEqualToString:@"file"]) {
__block BOOL cancelled = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (cancelled) {
return;
}
UIImage *image = [UIImage imageWithContentsOfFile:imageURL.resourceSpecifier];
if (image) {
if (progressHandler) {
progressHandler(1, 1);
}
if (completionHandler) {
completionHandler(nil, image);
}
} else {
if (completionHandler) {
NSString *message = [NSString stringWithFormat:@"Could not find image at path: %@", imageURL.absoluteString];
completionHandler(RCTErrorWithMessage(message), nil);
}
}
});
return ^{
cancelled = YES;
};
} else {
RCTLogError(@"Unexpected image schema %@", imageURL.scheme);
return ^{};
}
}
@end

View File

@ -10,14 +10,16 @@
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTURLRequestHandler.h"
@class ALAssetsLibrary;
typedef void (^RCTImageLoaderProgressBlock)(int64_t progress, int64_t total);
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* UIImage or CAAnimation */);
typedef void (^RCTImageLoaderCompletionBlock)(NSError *error, id image /* NSData, UIImage, CAAnimation */);
typedef void (^RCTImageLoaderCancellationBlock)(void);
@interface RCTImageLoader : NSObject <RCTBridgeModule>
@interface RCTImageLoader : NSObject <RCTBridgeModule, RCTURLRequestHandler>
/**
* Loads the specified image at the highest available resolution.
@ -38,14 +40,14 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
/**
* Is the specified image tag an asset library image?
* Finds an appropriate image decoder and passes the target size, scale and
* resizeMode for optimal image decoding.
*/
+ (BOOL)isAssetLibraryImage:(NSString *)imageTag;
/**
* Is the specified image tag a remote image?
*/
+ (BOOL)isRemoteImage:(NSString *)imageTag;
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock;
@end
@ -56,9 +58,72 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
*/
@property (nonatomic, readonly) RCTImageLoader *imageLoader;
@end
/**
* The shared asset library instance.
* Provides the interface needed to register an image data loader. Image data
* loaders are also bridge modules, so should be registered using
* RCT_EXPORT_MODULE().
*/
@property (nonatomic, readonly) ALAssetsLibrary *assetsLibrary;
@protocol RCTImageURLLoader <RCTBridgeModule>
/**
* Indicates whether this data loader is capable of processing the specified
* request URL. Typically the handler would examine the scheme/protocol of the
* URL to determine this.
*/
- (BOOL)canLoadImageURL:(NSURL *)requestURL;
/**
* Send a network request to load the request URL. The method should call the
* progressHandler (if applicable) and the completionHandler when the request
* has finished. The method should also return a cancellation block, if
* applicable.
*/
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
@optional
/**
* If more than one RCTImageURLLoader responds YES to `-canLoadImageURL:`
* then `imageLoaderPriority` is used to determine which one to use. The handler
* with the highest priority will be selected. Default priority is zero. If
* two or more valid handlers have the same priority, the selection order is
* undefined.
*/
- (float)imageLoaderPriority;
@end
/**
* Provides the interface needed to register an image decoder. Image decoders
* are also bridge modules, so should be registered using RCT_EXPORT_MODULE().
*/
@protocol RCTImageDecoder <RCTBridgeModule>
/**
* Indicates whether this handler is capable of decoding the specified data.
* Typically the handler would examine some sort of header data to determine
* this.
*/
- (BOOL)canDecodeImageData:(NSData *)imageData;
/**
* Decode an image from the data object. The method should call the
* completionHandler when the decoding operation has finished. The method
* should also return a cancellation block, if applicable.
*/
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
@optional
/**
* If more than one RCTImageDecoder responds YES to `-canDecodeImageData:`
* then `imageDecoderPriority` is used to determine which one to use. The
* handler with the highest priority will be selected. Default priority is zero.
* If two or more valid handlers have the same priority, the selection order is
* undefined.
*/
- (float)imageDecoderPriority;
@end

View File

@ -9,19 +9,11 @@
#import "RCTImageLoader.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import <Photos/PHAsset.h>
#import <Photos/PHFetchResult.h>
#import <Photos/PHImageManager.h>
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTDefines.h"
#import "RCTGIFImage.h"
#import "RCTImageDownloader.h"
#import "RCTImageStoreManager.h"
#import "RCTImageUtils.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@ -36,21 +28,7 @@ static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSEr
}
}
static dispatch_queue_t RCTImageLoaderQueue(void)
{
static dispatch_queue_t queue = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.facebook.rctImageLoader", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
@implementation RCTImageLoader
{
ALAssetsLibrary *_assetsLibrary;
}
@synthesize bridge = _bridge;
@ -67,56 +45,33 @@ RCT_EXPORT_MODULE()
completionBlock:callback];
}
// Why use a custom scaling method? Greater efficiency, reduced memory overhead:
// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
CGSize size, CGFloat scale,
UIViewContentMode resizeMode,
NSError **error)
- (id<RCTImageURLLoader>)imageURLLoaderForRequest:(NSURL *)requestURL
{
NSUInteger length = (NSUInteger)representation.size;
NSMutableData *data = [NSMutableData dataWithLength:length];
if (![representation getBytes:data.mutableBytes
fromOffset:0
length:length
error:error]) {
return nil;
NSMutableArray *handlers = [NSMutableArray array];
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
if ([(id<RCTImageURLLoader>)module canLoadImageURL:requestURL]) {
[handlers addObject:module];
}
}
}
[handlers sortUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
float priorityA = [a respondsToSelector:@selector(imageLoaderPriority)] ? [a imageLoaderPriority] : 0;
float priorityB = [b respondsToSelector:@selector(imageLoaderPriority)] ? [b imageLoaderPriority] : 0;
if (priorityA < priorityB) {
return NSOrderedAscending;
} else if (priorityA > priorityB) {
return NSOrderedDescending;
} else {
RCTLogError(@"The RCTImageLoader %@ and %@ both reported that they can"
" handle the load request %@, and have equal priority (%g)."
" This could result in non-deterministic behavior.",
a, b, requestURL, priorityA);
CGSize sourceSize = representation.dimensions;
CGSize targetSize = RCTTargetSize(sourceSize, representation.scale,
size, scale, resizeMode, NO);
NSDictionary *options = @{
(id)kCGImageSourceShouldAllowFloat: @YES,
(id)kCGImageSourceCreateThumbnailWithTransform: @YES,
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
(id)kCGImageSourceThumbnailMaxPixelSize: @(MAX(targetSize.width, targetSize.height) * scale)
};
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
if (sourceRef) {
CFRelease(sourceRef);
}
if (imageRef) {
UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale
orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
return image;
}
return nil;
}
- (ALAssetsLibrary *)assetsLibrary
{
if (!_assetsLibrary) {
_assetsLibrary = [ALAssetsLibrary new];
}
return _assetsLibrary;
return NSOrderedSame;
}
}];
return [handlers lastObject];
}
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
@ -126,141 +81,134 @@ static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
if ([imageTag hasPrefix:@"assets-library://"]) {
[[self assetsLibrary] assetForURL:[RCTConvert NSURL:imageTag] resultBlock:^(ALAsset *asset) {
if (asset) {
// ALAssetLibrary API is async and will be multi-threaded. Loading a few full
// resolution images at once will spike the memory up to store the image data,
// and might trigger memory warnings and/or OOM crashes.
// To improve this, process the loaded asset in a serial queue.
dispatch_async(RCTImageLoaderQueue(), ^{
// Also make sure the image is released immediately after it's used so it
// doesn't spike the memory up during the process.
@autoreleasepool {
NSURL *requestURL = [RCTConvert NSURL:imageTag];
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForRequest:requestURL];
if (!loadHandler) {
RCTLogError(@"No suitable image URL loader found for %@", imageTag);
}
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
ALAssetRepresentation *representation = [asset defaultRepresentation];
UIImage *image;
NSError *error = nil;
if (useMaximumSize) {
image = [UIImage imageWithCGImage:representation.fullResolutionImage
scale:scale
orientation:(UIImageOrientation)representation.orientation];
} else {
image = RCTScaledImageForAsset(representation, size, scale, resizeMode, &error);
}
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
}
});
} else {
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageTag];
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
}
} failureBlock:^(NSError *loadError) {
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageTag, loadError];
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
}];
return ^{};
} else if ([imageTag hasPrefix:@"ph://"]) {
// Using PhotoKit for iOS 8+
// The 'ph://' prefix is used by FBMediaKit to differentiate between
// assets-library. It is prepended to the local ID so that it is in the
// form of an, NSURL which is what assets-library uses.
NSString *phAssetID = [imageTag substringFromIndex:@"ph://".length];
PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil];
if (results.count == 0) {
NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", phAssetID];
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
return ^{};
return [loadHandler loadImageForURL:requestURL size:size scale:scale resizeMode:resizeMode progressHandler:^(int64_t progress, int64_t total) {
if (!progressBlock) {
return;
}
PHAsset *asset = results.firstObject;
PHImageRequestOptions *imageOptions = [PHImageRequestOptions new];
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
CGSize targetSize;
if ( useMaximumSize ){
targetSize = PHImageManagerMaximumSize;
imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
if ([NSThread isMainThread]) {
progressBlock(progress, total);
} else {
targetSize = size;
imageOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
dispatch_async(dispatch_get_main_queue(), ^{
progressBlock(progress, total);
});
}
} completionHandler:^(NSError *error, id image) {
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
}] ?: ^{};
}
PHImageContentMode contentMode = PHImageContentModeAspectFill;
if (resizeMode == UIViewContentModeScaleAspectFit) {
contentMode = PHImageContentModeAspectFit;
}
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:imageOptions resultHandler:^(UIImage *result, NSDictionary *info) {
if (result) {
RCTDispatchCallbackOnMainQueue(completionBlock, nil, result);
} else {
NSString *errorText = [NSString stringWithFormat:@"Failed to load PHAsset with local identifier %@ with no error message.", phAssetID];
NSError *error = RCTErrorWithMessage(errorText);
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
return;
- (id<RCTImageDecoder>)imageDecoderForRequest:(NSData *)imageData
{
NSMutableArray *handlers = [NSMutableArray array];
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
if ([module conformsToProtocol:@protocol(RCTImageDecoder)]) {
if ([(id<RCTImageDecoder>)module canDecodeImageData:imageData]) {
[handlers addObject:module];
}
}];
return ^{};
} else if ([imageTag hasPrefix:@"http"]) {
NSURL *url = [RCTConvert NSURL:imageTag];
if (!url) {
NSString *errorMessage = [NSString stringWithFormat:@"Invalid URL: %@", imageTag];
RCTDispatchCallbackOnMainQueue(completionBlock, RCTErrorWithMessage(errorMessage), nil);
return ^{};
}
return [_bridge.imageDownloader downloadImageForURL:url size:size scale:scale resizeMode:resizeMode progressBlock:progressBlock completionBlock:^(NSError *error, id image) {
}
[handlers sortUsingComparator:^NSComparisonResult(id<RCTImageDecoder> a, id<RCTImageDecoder> b) {
float priorityA = [a respondsToSelector:@selector(imageDecoderPriority)] ? [a imageDecoderPriority] : 0;
float priorityB = [b respondsToSelector:@selector(imageDecoderPriority)] ? [b imageDecoderPriority] : 0;
if (priorityA < priorityB) {
return NSOrderedAscending;
} else if (priorityA > priorityB) {
return NSOrderedDescending;
} else {
RCTLogError(@"The RCTImageDecoder %@ and %@ both reported that they can"
" handle the decode request <NSData %p; %tu bytes>, and have"
" equal priority (%g). This could result in"
" non-deterministic behavior.",
a, b, imageData, imageData.length, priorityA);
return NSOrderedSame;
}
}];
return [handlers lastObject];
}
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(UIViewContentMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
id<RCTImageDecoder> imageDecoder = [self imageDecoderForRequest:data];
if (imageDecoder) {
return [imageDecoder decodeImageData:data size:size scale:scale resizeMode:resizeMode completionHandler:^(NSError *error, id image) {
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
}];
} else if ([imageTag hasPrefix:@"rct-image-store://"]) {
[_bridge.imageStoreManager getImageForTag:imageTag withBlock:^(UIImage *image) {
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:data];
if (image) {
RCTDispatchCallbackOnMainQueue(completionBlock, nil, image);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load image from image store: %@", imageTag];
NSError *error = RCTErrorWithMessage(errorMessage);
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
NSError *finalError = RCTErrorWithMessage(errorMessage);
RCTDispatchCallbackOnMainQueue(completionBlock, finalError, nil);
}
}];
return ^{};
} else if ([imageTag.lowercaseString hasSuffix:@".gif"]) {
id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
if (image) {
RCTDispatchCallbackOnMainQueue(completionBlock, nil, image);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
NSError *error = RCTErrorWithMessage(errorMessage);
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
}
return ^{};
} else {
UIImage *image = [RCTConvert UIImage:imageTag];
if (image) {
RCTDispatchCallbackOnMainQueue(completionBlock, nil, image);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Unrecognized tag protocol: %@", imageTag];
NSError *error = RCTErrorWithMessage(errorMessage);
RCTDispatchCallbackOnMainQueue(completionBlock, error, nil);
}
});
return ^{};
}
}
+ (BOOL)isAssetLibraryImage:(NSString *)imageTag
#pragma mark - RCTURLRequestHandler
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [imageTag hasPrefix:@"assets-library://"] || [imageTag hasPrefix:@"ph://"];
id<RCTImageURLLoader> handler = [self imageURLLoaderForRequest:request.URL];
// RCTImageDownloader is an image plugin that uses the networking stack.
// We don't want to route any network calls through the image downloader
// as that would cause cyclical dependencies.
return handler && ![handler isKindOfClass:[RCTImageDownloader class]];
}
+ (BOOL)isRemoteImage:(NSString *)imageTag
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
{
return [imageTag hasPrefix:@"http://"] || [imageTag hasPrefix:@"https://"];
__block RCTImageLoaderCancellationBlock requestToken;
requestToken = [self.bridge.imageLoader loadImageWithTag:request.URL.absoluteString callback:^(NSError *error, UIImage *image) {
if (error) {
[delegate URLRequest:requestToken didCompleteWithError:error];
return;
}
NSString *mimeType = nil;
NSData *imageData = nil;
if (RCTImageHasAlpha(image.CGImage)) {
mimeType = @"image/png";
imageData = UIImagePNGRepresentation(image);
} else {
mimeType = @"image/jpeg";
imageData = UIImageJPEGRepresentation(image, 1.0);
}
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:mimeType
expectedContentLength:imageData.length
textEncodingName:nil];
[delegate URLRequest:requestToken didReceiveResponse:response];
[delegate URLRequest:requestToken didReceiveData:imageData];
[delegate URLRequest:requestToken didCompleteWithError:nil];
}];
return requestToken;
}
- (void)cancelRequest:(id)requestToken
{
if (requestToken) {
((RCTImageLoaderCancellationBlock)requestToken)();
}
}
@end
@ -272,9 +220,4 @@ static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation,
return self.modules[RCTBridgeModuleNameForClass([RCTImageLoader class])];
}
- (ALAssetsLibrary *)assetsLibrary
{
return [self.imageLoader assetsLibrary];
}
@end

View File

@ -1,70 +0,0 @@
//
// RCTImageRequestHandler.m
// RCTImage
//
// Created by Nick Lockwood on 09/06/2015.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import "RCTImageRequestHandler.h"
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTImageLoader.h"
#import "RCTUtils.h"
@implementation RCTImageRequestHandler
RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return [@[@"assets-library", @"ph"] containsObject:request.URL.scheme.lowercaseString];
}
- (id)sendRequest:(NSURLRequest *)request
withDelegate:(id<RCTURLRequestDelegate>)delegate
{
NSString *URLString = request.URL.absoluteString;
__block RCTImageLoaderCancellationBlock requestToken = nil;
requestToken = [_bridge.imageLoader loadImageWithTag:URLString callback:^(NSError *error, UIImage *image) {
if (error) {
[delegate URLRequest:requestToken didCompleteWithError:error];
return;
}
NSString *mimeType = nil;
NSData *imageData = nil;
if (RCTImageHasAlpha(image.CGImage)) {
mimeType = @"image/png";
imageData = UIImagePNGRepresentation(image);
} else {
mimeType = @"image/jpeg";
imageData = UIImageJPEGRepresentation(image, 1.0);
}
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:mimeType
expectedContentLength:imageData.length
textEncodingName:nil];
[delegate URLRequest:requestToken didReceiveResponse:response];
[delegate URLRequest:requestToken didReceiveData:imageData];
[delegate URLRequest:requestToken didCompleteWithError:nil];
}];
return requestToken;
}
- (void)cancelRequest:(id /* RCTImageLoaderCancellationBlock */)requestToken
{
if (requestToken) {
((RCTImageLoaderCancellationBlock)requestToken)();
}
}
@end

View File

@ -3,9 +3,10 @@
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTImageLoader.h"
#import "RCTURLRequestHandler.h"
@interface RCTImageStoreManager : NSObject<RCTURLRequestHandler>
@interface RCTImageStoreManager : NSObject <RCTImageURLLoader>
/**
* Set and get cached images. These must be called from the main thread.

View File

@ -97,44 +97,27 @@ RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
}
}
#pragma mark - RCTURLRequestHandler
#pragma mark - RCTImageLoader
- (BOOL)canHandleRequest:(NSURLRequest *)request
- (BOOL)canLoadImageURL:(NSURL *)requestURL
{
return [@[@"rct-image-store"] containsObject:request.URL.scheme.lowercaseString];
return [requestURL.scheme.lowercaseString isEqualToString:@"rct-image-store"];
}
- (id)sendRequest:(NSURLRequest *)request
withDelegate:(id<RCTURLRequestDelegate>)delegate
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
NSString *imageTag = request.URL.absoluteString;
NSString *imageTag = imageURL.absoluteString;
[self getImageForTag:imageTag withBlock:^(UIImage *image) {
if (!image) {
NSError *error = RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]);
[delegate URLRequest:request didCompleteWithError:error];
return;
}
NSString *mimeType = nil;
NSData *imageData = nil;
if (RCTImageHasAlpha(image.CGImage)) {
mimeType = @"image/png";
imageData = UIImagePNGRepresentation(image);
if (image) {
completionHandler(nil, image);
} else {
mimeType = @"image/jpeg";
imageData = UIImageJPEGRepresentation(image, 1.0);
NSString *errorMessage = [NSString stringWithFormat:@"Unable to load image from image store: %@", imageTag];
NSError *error = RCTErrorWithMessage(errorMessage);
completionHandler(error, nil);
}
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:mimeType
expectedContentLength:imageData.length
textEncodingName:nil];
[delegate URLRequest:request didReceiveResponse:response];
[delegate URLRequest:request didReceiveData:imageData];
[delegate URLRequest:request didCompleteWithError:nil];
}];
return request;
return nil;
}
@end

View File

@ -12,7 +12,6 @@
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTGIFImage.h"
#import "RCTImageLoader.h"
#import "RCTImageUtils.h"
#import "RCTUtils.h"
@ -21,11 +20,11 @@
@interface RCTImageView ()
@property (nonatomic, assign) BOOL onLoadStart;
@property (nonatomic, assign) BOOL onProgress;
@property (nonatomic, assign) BOOL onError;
@property (nonatomic, assign) BOOL onLoad;
@property (nonatomic, assign) BOOL onLoadEnd;
@property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
@property (nonatomic, copy) RCTDirectEventBlock onProgress;
@property (nonatomic, copy) RCTDirectEventBlock onError;
@property (nonatomic, copy) RCTDirectEventBlock onLoad;
@property (nonatomic, copy) RCTDirectEventBlock onLoadEnd;
@end
@ -101,11 +100,20 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
}
}
+ (BOOL)srcNeedsReload:(NSString *)src
{
return
[src hasPrefix:@"http://"] ||
[src hasPrefix:@"https://"] ||
[src hasPrefix:@"assets-library://"] ||
[src hasPrefix:@"ph://"];
}
- (void)setContentMode:(UIViewContentMode)contentMode
{
if (self.contentMode != contentMode) {
super.contentMode = contentMode;
if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) {
if ([RCTImageView srcNeedsReload:_src]) {
[self reloadImage];
}
}
@ -116,19 +124,16 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
if (_onLoadStart) {
NSDictionary *event = @{ @"target": self.reactTag };
[_bridge.eventDispatcher sendInputEventWithName:@"loadStart" body:event];
_onLoadStart(nil);
}
RCTImageLoaderProgressBlock progressHandler = nil;
if (_onProgress) {
progressHandler = ^(int64_t loaded, int64_t total) {
NSDictionary *event = @{
@"target": self.reactTag,
_onProgress(@{
@"loaded": @((double)loaded),
@"total": @((double)total),
};
[_bridge.eventDispatcher sendInputEventWithName:@"progress" body:event];
});
};
}
@ -147,21 +152,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
}
if (error) {
if (_onError) {
NSDictionary *event = @{
@"target": self.reactTag,
@"error": error.localizedDescription,
};
[_bridge.eventDispatcher sendInputEventWithName:@"error" body:event];
_onError(@{ @"error": error.localizedDescription });
}
} else {
if (_onLoad) {
NSDictionary *event = @{ @"target": self.reactTag };
[_bridge.eventDispatcher sendInputEventWithName:@"load" body:event];
_onLoad(nil);
}
}
if (_onLoadEnd) {
NSDictionary *event = @{ @"target": self.reactTag };
[_bridge.eventDispatcher sendInputEventWithName:@"loadEnd" body:event];
_onLoadEnd(nil);
}
}];
} else {
@ -175,7 +174,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
[super reactSetFrame:frame];
if (self.image == nil) {
[self reloadImage];
} else if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) {
} else if ([RCTImageView srcNeedsReload:_src]) {
// Get optimal image size
CGSize currentSize = self.image.size;

View File

@ -27,11 +27,11 @@ RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
RCT_EXPORT_VIEW_PROPERTY(src, NSString)
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onProgress, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onError, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onLoad, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
{
if (json) {
@ -43,15 +43,4 @@ RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
}
}
- (NSArray *)customDirectEventTypes
{
return @[
@"loadStart",
@"progress",
@"error",
@"load",
@"loadEnd",
];
}
@end

View File

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

View File

@ -0,0 +1,82 @@
/**
* 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 "RCTPhotoLibraryImageLoader.h"
#import <Photos/Photos.h>
#import "RCTImageUtils.h"
#import "RCTUtils.h"
@implementation RCTPhotoLibraryImageLoader
RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
#pragma mark - RCTImageLoader
- (BOOL)canLoadImageURL:(NSURL *)requestURL
{
return [requestURL.scheme.lowercaseString isEqualToString:@"ph"];
}
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
// Using PhotoKit for iOS 8+
// The 'ph://' prefix is used by FBMediaKit to differentiate between
// assets-library. It is prepended to the local ID so that it is in the
// form of an, NSURL which is what assets-library uses.
NSString *phAssetID = [imageURL.absoluteString substringFromIndex:[@"ph://" length]];
PHFetchResult *results = [PHAsset fetchAssetsWithLocalIdentifiers:@[phAssetID] options:nil];
if (results.count == 0) {
NSString *errorText = [NSString stringWithFormat:@"Failed to fetch PHAsset with local identifier %@ with no error message.", phAssetID];
NSError *error = RCTErrorWithMessage(errorText);
completionHandler(error, nil);
return ^{};
}
PHAsset *asset = [results firstObject];
PHImageRequestOptions *imageOptions = [PHImageRequestOptions new];
imageOptions.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
static const double multiplier = 1e6;
progressHandler(progress * multiplier, multiplier);
};
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
CGSize targetSize;
if (useMaximumSize) {
targetSize = PHImageManagerMaximumSize;
imageOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
} else {
targetSize = size;
imageOptions.resizeMode = PHImageRequestOptionsResizeModeFast;
}
PHImageContentMode contentMode = PHImageContentModeAspectFill;
if (resizeMode == UIViewContentModeScaleAspectFit) {
contentMode = PHImageContentModeAspectFit;
}
PHImageRequestID requestID = [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:imageOptions resultHandler:^(UIImage *result, NSDictionary *info) {
if (result) {
completionHandler(nil, result);
} else {
completionHandler(info[PHImageErrorKey], nil);
}
}];
return ^{
[[PHImageManager defaultManager] cancelImageRequest:requestID];
};
}
@end

View File

@ -30,7 +30,7 @@ var DEVICE_NOTIF_EVENT = 'openURL';
*
* #### Handling deep links
*
* If your app was launched from a external url registered to your app you can
* If your app was launched from an external url registered to your app you can
* access and handle it from any component you want with
*
* ```
@ -127,7 +127,7 @@ class LinkingIOS {
}
/**
* Determine wether or not the an installed app can handle a given `url`
* Determine wether or not an installed app can handle a given `url`
* The callback function will be called with `bool supported` as the only argument
*/
static canOpenURL(url: string, callback: Function) {

View File

@ -35,6 +35,7 @@
_textView = [[UITextView alloc] initWithFrame:self.bounds];
_textView.backgroundColor = [UIColor clearColor];
_textView.scrollsToTop = NO;
_textView.delegate = self;
[self addSubview:_textView];
}

View File

@ -664,22 +664,23 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
// verify that class has been registered
(void)_modulesByName[moduleData.name];
}
NSMutableOrderedSet *set = [buckets objectForKey:moduleData];
id queue = [moduleData queue];
NSMutableOrderedSet *set = [buckets objectForKey:queue];
if (!set) {
set = [NSMutableOrderedSet new];
[buckets setObject:set forKey:moduleData];
[buckets setObject:set forKey:queue];
}
[set addObject:@(i)];
}
for (RCTModuleData *moduleData in buckets) {
for (id queue in buckets) {
RCTProfileBeginFlowEvent();
[moduleData dispatchBlock:^{
dispatch_block_t block = ^{
RCTProfileEndFlowEvent();
RCTProfileBeginEvent(0, RCTCurrentThreadName(), nil);
NSOrderedSet *calls = [buckets objectForKey:moduleData];
NSOrderedSet *calls = [buckets objectForKey:queue];
@autoreleasepool {
for (NSNumber *indexObj in calls) {
NSUInteger index = indexObj.unsignedIntegerValue;
@ -693,7 +694,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithBundleURL:(__unused NSURL *)bundleUR
RCTProfileEndEvent(0, @"objc_call,dispatch_async", @{
@"calls": @(calls.count),
});
}];
};
if (queue == RCTJSThread) {
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
} else if (queue) {
dispatch_async(queue, block);
}
}
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?

View File

@ -38,7 +38,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
// 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];
NSString *desc = [@"Could not connect to development server.\n\nEnsure the following:\n- Node server is running and available on the same network - run 'npm start' from react-native root\n- Node server URL is correctly set in AppDelegate\n\nURL: " stringByAppendingString:scriptURL.absoluteString];
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: desc,
NSLocalizedFailureReasonErrorKey: error.localizedDescription,

View File

@ -62,7 +62,7 @@ IMPLEMENT_KEYBOARD_HANDLER(keyboardDidChangeFrame)
@end
static NSDictionary *RCTRectDictionaryValue(CGRect rect)
NS_INLINE NSDictionary *RCTRectDictionaryValue(CGRect rect)
{
return @{
@"screenX": @(rect.origin.x),
@ -72,16 +72,34 @@ static NSDictionary *RCTRectDictionaryValue(CGRect rect)
};
}
static NSString *RCTAnimationNameForCurve(UIViewAnimationCurve curve)
{
switch (curve) {
case UIViewAnimationCurveEaseIn:
return @"easeIn";
case UIViewAnimationCurveEaseInOut:
return @"easeInEaseOut";
case UIViewAnimationCurveEaseOut:
return @"easeOut";
case UIViewAnimationCurveLinear:
return @"linear";
default:
return @"keyboard";
}
}
static NSDictionary *RCTParseKeyboardNotification(NSNotification *notification)
{
NSDictionary *userInfo = notification.userInfo;
CGRect beginFrame = [userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGRect endFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
return @{
@"startCoordinates": RCTRectDictionaryValue(beginFrame),
@"endCoordinates": RCTRectDictionaryValue(endFrame),
@"duration": @(duration * 1000.0), // ms
@"easing": RCTAnimationNameForCurve(curve),
};
}

View File

@ -66,13 +66,15 @@ RCT_EXPORT_MODULE()
- (void)setUp
{
if (!_webView) {
_webView = [UIWebView new];
[self executeBlockOnJavaScriptQueue:^{
_webView = [UIWebView new];
_webView.delegate = self;
}];
}
_objectsToInject = [NSMutableDictionary new];
_commentsRegex = [NSRegularExpression regularExpressionWithPattern:@"(^ *?\\/\\/.*?$|\\/\\*\\*[\\s\\S]*?\\*\\/)" options:NSRegularExpressionAnchorsMatchLines error:NULL],
_scriptTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\/?script[^>]*?)>" options:0 error:NULL],
_webView.delegate = self;
_commentsRegex = [NSRegularExpression regularExpressionWithPattern:@"(^ *?\\/\\/.*?$|\\/\\*\\*[\\s\\S]*?\\*\\/)" options:NSRegularExpressionAnchorsMatchLines error:NULL];
_scriptTagsRegex = [NSRegularExpression regularExpressionWithPattern:@"<(\\/?script[^>]*?)>" options:0 error:NULL];
}
- (void)invalidate

View File

@ -34,4 +34,10 @@ RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback
}
}
- (NSDictionary *)constantsToExport
{
NSString *URL = self.bridge.bundleURL.absoluteString ?: @"";
return @{@"scriptURL": URL};
}
@end

View File

@ -448,26 +448,12 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
NSMutableArray *frames = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
NSMutableArray *areNew = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
NSMutableArray *parentsAreNew = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
NSMutableArray *onLayoutEvents = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
for (RCTShadowView *shadowView in viewsWithNewFrames) {
[frameReactTags addObject:shadowView.reactTag];
[frames addObject:[NSValue valueWithCGRect:shadowView.frame]];
[areNew addObject:@(shadowView.isNewView)];
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
id event = (id)kCFNull;
if (shadowView.onLayout) {
event = @{
@"target": shadowView.reactTag,
@"layout": @{
@"x": @(shadowView.frame.origin.x),
@"y": @(shadowView.frame.origin.y),
@"width": @(shadowView.frame.size.width),
@"height": @(shadowView.frame.size.height),
},
};
}
[onLayoutEvents addObject:event];
}
for (RCTShadowView *shadowView in viewsWithNewFrames) {
@ -483,7 +469,20 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
for (RCTShadowView *shadowView in viewsWithNewFrames) {
RCTViewManager *manager = [_componentDataByName[shadowView.viewName] manager];
RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView];
if (block) [updateBlocks addObject:block];
if (shadowView.onLayout) {
CGRect frame = shadowView.frame;
shadowView.onLayout(@{
@"layout": @{
@"x": @(frame.origin.x),
@"y": @(frame.origin.y),
@"width": @(frame.size.width),
@"height": @(frame.size.height),
},
});
}
if (block) {
[updateBlocks addObject:block];
}
}
// Perform layout (possibly animated)
@ -494,7 +493,6 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
NSNumber *reactTag = frameReactTags[ii];
UIView *view = viewRegistry[reactTag];
CGRect frame = [frames[ii] CGRectValue];
id event = onLayoutEvents[ii];
BOOL isNew = [areNew[ii] boolValue];
RCTAnimation *updateAnimation = isNew ? nil : _layoutAnimation.updateAnimation;
@ -503,9 +501,6 @@ extern NSString *RCTBridgeModuleNameForClass(Class cls);
void (^completion)(BOOL) = ^(BOOL finished) {
completionsCalled++;
if (event != (id)kCFNull) {
[self.bridge.eventDispatcher sendInputEventWithName:@"layout" body:event];
}
if (callback && completionsCalled == frames.count - 1) {
callback(@[@(finished)]);
}
@ -1128,67 +1123,68 @@ RCT_EXPORT_METHOD(clearJSResponder)
}];
}
- (NSDictionary *)bubblingEventsConfig
{
NSMutableDictionary *customBubblingEventTypesConfigs = [NSMutableDictionary new];
for (RCTComponentData *componentData in _componentDataByName.allValues) {
RCTViewManager *manager = componentData.manager;
if (RCTClassOverridesInstanceMethod([manager class], @selector(customBubblingEventTypes))) {
NSArray *events = [manager customBubblingEventTypes];
if (RCT_DEBUG) {
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
@"customBubblingEventTypes must return an array, but %@ returned %@",
[manager class], [events class]);
}
for (NSString *eventName in events) {
NSString *topName = RCTNormalizeInputEventName(eventName);
if (!customBubblingEventTypesConfigs[topName]) {
NSString *bubbleName = [topName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
customBubblingEventTypesConfigs[topName] = @{
@"phasedRegistrationNames": @{
@"bubbled": bubbleName,
@"captured": [bubbleName stringByAppendingString:@"Capture"],
}
};
}
}
}
};
return customBubblingEventTypesConfigs;
}
- (NSDictionary *)directEventsConfig
{
NSMutableDictionary *customDirectEventTypes = [NSMutableDictionary new];
for (RCTComponentData *componentData in _componentDataByName.allValues) {
RCTViewManager *manager = componentData.manager;
if (RCTClassOverridesInstanceMethod([manager class], @selector(customDirectEventTypes))) {
NSArray *events = [manager customDirectEventTypes];
if (RCT_DEBUG) {
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
@"customDirectEventTypes must return an array, but %@ returned %@",
[manager class], [events class]);
}
for (NSString *eventName in events) {
NSString *topName = RCTNormalizeInputEventName(eventName);
if (!customDirectEventTypes[topName]) {
customDirectEventTypes[topName] = @{
@"registrationName": [topName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
};
}
}
}
};
return customDirectEventTypes;
}
- (NSDictionary *)constantsToExport
{
NSMutableDictionary *allJSConstants = [@{
@"customBubblingEventTypes": [self bubblingEventsConfig],
@"customDirectEventTypes": [self directEventsConfig],
NSMutableDictionary *allJSConstants = [NSMutableDictionary new];
NSMutableDictionary *directEvents = [NSMutableDictionary new];
NSMutableDictionary *bubblingEvents = [NSMutableDictionary new];
[_componentDataByName enumerateKeysAndObjectsUsingBlock:
^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
RCTViewManager *manager = componentData.manager;
NSMutableDictionary *constantsNamespace =
[NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
// Add custom constants
// TODO: should these be inherited?
NSDictionary *constants = RCTClassOverridesInstanceMethod([manager class], @selector(constantsToExport)) ? [manager constantsToExport] : nil;
if (constants.count) {
RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
// add an additional 'Constants' namespace for each class
constantsNamespace[@"Constants"] = constants;
}
// Add native props
NSDictionary *viewConfig = [componentData viewConfig];
constantsNamespace[@"NativeProps"] = viewConfig[@"propTypes"];
// Add direct events
for (NSString *eventName in viewConfig[@"directEvents"]) {
if (!directEvents[eventName]) {
directEvents[eventName] = @{
@"registrationName": [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"],
};
}
if (RCT_DEBUG && bubblingEvents[eventName]) {
RCTLogError(@"Component '%@' re-registered bubbling event '%@' as a "
"direct event", componentData.name, eventName);
}
}
// Add bubbling events
for (NSString *eventName in viewConfig[@"bubblingEvents"]) {
if (!bubblingEvents[eventName]) {
NSString *bubbleName = [eventName stringByReplacingCharactersInRange:(NSRange){0, 3} withString:@"on"];
bubblingEvents[eventName] = @{
@"phasedRegistrationNames": @{
@"bubbled": bubbleName,
@"captured": [bubbleName stringByAppendingString:@"Capture"],
}
};
}
if (RCT_DEBUG && directEvents[eventName]) {
RCTLogError(@"Component '%@' re-registered direct event '%@' as a "
"bubbling event", componentData.name, eventName);
}
}
allJSConstants[name] = [constantsNamespace copy];
}];
[allJSConstants addEntriesFromDictionary:@{
@"customBubblingEventTypes": bubblingEvents,
@"customDirectEventTypes": directEvents,
@"Dimensions": @{
@"window": @{
@"width": @(RCTScreenSize().width),
@ -1200,28 +1196,8 @@ RCT_EXPORT_METHOD(clearJSResponder)
@"height": @(RCTScreenSize().height),
},
},
} mutableCopy];
[_componentDataByName enumerateKeysAndObjectsUsingBlock:
^(NSString *name, RCTComponentData *componentData, __unused BOOL *stop) {
RCTViewManager *manager = componentData.manager;
NSMutableDictionary *constantsNamespace =
[NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
// Add custom constants
// TODO: should these be inherited?
NSDictionary *constants = RCTClassOverridesInstanceMethod([manager class], @selector(constantsToExport)) ? [manager constantsToExport] : nil;
if (constants.count) {
RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
// add an additional 'Constants' namespace for each class
constantsNamespace[@"Constants"] = constants;
}
// Add native props
constantsNamespace[@"NativeProps"] = [componentData viewConfig];
allJSConstants[name] = [constantsNamespace copy];
}];
return allJSConstants;
}

View File

@ -10,6 +10,7 @@
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */ = {isa = PBXBuildFile; fileRef = 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */; };
131B6AF41AF1093D00FFC3E0 /* RCTSegmentedControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */; };
131B6AF51AF1093D00FFC3E0 /* RCTSegmentedControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */; };
133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */ = {isa = PBXBuildFile; fileRef = 133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */; };
13456E931ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */; };
13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */; };
134FCB361A6D42D900051CC8 /* RCTSparseArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 83BEE46D1A6D19BC00B5863B /* RCTSparseArray.m */; };
@ -104,6 +105,8 @@
131B6AF11AF1093D00FFC3E0 /* RCTSegmentedControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControl.m; sourceTree = "<group>"; };
131B6AF21AF1093D00FFC3E0 /* RCTSegmentedControlManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTSegmentedControlManager.h; sourceTree = "<group>"; };
131B6AF31AF1093D00FFC3E0 /* RCTSegmentedControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSegmentedControlManager.m; sourceTree = "<group>"; };
133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDatePicker.h; sourceTree = "<group>"; };
133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDatePicker.m; sourceTree = "<group>"; };
13442BF21AA90E0B0037E5B0 /* RCTAnimationType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationType.h; sourceTree = "<group>"; };
13442BF31AA90E0B0037E5B0 /* RCTPointerEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPointerEvents.h; sourceTree = "<group>"; };
13442BF41AA90E0B0037E5B0 /* RCTViewControllerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewControllerProtocol.h; sourceTree = "<group>"; };
@ -345,6 +348,8 @@
13456E921ADAD2DE009F94A7 /* RCTConvert+CoreLocation.m */,
13456E941ADAD482009F94A7 /* RCTConvert+MapKit.h */,
13456E951ADAD482009F94A7 /* RCTConvert+MapKit.m */,
133CAE8C1B8E5CFD00F6AD92 /* RCTDatePicker.h */,
133CAE8D1B8E5CFD00F6AD92 /* RCTDatePicker.m */,
58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */,
58C571BF1AA56C1900CDF9C8 /* RCTDatePickerManager.m */,
14435CE11AAC4AE100FC20F4 /* RCTMap.h */,
@ -597,6 +602,7 @@
13456E961ADAD482009F94A7 /* RCTConvert+MapKit.m in Sources */,
13723B501A82FD3C00F88898 /* RCTStatusBarManager.m in Sources */,
000E6CEB1AB0E980000CDF4D /* RCTSourceCode.m in Sources */,
133CAE8E1B8E5CFD00F6AD92 /* RCTDatePicker.m in Sources */,
14C2CA761B3AC64F00E6CBB2 /* RCTFrameUpdate.m in Sources */,
13B07FEF1A69327A00A75B9A /* RCTAlertManager.m in Sources */,
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */,

View File

@ -9,6 +9,13 @@
#import <CoreGraphics/CoreGraphics.h>
/**
* These block types can be used for mapping input event handlers from JS to view
* properties. Unlike JS method callbacks, these can be called multiple times.
*/
typedef void (^RCTDirectEventBlock)(NSDictionary *body);
typedef void (^RCTBubblingEventBlock)(NSDictionary *body);
/**
* Logical node in a tree of application components. Both `ShadowView` and
* `UIView` conforms to this. Allows us to write utilities that reason about

View File

@ -13,6 +13,7 @@
#import "RCTBridge.h"
#import "RCTShadowView.h"
#import "RCTUtils.h"
#import "RCTViewManager.h"
typedef void (^RCTPropBlock)(id<RCTComponent> view, id json);
@ -140,77 +141,95 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
// Build setter block
void (^setterBlock)(id target, id source, id json) = nil;
NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
switch (typeSignature.methodReturnType[0]) {
if (type == NSSelectorFromString(@"RCTBubblingEventBlock:") ||
type == NSSelectorFromString(@"RCTDirectEventBlock:")) {
#define RCT_CASE(_value, _type) \
case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
_type (*get)(id, SEL) = (typeof(get))objc_msgSend; \
void (*set)(id, SEL, _type) = (typeof(set))objc_msgSend; \
setterBlock = ^(id target, id source, id json) { \
set(target, setter, json ? convert([RCTConvert class], type, json) : get(source, getter)); \
}; \
break; \
}
// Special case for event handlers
__weak RCTViewManager *weakManager = _manager;
setterBlock = ^(id target, __unused id source, id json) {
__weak id<RCTComponent> weakTarget = target;
((void (*)(id, SEL, id))objc_msgSend)(target, setter, [RCTConvert BOOL:json] ? ^(NSDictionary *body) {
body = [NSMutableDictionary dictionaryWithDictionary:body];
((NSMutableDictionary *)body)[@"target"] = weakTarget.reactTag;
[weakManager.bridge.eventDispatcher sendInputEventWithName:RCTNormalizeInputEventName(name) body:body];
} : nil);
};
RCT_CASE(_C_SEL, SEL)
RCT_CASE(_C_CHARPTR, const char *)
RCT_CASE(_C_CHR, char)
RCT_CASE(_C_UCHR, unsigned char)
RCT_CASE(_C_SHT, short)
RCT_CASE(_C_USHT, unsigned short)
RCT_CASE(_C_INT, int)
RCT_CASE(_C_UINT, unsigned int)
RCT_CASE(_C_LNG, long)
RCT_CASE(_C_ULNG, unsigned long)
RCT_CASE(_C_LNG_LNG, long long)
RCT_CASE(_C_ULNG_LNG, unsigned long long)
RCT_CASE(_C_FLT, float)
RCT_CASE(_C_DBL, double)
RCT_CASE(_C_BOOL, BOOL)
RCT_CASE(_C_PTR, void *)
RCT_CASE(_C_ID, id)
} else {
case _C_STRUCT_B:
default: {
// Ordinary property handlers
NSMethodSignature *typeSignature = [[RCTConvert class] methodSignatureForSelector:type];
switch (typeSignature.methodReturnType[0]) {
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = type;
typeInvocation.target = [RCTConvert class];
#define RCT_CASE(_value, _type) \
case _value: { \
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
_type (*get)(id, SEL) = (typeof(get))objc_msgSend; \
void (*set)(id, SEL, _type) = (typeof(set))objc_msgSend; \
setterBlock = ^(id target, id source, id json) { \
set(target, setter, json ? convert([RCTConvert class], type, json) : get(source, getter)); \
}; \
break; \
}
__block NSInvocation *sourceInvocation = nil;
__block NSInvocation *targetInvocation = nil;
RCT_CASE(_C_SEL, SEL)
RCT_CASE(_C_CHARPTR, const char *)
RCT_CASE(_C_CHR, char)
RCT_CASE(_C_UCHR, unsigned char)
RCT_CASE(_C_SHT, short)
RCT_CASE(_C_USHT, unsigned short)
RCT_CASE(_C_INT, int)
RCT_CASE(_C_UINT, unsigned int)
RCT_CASE(_C_LNG, long)
RCT_CASE(_C_ULNG, unsigned long)
RCT_CASE(_C_LNG_LNG, long long)
RCT_CASE(_C_ULNG_LNG, unsigned long long)
RCT_CASE(_C_FLT, float)
RCT_CASE(_C_DBL, double)
RCT_CASE(_C_BOOL, BOOL)
RCT_CASE(_C_PTR, void *)
RCT_CASE(_C_ID, id)
setterBlock = ^(id target, id source, id json) { \
case _C_STRUCT_B:
default: {
// Get value
void *value = malloc(typeSignature.methodReturnLength);
if (json) {
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:value];
} else {
if (!sourceInvocation && source) {
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
sourceInvocation.selector = getter;
NSInvocation *typeInvocation = [NSInvocation invocationWithMethodSignature:typeSignature];
typeInvocation.selector = type;
typeInvocation.target = [RCTConvert class];
__block NSInvocation *sourceInvocation = nil;
__block NSInvocation *targetInvocation = nil;
setterBlock = ^(id target, id source, id json) { \
// Get value
void *value = malloc(typeSignature.methodReturnLength);
if (json) {
[typeInvocation setArgument:&json atIndex:2];
[typeInvocation invoke];
[typeInvocation getReturnValue:value];
} else {
if (!sourceInvocation && source) {
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
sourceInvocation = [NSInvocation invocationWithMethodSignature:signature];
sourceInvocation.selector = getter;
}
[sourceInvocation invokeWithTarget:source];
[sourceInvocation getReturnValue:value];
}
[sourceInvocation invokeWithTarget:source];
[sourceInvocation getReturnValue:value];
}
// Set value
if (!targetInvocation && target) {
NSMethodSignature *signature = [target methodSignatureForSelector:setter];
targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
targetInvocation.selector = setter;
}
[targetInvocation setArgument:value atIndex:2];
[targetInvocation invokeWithTarget:target];
free(value);
};
break;
// Set value
if (!targetInvocation && target) {
NSMethodSignature *signature = [target methodSignatureForSelector:setter];
targetInvocation = [NSInvocation invocationWithMethodSignature:signature];
targetInvocation.selector = setter;
}
[targetInvocation setArgument:value atIndex:2];
[targetInvocation invokeWithTarget:target];
free(value);
};
break;
}
}
}
@ -292,9 +311,35 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (NSDictionary *)viewConfig
{
Class managerClass = [_manager class];
NSMutableDictionary *propTypes = [NSMutableDictionary new];
NSMutableArray *directEvents = [NSMutableArray new];
if (RCTClassOverridesInstanceMethod(managerClass, @selector(customDirectEventTypes))) {
NSArray *events = [_manager customDirectEventTypes];
if (RCT_DEBUG) {
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
@"customDirectEventTypes must return an array, but %@ returned %@",
managerClass, [events class]);
}
for (NSString *event in events) {
[directEvents addObject:RCTNormalizeInputEventName(event)];
}
}
NSMutableArray *bubblingEvents = [NSMutableArray new];
if (RCTClassOverridesInstanceMethod(managerClass, @selector(customBubblingEventTypes))) {
NSArray *events = [_manager customBubblingEventTypes];
if (RCT_DEBUG) {
RCTAssert(!events || [events isKindOfClass:[NSArray class]],
@"customBubblingEventTypes must return an array, but %@ returned %@",
managerClass, [events class]);
}
for (NSString *event in events) {
[bubblingEvents addObject:RCTNormalizeInputEventName(event)];
}
}
unsigned int count = 0;
NSMutableDictionary *propTypes = [NSMutableDictionary new];
Method *methods = class_copyMethodList(object_getClass(managerClass), &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methods[i];
@ -309,13 +354,41 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
RCTLogError(@"Property '%@' of component '%@' redefined from '%@' "
"to '%@'", name, _name, propTypes[name], type);
}
propTypes[name] = type;
if ([type isEqualToString:@"RCTBubblingEventBlock"]) {
[bubblingEvents addObject:RCTNormalizeInputEventName(name)];
propTypes[name] = @"BOOL";
} else if ([type isEqualToString:@"RCTDirectEventBlock"]) {
[directEvents addObject:RCTNormalizeInputEventName(name)];
propTypes[name] = @"BOOL";
} else {
propTypes[name] = type;
}
}
}
}
free(methods);
return propTypes;
if (RCT_DEBUG) {
for (NSString *event in directEvents) {
if ([bubblingEvents containsObject:event]) {
RCTLogError(@"Component '%@' registered '%@' as both a bubbling event "
"and a direct event", _name, event);
}
}
for (NSString *event in bubblingEvents) {
if ([directEvents containsObject:event]) {
RCTLogError(@"Component '%@' registered '%@' as both a bubbling event "
"and a direct event", _name, event);
}
}
}
return @{
@"propTypes" : propTypes,
@"directEvents" : directEvents,
@"bubblingEvents" : bubblingEvents,
};
}
@end

View File

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

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTDatePicker.h"
#import "RCTUtils.h"
#import "UIView+React.h"
@interface RCTDatePicker ()
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@end
@implementation RCTDatePicker
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
[self addTarget:self action:@selector(didChange)
forControlEvents:UIControlEventValueChanged];
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)didChange
{
if (_onChange) {
_onChange(@{ @"timestamp": @(self.date.timeIntervalSince1970 * 1000.0) });
}
}
@end

View File

@ -10,6 +10,7 @@
#import "RCTDatePickerManager.h"
#import "RCTBridge.h"
#import "RCTDatePicker.h"
#import "RCTEventDispatcher.h"
#import "UIView+React.h"
@ -30,14 +31,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
// TODO: we crash here if the RCTDatePickerManager is released
// while the UIDatePicker is still sending onChange events. To
// fix this we should maybe subclass UIDatePicker and make it
// be its own event target.
UIDatePicker *picker = [UIDatePicker new];
[picker addTarget:self action:@selector(onChange:)
forControlEvents:UIControlEventValueChanged];
return picker;
return [RCTDatePicker new];
}
RCT_EXPORT_VIEW_PROPERTY(date, NSDate)
@ -47,15 +41,6 @@ RCT_EXPORT_VIEW_PROPERTY(minuteInterval, NSInteger)
RCT_REMAP_VIEW_PROPERTY(mode, datePickerMode, UIDatePickerMode)
RCT_REMAP_VIEW_PROPERTY(timeZoneOffsetInMinutes, timeZone, NSTimeZone)
- (void)onChange:(UIDatePicker *)sender
{
NSDictionary *event = @{
@"target": sender.reactTag,
@"timestamp": @(sender.date.timeIntervalSince1970 * 1000.0)
};
[self.bridge.eventDispatcher sendInputEventWithName:@"change" body:event];
}
- (NSDictionary *)constantsToExport
{
UIDatePicker *view = [UIDatePicker new];

View File

@ -11,13 +11,12 @@
#import <UIKit/UIKit.h>
#import "RCTConvert+MapKit.h"
#import "RCTComponent.h"
extern const CLLocationDegrees RCTMapDefaultSpan;
extern const NSTimeInterval RCTMapRegionChangeObserveInterval;
extern const CGFloat RCTMapZoomBoundBuffer;
@class RCTEventDispatcher;
@interface RCTMap: MKMapView
@property (nonatomic, assign) BOOL followUserLocation;
@ -28,6 +27,9 @@ extern const CGFloat RCTMapZoomBoundBuffer;
@property (nonatomic, strong) NSTimer *regionChangeObserveTimer;
@property (nonatomic, strong) NSMutableArray *annotationIds;
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
- (void)setAnnotations:(RCTPointAnnotationArray *)annotations;
@end

View File

@ -46,6 +46,8 @@ RCT_EXPORT_VIEW_PROPERTY(minDelta, CGFloat)
RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets, UIEdgeInsets)
RCT_EXPORT_VIEW_PROPERTY(mapType, MKMapType)
RCT_EXPORT_VIEW_PROPERTY(annotations, RCTPointAnnotationArray)
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
{
[view setRegion:json ? [RCTConvert MKCoordinateRegion:json] : defaultView.region animated:YES];
@ -53,35 +55,27 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
#pragma mark MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
- (void)mapView:(RCTMap *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
if (![view.annotation isKindOfClass:[MKUserLocation class]]) {
if (mapView.onPress && [view.annotation isKindOfClass:[RCTPointAnnotation class]]) {
RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation;
NSString *title = view.annotation.title ?: @"";
NSString *subtitle = view.annotation.subtitle ?: @"";
NSDictionary *event = @{
@"target": mapView.reactTag,
@"action": @"annotation-click",
@"annotation": @{
@"id": annotation.identifier,
@"title": title,
@"subtitle": subtitle,
@"latitude": @(annotation.coordinate.latitude),
@"longitude": @(annotation.coordinate.longitude)
}
};
[self.bridge.eventDispatcher sendInputEventWithName:@"press" body:event];
mapView.onPress(@{
@"action": @"annotation-click",
@"annotation": @{
@"id": annotation.identifier,
@"title": annotation.title ?: @"",
@"subtitle": annotation.subtitle ?: @"",
@"latitude": @(annotation.coordinate.latitude),
@"longitude": @(annotation.coordinate.longitude)
}
});
}
}
- (MKAnnotationView *)mapView:(__unused MKMapView *)mapView viewForAnnotation:(RCTPointAnnotation *)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]) {
if (![annotation isKindOfClass:[RCTPointAnnotation class]]) {
return nil;
}
@ -103,23 +97,20 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
return annotationView;
}
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
- (void)mapView:(RCTMap *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
// Pass to js
RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation;
NSString *side = (control == view.leftCalloutAccessoryView) ? @"left" : @"right";
if (mapView.onPress) {
NSDictionary *event = @{
@"target": mapView.reactTag,
@"side": side,
// Pass to js
RCTPointAnnotation *annotation = (RCTPointAnnotation *)view.annotation;
mapView.onPress(@{
@"side": (control == view.leftCalloutAccessoryView) ? @"left" : @"right",
@"action": @"callout-click",
@"annotationId": annotation.identifier
};
[self.bridge.eventDispatcher sendInputEventWithName:@"press" body:event];
});
}
}
- (void)mapView:(RCTMap *)mapView didUpdateUserLocation:(MKUserLocation *)location
{
if (mapView.followUserLocation) {
@ -205,24 +196,24 @@ RCT_CUSTOM_VIEW_PROPERTY(region, MKCoordinateRegion, RCTMap)
- (void)_emitRegionChangeEvent:(RCTMap *)mapView continuous:(BOOL)continuous
{
MKCoordinateRegion region = mapView.region;
if (!CLLocationCoordinate2DIsValid(region.center)) {
return;
}
#define FLUSH_NAN(value) (isnan(value) ? 0 : value)
NSDictionary *event = @{
@"target": mapView.reactTag,
@"continuous": @(continuous),
@"region": @{
@"latitude": @(FLUSH_NAN(region.center.latitude)),
@"longitude": @(FLUSH_NAN(region.center.longitude)),
@"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)),
@"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)),
if (mapView.onChange) {
MKCoordinateRegion region = mapView.region;
if (!CLLocationCoordinate2DIsValid(region.center)) {
return;
}
};
[self.bridge.eventDispatcher sendInputEventWithName:@"change" body:event];
#define FLUSH_NAN(value) (isnan(value) ? 0 : value)
mapView.onChange(@{
@"continuous": @(continuous),
@"region": @{
@"latitude": @(FLUSH_NAN(region.center.latitude)),
@"longitude": @(FLUSH_NAN(region.center.longitude)),
@"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)),
@"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)),
}
});
}
}
@end

View File

@ -9,6 +9,8 @@
#import <UIKit/UIKit.h>
#import "RCTComponent.h"
@interface RCTNavItem : UIView
@property (nonatomic, copy) NSString *title;
@ -29,4 +31,7 @@
@property (nonatomic, readonly) UIBarButtonItem *leftButtonItem;
@property (nonatomic, readonly) UIBarButtonItem *rightButtonItem;
@property (nonatomic, copy) RCTBubblingEventBlock onNavLeftButtonTap;
@property (nonatomic, copy) RCTBubblingEventBlock onNavRightButtonTap;
@end

View File

@ -63,15 +63,18 @@
{
if (!_leftButtonItem) {
if (_leftButtonIcon) {
_leftButtonItem = [[UIBarButtonItem alloc] initWithImage:_leftButtonIcon
style:UIBarButtonItemStylePlain
target:nil
action:nil];
_leftButtonItem =
[[UIBarButtonItem alloc] initWithImage:_leftButtonIcon
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleNavLeftButtonTapped)];
} else if (_leftButtonTitle.length) {
_leftButtonItem = [[UIBarButtonItem alloc] initWithTitle:_leftButtonTitle
style:UIBarButtonItemStylePlain
target:nil
action:nil];
_leftButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_leftButtonTitle
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleNavLeftButtonTapped)];
} else {
_leftButtonItem = nil;
}
@ -79,6 +82,13 @@
return _leftButtonItem;
}
- (void)handleNavLeftButtonTapped
{
if (_onNavLeftButtonTap) {
_onNavLeftButtonTap(nil);
}
}
- (void)setRightButtonTitle:(NSString *)rightButtonTitle
{
_rightButtonTitle = rightButtonTitle;
@ -95,15 +105,18 @@
{
if (!_rightButtonItem) {
if (_rightButtonIcon) {
_rightButtonItem = [[UIBarButtonItem alloc] initWithImage:_rightButtonIcon
style:UIBarButtonItemStylePlain
target:nil
action:nil];
_rightButtonItem =
[[UIBarButtonItem alloc] initWithImage:_rightButtonIcon
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleNavRightButtonTapped)];
} else if (_rightButtonTitle.length) {
_rightButtonItem = [[UIBarButtonItem alloc] initWithTitle:_rightButtonTitle
style:UIBarButtonItemStylePlain
target:nil
action:nil];
_rightButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_rightButtonTitle
style:UIBarButtonItemStylePlain
target:self
action:@selector(handleNavRightButtonTapped)];
} else {
_rightButtonItem = nil;
}
@ -111,4 +124,11 @@
return _rightButtonItem;
}
- (void)handleNavRightButtonTapped
{
if (_onNavRightButtonTap) {
_onNavRightButtonTap(nil);
}
}
@end

View File

@ -39,4 +39,7 @@ RCT_EXPORT_VIEW_PROPERTY(leftButtonIcon, UIImage)
RCT_EXPORT_VIEW_PROPERTY(rightButtonIcon, UIImage)
RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString)
RCT_EXPORT_VIEW_PROPERTY(onNavLeftButtonTap, RCTBubblingEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onNavRightButtonTap, RCTBubblingEventBlock)
@end

View File

@ -193,6 +193,9 @@ NSInteger kNeverProgressed = -10000;
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate>
@property (nonatomic, copy) RCTDirectEventBlock onNavigationProgress;
@property (nonatomic, copy) RCTBubblingEventBlock onNavigationComplete;
@property (nonatomic, assign) NSInteger previousRequestedTopOfStack;
// Previous views are only mainted in order to detect incorrect
@ -308,12 +311,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
return;
}
_mostRecentProgress = nextProgress;
[_bridge.eventDispatcher sendInputEventWithName:@"navigationProgress" body:@{
@"fromIndex": @(_currentlyTransitioningFrom),
@"toIndex": @(_currentlyTransitioningTo),
@"progress": @(nextProgress),
@"target": self.reactTag
}];
if (_onNavigationProgress) {
_onNavigationProgress(@{
@"fromIndex": @(_currentlyTransitioningFrom),
@"toIndex": @(_currentlyTransitioningTo),
@"progress": @(nextProgress),
});
}
}
}
@ -416,10 +420,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)handleTopOfStackChanged
{
[_bridge.eventDispatcher sendInputEventWithName:@"navigationComplete" body:@{
@"target":self.reactTag,
@"stackLength":@(_navigationController.viewControllers.count)
}];
if (_onNavigationComplete) {
_onNavigationComplete(@{
@"stackLength":@(_navigationController.viewControllers.count)
});
}
}
- (void)dispatchFakeScrollEvent
@ -502,7 +507,7 @@ BOOL jsGettingtooSlow =
if (jsGettingAhead) {
if (reactPushOne) {
UIView *lastView = _currentViews.lastObject;
RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView eventDispatcher:_bridge.eventDispatcher];
RCTWrapperViewController *vc = [[RCTWrapperViewController alloc] initWithNavItem:(RCTNavItem *)lastView];
vc.navigationListener = self;
_numberOfViewControllerMovesToIgnore = 1;
[_navigationController pushViewController:vc animated:(currentReactCount > 1)];

View File

@ -25,22 +25,8 @@ RCT_EXPORT_MODULE()
}
RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack, NSInteger)
- (NSArray *)customBubblingEventTypes
{
return @[
@"navigationComplete",
@"navLeftButtonTap",
@"navRightButtonTap",
];
}
- (NSArray *)customDirectEventTypes
{
return @[
@"navigationProgress",
];
}
RCT_EXPORT_VIEW_PROPERTY(onNavigationProgress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onNavigationComplete, RCTBubblingEventBlock)
// TODO: remove error callbacks
RCT_EXPORT_METHOD(requestSchedulingJavaScriptNavigation:(nonnull NSNumber *)reactTag

View File

@ -9,13 +9,6 @@
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@interface RCTPicker : UIPickerView
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@property (nonatomic, copy) NSArray *items;
@property (nonatomic, assign) NSInteger selectedIndex;
@end

View File

@ -9,37 +9,28 @@
#import "RCTPicker.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTUtils.h"
#import "UIView+React.h"
const NSInteger UNINITIALIZED_INDEX = -1;
@interface RCTPicker() <UIPickerViewDataSource, UIPickerViewDelegate>
@property (nonatomic, copy) NSArray *items;
@property (nonatomic, assign) NSInteger selectedIndex;
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@end
@implementation RCTPicker
{
RCTEventDispatcher *_eventDispatcher;
NSArray *_items;
NSInteger _selectedIndex;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
- (instancetype)initWithFrame:(CGRect)frame
{
RCTAssertParam(eventDispatcher);
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
_selectedIndex = UNINITIALIZED_INDEX;
if ((self = [super initWithFrame:frame])) {
_selectedIndex = NSNotFound;
self.delegate = self;
}
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)setItems:(NSArray *)items
@ -51,7 +42,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)setSelectedIndex:(NSInteger)selectedIndex
{
if (_selectedIndex != selectedIndex) {
BOOL animated = _selectedIndex != UNINITIALIZED_INDEX; // Don't animate the initial value
BOOL animated = _selectedIndex != NSNotFound; // Don't animate the initial value
_selectedIndex = selectedIndex;
dispatch_async(dispatch_get_main_queue(), ^{
[self selectRow:selectedIndex inComponent:0 animated:animated];
@ -94,13 +85,12 @@ numberOfRowsInComponent:(__unused NSInteger)component
didSelectRow:(NSInteger)row inComponent:(__unused NSInteger)component
{
_selectedIndex = row;
NSDictionary *event = @{
@"target": self.reactTag,
@"newIndex": @(row),
@"newValue": [self valueForRow:row]
};
[_eventDispatcher sendInputEventWithName:@"change" body:event];
if (_onChange) {
_onChange(@{
@"newIndex": @(row),
@"newValue": [self valueForRow:row]
});
}
}
@end

View File

@ -19,7 +19,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTPicker alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
return [RCTPicker new];
}
RCT_EXPORT_VIEW_PROPERTY(items, NSDictionaryArray)

View File

@ -9,13 +9,12 @@
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
#import "RCTComponent.h"
@interface RCTSegmentedControl : UISegmentedControl
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@property (nonatomic, copy) NSArray *values;
@property (nonatomic, assign) NSInteger selectedIndex;
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@end

View File

@ -14,17 +14,13 @@
#import "UIView+React.h"
@implementation RCTSegmentedControl
{
RCTEventDispatcher *_eventDispatcher;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
if ((self = [super initWithFrame:frame])) {
_selectedIndex = self.selectedSegmentIndex;
[self addTarget:self action:@selector(onChange:)
forControlEvents:UIControlEventValueChanged];
[self addTarget:self action:@selector(didChange)
forControlEvents:UIControlEventValueChanged];
}
return self;
}
@ -45,14 +41,14 @@
super.selectedSegmentIndex = selectedIndex;
}
- (void)onChange:(UISegmentedControl *)sender
- (void)didChange
{
NSDictionary *event = @{
@"target": self.reactTag,
@"value": [self titleForSegmentAtIndex:sender.selectedSegmentIndex],
@"selectedSegmentIndex": @(sender.selectedSegmentIndex)
};
[_eventDispatcher sendInputEventWithName:@"change" body:event];
if (_onChange) {
_onChange(@{
@"value": [self titleForSegmentAtIndex:_selectedIndex],
@"selectedSegmentIndex": @(_selectedIndex)
});
}
}
@end

View File

@ -19,7 +19,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTSegmentedControl alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
return [RCTSegmentedControl new];
}
RCT_EXPORT_VIEW_PROPERTY(values, NSStringArray)
@ -27,6 +27,7 @@ RCT_EXPORT_VIEW_PROPERTY(selectedIndex, NSInteger)
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(momentary, BOOL)
RCT_EXPORT_VIEW_PROPERTY(enabled, BOOL)
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock)
- (NSDictionary *)constantsToExport
{

View File

@ -39,7 +39,7 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *viewRegistry);
@property (nonatomic, copy) NSString *viewName;
@property (nonatomic, strong) UIColor *backgroundColor; // Used to propagate to children
@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle;
@property (nonatomic, assign) BOOL onLayout;
@property (nonatomic, copy) RCTDirectEventBlock onLayout;
/**
* isNewView - Used to track the first time the view is introduced into the hierarchy. It is initialized YES, then is

View File

@ -9,6 +9,10 @@
#import <UIKit/UIKit.h>
#import "RCTComponent.h"
@interface RCTSlider : UISlider
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@end

View File

@ -27,25 +27,24 @@ RCT_EXPORT_MODULE()
return slider;
}
static void RCTSendSliderEvent(RCTSliderManager *self, UISlider *sender, BOOL continuous)
static void RCTSendSliderEvent(RCTSlider *sender, BOOL continuous)
{
NSDictionary *event = @{
@"target": sender.reactTag,
@"value": @(sender.value),
@"continuous": @(continuous),
};
[self.bridge.eventDispatcher sendInputEventWithName:@"change" body:event];
if (sender.onChange) {
sender.onChange(@{
@"value": @(sender.value),
@"continuous": @(continuous),
});
}
}
- (void)sliderValueChanged:(UISlider *)sender
- (void)sliderValueChanged:(RCTSlider *)sender
{
RCTSendSliderEvent(self, sender, YES);
RCTSendSliderEvent(sender, YES);
}
- (void)sliderTouchEnd:(UISlider *)sender
- (void)sliderTouchEnd:(RCTSlider *)sender
{
RCTSendSliderEvent(self, sender, NO);
RCTSendSliderEvent(sender, NO);
}
RCT_EXPORT_VIEW_PROPERTY(value, float);
@ -53,5 +52,6 @@ RCT_EXPORT_VIEW_PROPERTY(minimumValue, float);
RCT_EXPORT_VIEW_PROPERTY(maximumValue, float);
RCT_EXPORT_VIEW_PROPERTY(minimumTrackTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(maximumTrackTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock);
@end

View File

@ -10,8 +10,11 @@
#import <UIKit/UIKit.h>
#import "RCTComponent.h"
@interface RCTSwitch : UISwitch
@property (nonatomic, assign) BOOL wasOn;
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
@end

View File

@ -30,11 +30,9 @@ RCT_EXPORT_MODULE()
- (void)onChange:(RCTSwitch *)sender
{
if (sender.wasOn != sender.on) {
[self.bridge.eventDispatcher sendInputEventWithName:@"change" body:@{
@"target": sender.reactTag,
@"value": @(sender.on)
}];
if (sender.onChange) {
sender.onChange(@{ @"value": @(sender.on) });
}
sender.wasOn = sender.on;
}
}
@ -43,6 +41,7 @@ RCT_EXPORT_VIEW_PROPERTY(onTintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor);
RCT_EXPORT_VIEW_PROPERTY(thumbTintColor, UIColor);
RCT_REMAP_VIEW_PROPERTY(value, on, BOOL);
RCT_EXPORT_VIEW_PROPERTY(onChange, RCTBubblingEventBlock);
RCT_CUSTOM_VIEW_PROPERTY(disabled, BOOL, RCTSwitch)
{
if (json) {

View File

@ -9,14 +9,10 @@
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@interface RCTTabBar : UIView
@property (nonatomic, strong) UIColor *tintColor;
@property (nonatomic, strong) UIColor *barTintColor;
@property (nonatomic, assign) BOOL translucent;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@end

View File

@ -25,17 +25,13 @@
@implementation RCTTabBar
{
BOOL _tabsChanged;
RCTEventDispatcher *_eventDispatcher;
UITabBarController *_tabController;
NSMutableArray *_tabViews;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
- (instancetype)initWithFrame:(CGRect)frame
{
RCTAssertParam(eventDispatcher);
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
if ((self = [super initWithFrame:frame])) {
_tabViews = [NSMutableArray new];
_tabController = [UITabBarController new];
_tabController.delegate = self;
@ -44,7 +40,6 @@
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (UIViewController *)reactViewController
@ -100,8 +95,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
for (RCTTabBarItem *tab in [self reactSubviews]) {
UIViewController *controller = tab.reactViewController;
if (!controller) {
controller = [[RCTWrapperViewController alloc] initWithContentView:tab
eventDispatcher:_eventDispatcher];
controller = [[RCTWrapperViewController alloc] initWithContentView:tab];
}
[viewControllers addObject:controller];
}
@ -154,7 +148,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
{
NSUInteger index = [tabBarController.viewControllers indexOfObject:viewController];
RCTTabBarItem *tab = [self reactSubviews][index];
[_eventDispatcher sendInputEventWithName:@"press" body:@{@"target": tab.reactTag}];
if (tab.onPress) tab.onPress(nil);
return NO;
}

View File

@ -9,10 +9,13 @@
#import <UIKit/UIKit.h>
#import "RCTComponent.h"
@interface RCTTabBarItem : UIView
@property (nonatomic, copy) id icon;
@property (nonatomic, assign, getter=isSelected) BOOL selected;
@property (nonatomic, readonly) UITabBarItem *barItem;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
@end

View File

@ -21,10 +21,11 @@ RCT_EXPORT_MODULE()
return [RCTTabBarItem new];
}
RCT_EXPORT_VIEW_PROPERTY(selected, BOOL);
RCT_EXPORT_VIEW_PROPERTY(icon, id);
RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage);
RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString);
RCT_EXPORT_VIEW_PROPERTY(selected, BOOL)
RCT_EXPORT_VIEW_PROPERTY(icon, id)
RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage)
RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem)
{
view.barItem.title = json ? [RCTConvert NSString:json] : defaultView.barItem.title;

View File

@ -18,7 +18,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTTabBar alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
return [RCTTabBar new];
}
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)

View File

@ -11,17 +11,20 @@
#import <UIKit/UIKit.h>
#import "RCTComponent.h"
#import "RCTPointerEvents.h"
@protocol RCTAutoInsetsProtocol;
@class RCTView;
typedef void (^RCTViewEventHandler)(RCTView *view);
@interface RCTView : UIView
@property (nonatomic, copy) RCTViewEventHandler accessibilityTapHandler;
@property (nonatomic, copy) RCTViewEventHandler magicTapHandler;
/**
* Accessibility event handlers
*/
@property (nonatomic, copy) RCTDirectEventBlock onAccessibilityTap;
@property (nonatomic, copy) RCTDirectEventBlock onMagicTap;
/**
* Used to control how touch events are processed.

View File

@ -167,8 +167,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
- (BOOL)accessibilityActivate
{
if (self.accessibilityTapHandler) {
self.accessibilityTapHandler(self);
if (_onAccessibilityTap) {
_onAccessibilityTap(nil);
return YES;
} else {
return NO;
@ -177,8 +177,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
- (BOOL)accessibilityPerformMagicTap
{
if (self.magicTapHandler) {
self.magicTapHandler(self);
if (_onMagicTap) {
_onMagicTap(nil);
return YES;
} else {
return NO;

View File

@ -11,10 +11,12 @@
#import "RCTBridgeModule.h"
#import "RCTConvert.h"
#import "RCTComponent.h"
#import "RCTDefines.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
@class RCTBridge;
@class RCTEventDispatcher;
@class RCTShadowView;
@class RCTSparseArray;
@class RCTUIManager;
@ -37,7 +39,7 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
* return a fresh instance each time. The view module MUST NOT cache the returned
* view and return the same instance for subsequent calls.
*/
- (UIView *)view;
- (UIView<RCTComponent> *)view;
/**
* This method instantiates a native view using the props passed into the component.
@ -57,6 +59,8 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
- (RCTShadowView *)shadowView;
/**
* DEPRECATED: declare properties of type RCTBubblingEventBlock instead
*
* Returns an array of names of events that can be sent by native views. This
* should return bubbling, directly-dispatched event types. The event name
* should not include a prefix such as 'on' or 'top', as this will be applied
@ -69,6 +73,8 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
- (NSArray *)customBubblingEventTypes;
/**
* DEPRECATED: declare properties of type RCTDirectEventBlock instead
*
* Returns an array of names of events that can be sent by native views. This
* should return non-bubbling, directly-dispatched event types. The event name
* should not include a prefix such as 'on' or 'top', as this will be applied

View File

@ -54,7 +54,7 @@ RCT_EXPORT_MODULE()
return _bridge.uiManager.methodQueue;
}
- (UIView *)viewWithProps:(NSDictionary *)props
- (UIView *)viewWithProps:(__unused NSDictionary *)props
{
return [self view];
}
@ -76,7 +76,6 @@ RCT_EXPORT_MODULE()
// Generic events
@"press",
@"change",
@"change",
@"focus",
@"blur",
@"submitEditing",
@ -92,11 +91,7 @@ RCT_EXPORT_MODULE()
- (NSArray *)customDirectEventTypes
{
return @[
@"layout",
@"accessibilityTap",
@"magicTap",
];
return @[];
}
- (NSDictionary *)constantsToExport
@ -195,27 +190,8 @@ RCT_CUSTOM_VIEW_PROPERTY(borderWidth, CGFloat, RCTView)
view.layer.borderWidth = json ? [RCTConvert CGFloat:json] : defaultView.layer.borderWidth;
}
}
RCT_CUSTOM_VIEW_PROPERTY(onAccessibilityTap, BOOL, __unused RCTView)
{
view.accessibilityTapHandler = [self eventHandlerWithName:@"accessibilityTap" json:json];
}
RCT_CUSTOM_VIEW_PROPERTY(onMagicTap, BOOL, __unused RCTView)
{
view.magicTapHandler = [self eventHandlerWithName:@"magicTap" json:json];
}
- (RCTViewEventHandler)eventHandlerWithName:(NSString *)eventName json:(id)json
{
RCTViewEventHandler handler = nil;
if ([RCTConvert BOOL:json]) {
__weak RCTViewManager *weakSelf = self;
handler = ^(RCTView *tappedView) {
NSDictionary *body = @{ @"target": tappedView.reactTag };
[weakSelf.bridge.eventDispatcher sendInputEventWithName:eventName body:body];
};
}
return handler;
}
RCT_EXPORT_VIEW_PROPERTY(onAccessibilityTap, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onMagicTap, RCTDirectEventBlock)
#define RCT_VIEW_BORDER_PROPERTY(SIDE) \
RCT_CUSTOM_VIEW_PROPERTY(border##SIDE##Width, CGFloat, RCTView) \
@ -291,6 +267,6 @@ RCT_EXPORT_SHADOW_PROPERTY(alignItems, css_align_t)
RCT_EXPORT_SHADOW_PROPERTY(alignSelf, css_align_t)
RCT_EXPORT_SHADOW_PROPERTY(position, css_position_type_t)
RCT_EXPORT_SHADOW_PROPERTY(onLayout, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(onLayout, RCTDirectEventBlock)
@end

View File

@ -17,8 +17,6 @@
*/
extern NSString *const RCTJSNavigationScheme;
@class RCTEventDispatcher;
@interface RCTWebView : RCTView
@property (nonatomic, strong) NSURL *URL;
@ -26,8 +24,6 @@ extern NSString *const RCTJSNavigationScheme;
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
@property (nonatomic, copy) NSString *injectedJavaScript;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
- (void)goForward;
- (void)goBack;
- (void)reload;

View File

@ -22,24 +22,24 @@ NSString *const RCTJSNavigationScheme = @"react-js-navigation";
@interface RCTWebView () <UIWebViewDelegate, RCTAutoInsetsProtocol>
@property (nonatomic, copy) RCTDirectEventBlock onLoadingStart;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingFinish;
@property (nonatomic, copy) RCTDirectEventBlock onLoadingError;
@end
@implementation RCTWebView
{
RCTEventDispatcher *_eventDispatcher;
UIWebView *_webView;
NSString *_injectedJavaScript;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
- (instancetype)initWithFrame:(CGRect)frame
{
RCTAssertParam(eventDispatcher);
if ((self = [super initWithFrame:CGRectZero])) {
if ((self = [super initWithFrame:frame])) {
super.backgroundColor = [UIColor clearColor];
_automaticallyAdjustContentInsets = YES;
_contentInset = UIEdgeInsetsZero;
_eventDispatcher = eventDispatcher;
_webView = [[UIWebView alloc] initWithFrame:self.bounds];
_webView.delegate = self;
[self addSubview:_webView];
@ -47,7 +47,6 @@ NSString *const RCTJSNavigationScheme = @"react-js-navigation";
return self;
}
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)goForward
@ -123,13 +122,10 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (NSMutableDictionary *)baseEvent
{
NSURL *url = _webView.request.URL;
NSString *title = [_webView stringByEvaluatingJavaScriptFromString:@"document.title"];
NSMutableDictionary *event = [[NSMutableDictionary alloc] initWithDictionary: @{
@"target": self.reactTag,
@"url": url ? url.absoluteString : @"",
@"url": _webView.request.URL.absoluteString ?: @"",
@"loading" : @(_webView.loading),
@"title": title,
@"title": [_webView stringByEvaluatingJavaScriptFromString:@"document.title"],
@"canGoBack": @(_webView.canGoBack),
@"canGoForward" : @(_webView.canGoForward),
}];
@ -139,19 +135,20 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
#pragma mark - UIWebViewDelegate methods
- (BOOL)webView:(__unused UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
// We have this check to filter out iframe requests and whatnot
BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
if (isTopFrame) {
NSMutableDictionary *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"url": (request.URL).absoluteString,
@"navigationType": @(navigationType)
}];
[_eventDispatcher sendInputEventWithName:@"loadingStart" body:event];
if (_onLoadingStart) {
// We have this check to filter out iframe requests and whatnot
BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL];
if (isTopFrame) {
NSMutableDictionary *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"url": (request.URL).absoluteString,
@"navigationType": @(navigationType)
}];
_onLoadingStart(event);
}
}
// JS Navigation handler
@ -160,21 +157,24 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
- (void)webView:(__unused UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
// NSURLErrorCancelled is reported when a page has a redirect OR if you load
// a new URL in the WebView before the previous one came back. We can just
// ignore these since they aren't real errors.
// http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
return;
}
if (_onLoadingError) {
NSMutableDictionary *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"domain": error.domain,
@"code": @(error.code),
@"description": error.localizedDescription,
}];
[_eventDispatcher sendInputEventWithName:@"loadingError" body:event];
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
// NSURLErrorCancelled is reported when a page has a redirect OR if you load
// a new URL in the WebView before the previous one came back. We can just
// ignore these since they aren't real errors.
// http://stackoverflow.com/questions/1024748/how-do-i-fix-nsurlerrordomain-error-999-in-iphone-3-0-os
return;
}
NSMutableDictionary *event = [self baseEvent];
[event addEntriesFromDictionary: @{
@"domain": error.domain,
@"code": @(error.code),
@"description": error.localizedDescription,
}];
_onLoadingError(event);
}
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
@ -184,8 +184,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
}
// we only need the final 'finishLoad' call so only fire the event when we're actually done loading.
if (!webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) {
[_eventDispatcher sendInputEventWithName:@"loadingFinish" body:[self baseEvent]];
if (_onLoadingFinish && !webView.loading && ![webView.request.URL.absoluteString isEqualToString:@"about:blank"]) {
_onLoadingFinish([self baseEvent]);
}
}

View File

@ -20,7 +20,7 @@ RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTWebView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
return [RCTWebView new];
}
RCT_REMAP_VIEW_PROPERTY(url, URL, NSURL);
@ -31,15 +31,9 @@ RCT_REMAP_VIEW_PROPERTY(scalesPageToFit, _webView.scalesPageToFit, BOOL);
RCT_EXPORT_VIEW_PROPERTY(injectedJavaScript, NSString);
RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets);
RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL);
- (NSArray *)customDirectEventTypes
{
return @[
@"loadingStart",
@"loadingFinish",
@"loadingError",
];
}
RCT_EXPORT_VIEW_PROPERTY(onLoadingStart, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onLoadingFinish, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onLoadingError, RCTDirectEventBlock);
- (NSDictionary *)constantsToExport
{
@ -80,7 +74,6 @@ RCT_EXPORT_METHOD(goForward:(nonnull NSNumber *)reactTag)
}];
}
RCT_EXPORT_METHOD(reload:(nonnull NSNumber *)reactTag)
{
[self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {

View File

@ -11,7 +11,6 @@
#import "RCTViewControllerProtocol.h"
@class RCTEventDispatcher;
@class RCTNavItem;
@class RCTWrapperViewController;
@ -24,11 +23,8 @@ didMoveToNavigationController:(UINavigationController *)navigationController;
@interface RCTWrapperViewController : UIViewController <RCTViewControllerProtocol>
- (instancetype)initWithContentView:(UIView *)contentView
eventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNavItem:(RCTNavItem *)navItem
eventDispatcher:(RCTEventDispatcher *)eventDispatcher;
- (instancetype)initWithContentView:(UIView *)contentView NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNavItem:(RCTNavItem *)navItem;
@property (nonatomic, weak) id<RCTWrapperViewControllerNavigationListener> navigationListener;
@property (nonatomic, strong) RCTNavItem *navItem;

View File

@ -21,7 +21,6 @@
{
UIView *_wrapperView;
UIView *_contentView;
RCTEventDispatcher *_eventDispatcher;
CGFloat _previousTopLayout;
CGFloat _previousBottomLayout;
}
@ -30,23 +29,19 @@
@synthesize currentBottomLayoutGuide = _currentBottomLayoutGuide;
- (instancetype)initWithContentView:(UIView *)contentView
eventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
RCTAssertParam(contentView);
RCTAssertParam(eventDispatcher);
if ((self = [super initWithNibName:nil bundle:nil])) {
_contentView = contentView;
_eventDispatcher = eventDispatcher;
self.automaticallyAdjustsScrollViewInsets = NO;
}
return self;
}
- (instancetype)initWithNavItem:(RCTNavItem *)navItem
eventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [self initWithContentView:navItem eventDispatcher:eventDispatcher])) {
if ((self = [self initWithContentView:navItem])) {
_navItem = navItem;
}
return self;
@ -101,14 +96,8 @@ static UIView *RCTFindNavBarShadowViewInView(UIView *view)
UINavigationItem *item = self.navigationItem;
item.title = _navItem.title;
item.backBarButtonItem = _navItem.backButtonItem;
if ((item.leftBarButtonItem = _navItem.leftButtonItem)) {
item.leftBarButtonItem.target = self;
item.leftBarButtonItem.action = @selector(handleNavLeftButtonTapped);
}
if ((item.rightBarButtonItem = _navItem.rightButtonItem)) {
item.rightBarButtonItem.target = self;
item.rightBarButtonItem.action = @selector(handleNavRightButtonTapped);
}
item.leftBarButtonItem = _navItem.leftButtonItem;
item.rightBarButtonItem = _navItem.rightButtonItem;
}
}
@ -122,18 +111,6 @@ static UIView *RCTFindNavBarShadowViewInView(UIView *view)
self.view = _wrapperView;
}
- (void)handleNavLeftButtonTapped
{
[_eventDispatcher sendInputEventWithName:@"navLeftButtonTap"
body:@{@"target":_navItem.reactTag}];
}
- (void)handleNavRightButtonTapped
{
[_eventDispatcher sendInputEventWithName:@"navRightButtonTap"
body:@{@"target":_navItem.reactTag}];
}
- (void)didMoveToParentViewController:(UIViewController *)parent
{
// There's no clear setter for navigation controllers, but did move to parent

View File

@ -23,7 +23,7 @@
- (void)setReactTag:(NSNumber *)reactTag
{
objc_setAssociatedObject(self, @selector(reactTag), reactTag, OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_setAssociatedObject(self, @selector(reactTag), reactTag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)isReactRootView

5759
npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -126,7 +126,7 @@ describe('processRequest', () => {
expect(Bundler.prototype.bundle).toBeCalledWith(
'index.js',
true,
'index.map',
'index.map?platform=ios',
true,
'ios',
);
@ -271,7 +271,7 @@ describe('processRequest', () => {
expect(Bundler.prototype.bundle).toBeCalledWith(
'path/to/foo.js',
false,
'/path/to/foo.map',
'/path/to/foo.map?dev=false&runModule=false',
false,
undefined
);

View File

@ -429,8 +429,11 @@ class Server {
return true;
}).join('.') + '.js';
const sourceMapUrlObj = _.clone(urlObj);
sourceMapUrlObj.pathname = pathname.replace(/\.bundle$/, '.map');
return {
sourceMapUrl: pathname.replace(/\.bundle$/, '.map'),
sourceMapUrl: url.format(sourceMapUrlObj),
entryFile: entryFile,
dev: this._getBoolOptionFromQuery(urlObj.query, 'dev', true),
minify: this._getBoolOptionFromQuery(urlObj.query, 'minify'),

View File

@ -15,7 +15,7 @@ const debug = require('debug')('ReactPackager:SocketServer');
const fs = require('fs');
const net = require('net');
const MAX_IDLE_TIME = 10 * 60 * 1000;
const MAX_IDLE_TIME = 30 * 1000;
class SocketServer {
constructor(sockPath, options) {
@ -118,7 +118,7 @@ class SocketServer {
this._deathTimer = setTimeout(() => {
if (this._jobs <= 0) {
debug('server dying', process.pid);
process.exit(1);
process.exit();
}
this._dieEventually();
}, MAX_IDLE_TIME);

View File

@ -12,7 +12,7 @@ const crypto = require('crypto');
const path = require('path');
const tmpdir = require('os').tmpDir();
function getCacheFilePath(args) {
function getCacheFilePath(...args) {
args = Array.prototype.slice.call(args);
const prefix = args.shift();