Merge pull request #2541 from sahrens/Updates_from_Thu_Sep_3rd
Updates from Thu September 3rd
This commit is contained in:
commit
304989e3b8
|
@ -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]}>
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
expectErrorRegex:@"because shouldThrow"];
|
||||
}
|
||||
|
||||
- (void)DISABLED_testTimers // #8192477
|
||||
- (void)testTimers
|
||||
{
|
||||
[_runner runTest:_cmd module:@"TimersTest"];
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//#include "config.h"
|
||||
|
||||
#include "JSCLegacyProfiler.h"
|
||||
|
||||
#include "APICast.h"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -107,6 +107,8 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS);
|
||||
var RCTSlider = requireNativeComponent('RCTSlider', SliderIOS, {
|
||||
nativeOnly: { onChange: true },
|
||||
});
|
||||
|
||||
module.exports = SliderIOS;
|
||||
|
|
|
@ -108,6 +108,8 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
});
|
||||
|
||||
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS);
|
||||
var RCTSwitch = requireNativeComponent('RCTSwitch', SwitchIOS, {
|
||||
nativeOnly: { onChange: true }
|
||||
});
|
||||
|
||||
module.exports = SwitchIOS;
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTAssetsLibraryImageLoader.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTLog.h"
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
|
||||
_textView = [[UITextView alloc] initWithFrame:self.bounds];
|
||||
_textView.backgroundColor = [UIColor clearColor];
|
||||
_textView.scrollsToTop = NO;
|
||||
_textView.delegate = self;
|
||||
[self addSubview:_textView];
|
||||
}
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -34,4 +34,10 @@ RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback
|
|||
}
|
||||
}
|
||||
|
||||
- (NSDictionary *)constantsToExport
|
||||
{
|
||||
NSString *URL = self.bridge.bundleURL.absoluteString ?: @"";
|
||||
return @{@"scriptURL": URL};
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTComponent.h"
|
||||
|
||||
@interface RCTSlider : UISlider
|
||||
|
||||
@property (nonatomic, copy) RCTBubblingEventBlock onChange;
|
||||
|
||||
@end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in New Issue