diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js index 494d7771d..e636653ed 100644 --- a/Examples/UIExplorer/TouchableExample.js +++ b/Examples/UIExplorer/TouchableExample.js @@ -130,10 +130,11 @@ var TouchableFeedbackEvents = React.createClass({ }, render: function() { return ( - + this._appendEvent('press')} onPressIn={() => this._appendEvent('pressIn')} onPressOut={() => this._appendEvent('pressOut')} @@ -143,7 +144,7 @@ var TouchableFeedbackEvents = React.createClass({ - + {this.state.eventLog.map((e, ii) => {e})} @@ -165,10 +166,11 @@ var TouchableDelayEvents = React.createClass({ }, render: function() { return ( - + this._appendEvent('press')} delayPressIn={400} onPressIn={() => this._appendEvent('pressIn - 400ms delay')} @@ -181,7 +183,7 @@ var TouchableDelayEvents = React.createClass({ - + {this.state.eventLog.map((e, ii) => {e})} diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png index 263875acd..ff9eaeae1 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testLayoutExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png index 8cf73dbde..b08e761b6 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSliderExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png index b3f91815e..7297dde2d 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testSwitchExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png index 85757513f..65cadda4f 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTabBarExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png index 702043349..32449556d 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testTextExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png index b73cbc012..2451b5f92 100644 Binary files a/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png and b/Examples/UIExplorer/UIExplorerIntegrationTests/ReferenceImages/Examples-UIExplorer-UIExplorerApp.ios/testViewExample_1@2x.png differ diff --git a/Examples/UIExplorer/UIExplorerList.js b/Examples/UIExplorer/UIExplorerList.js index 16b597cfe..217797e26 100644 --- a/Examples/UIExplorer/UIExplorerList.js +++ b/Examples/UIExplorer/UIExplorerList.js @@ -190,6 +190,7 @@ class UIExplorerList extends React.Component { onChangeText={this._search.bind(this)} placeholder="Search..." style={[styles.searchTextInput, platformTextInputStyle]} + testID="explorer_search" value={this.state.searchText} /> ); diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m index 35dfe1167..6ef31ff4b 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_UIFontTests.m @@ -14,25 +14,25 @@ XCTAssertEqualObjects(font1, font2); \ } -- (void)DISABLED_testWeight // task #7118691 +- (void)testWeight { { - UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Bold" size:14]; + UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightBold]; UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"bold"}]; RCTAssertEqualFonts(expected, result); } { - UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Medium" size:14]; + UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium]; UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"500"}]; RCTAssertEqualFonts(expected, result); } { - UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-UltraLight" size:14]; + UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightUltraLight]; UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"100"}]; RCTAssertEqualFonts(expected, result); } { - UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:14]; + UIFont *expected = [UIFont systemFontOfSize:14 weight:UIFontWeightRegular]; UIFont *result = [RCTConvert UIFont:@{@"fontWeight": @"normal"}]; RCTAssertEqualFonts(expected, result); } @@ -41,7 +41,7 @@ - (void)testSize { { - UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:18.5]; + UIFont *expected = [UIFont systemFontOfSize:18.5]; UIFont *result = [RCTConvert UIFont:@{@"fontSize": @18.5}]; RCTAssertEqualFonts(expected, result); } @@ -69,32 +69,47 @@ - (void)testStyle { { - UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Italic" size:14]; + UIFont *font = [UIFont systemFontOfSize:14]; + UIFontDescriptor *fontDescriptor = [font fontDescriptor]; + UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits; + symbolicTraits |= UIFontDescriptorTraitItalic; + fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits]; + UIFont *expected = [UIFont fontWithDescriptor:fontDescriptor size:14]; UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"italic"}]; RCTAssertEqualFonts(expected, result); } { - UIFont *expected = [UIFont fontWithName:@"HelveticaNeue" size:14]; + UIFont *expected = [UIFont systemFontOfSize:14]; UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"normal"}]; RCTAssertEqualFonts(expected, result); } } -- (void)DISABLED_testStyleAndWeight // task #7118691 +- (void)testStyleAndWeight { { - UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-UltraLightItalic" size:14]; + UIFont *font = [UIFont systemFontOfSize:14 weight:UIFontWeightUltraLight]; + UIFontDescriptor *fontDescriptor = [font fontDescriptor]; + UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits; + symbolicTraits |= UIFontDescriptorTraitItalic; + fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits]; + UIFont *expected = [UIFont fontWithDescriptor:fontDescriptor size:14]; UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"italic", @"fontWeight": @"100"}]; RCTAssertEqualFonts(expected, result); } { - UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-BoldItalic" size:14]; + UIFont *font = [UIFont systemFontOfSize:14 weight:UIFontWeightBold]; + UIFontDescriptor *fontDescriptor = [font fontDescriptor]; + UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits; + symbolicTraits |= UIFontDescriptorTraitItalic; + fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits]; + UIFont *expected = [UIFont fontWithDescriptor:fontDescriptor size:14]; UIFont *result = [RCTConvert UIFont:@{@"fontStyle": @"italic", @"fontWeight": @"bold"}]; RCTAssertEqualFonts(expected, result); } } -- (void)DISABLED_testFamilyAndWeight // task #7118691 +- (void)testFamilyAndWeight { { UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-Bold" size:14]; @@ -111,11 +126,6 @@ UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Cochin", @"fontWeight": @"700"}]; RCTAssertEqualFonts(expected, result); } - { - UIFont *expected = [UIFont fontWithName:@"Cochin" size:14]; - UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Cochin", @"fontWeight": @"500"}]; // regular Cochin is actually medium bold - RCTAssertEqualFonts(expected, result); - } { UIFont *expected = [UIFont fontWithName:@"Cochin" size:14]; UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"Cochin", @"fontWeight": @"100"}]; @@ -137,7 +147,7 @@ } } -- (void)DISABLED_testFamilyStyleAndWeight // task #7118691 +- (void)testFamilyStyleAndWeight { { UIFont *expected = [UIFont fontWithName:@"HelveticaNeue-UltraLightItalic" size:14]; @@ -156,4 +166,18 @@ } } +- (void)testInvalidFont +{ + { + UIFont *expected = [UIFont systemFontOfSize:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"foobar"}]; + RCTAssertEqualFonts(expected, result); + } + { + UIFont *expected = [UIFont boldSystemFontOfSize:14]; + UIFont *result = [RCTConvert UIFont:@{@"fontFamily": @"foobar", @"fontWeight": @"bold"}]; + RCTAssertEqualFonts(expected, result); + } +} + @end diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m index 0fd4a7064..db4118a68 100644 --- a/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTUIManagerTests.m @@ -382,7 +382,7 @@ XCTAssertEqualObjects(removeIndices, (@[@0, @1, @3, @4])); } -- (void)testScenario1 +- (void)DISABLED_testScenario1 // t7660646 { RCTUIManager *uiManager = [[RCTUIManager alloc] init]; RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; @@ -454,7 +454,7 @@ [self waitForExpectationsWithTimeout:1 handler:nil]; } -- (void)testScenario2 +- (void)DISABLED_testScenario2 // t7660646 { RCTUIManager *uiManager = [[RCTUIManager alloc] init]; RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:nil moduleProvider:^{ return @[uiManager]; } launchOptions:nil]; diff --git a/Libraries/ART/ARTText.m b/Libraries/ART/ARTText.m index 7c8a57027..dfffde265 100644 --- a/Libraries/ART/ARTText.m +++ b/Libraries/ART/ARTText.m @@ -19,15 +19,22 @@ _alignment = alignment; } +static void ARTFreeTextFrame(ARTTextFrame frame) +{ + if (frame.count) { + // We must release each line before freeing up this struct + for (int i = 0; i < frame.count; i++) { + CFRelease(frame.lines[i]); + } + free(frame.lines); + free(frame.widths); + } +} + - (void)setTextFrame:(ARTTextFrame)frame { - if (frame.lines != _textFrame.lines && _textFrame.count) { - // We must release each line before overriding the old one - for (int i = 0; i < _textFrame.count; i++) { - CFRelease(_textFrame.lines[0]); - } - free(_textFrame.lines); - free(_textFrame.widths); + if (frame.lines != _textFrame.lines) { + ARTFreeTextFrame(_textFrame); } [self invalidate]; _textFrame = frame; @@ -35,14 +42,7 @@ - (void)dealloc { - if (_textFrame.count) { - // We must release each line before freeing up this struct - for (int i = 0; i < _textFrame.count; i++) { - CFRelease(_textFrame.lines[0]); - } - free(_textFrame.lines); - free(_textFrame.widths); - } + ARTFreeTextFrame(_textFrame); } - (void)renderLayerTo:(CGContextRef)context diff --git a/Libraries/Animation/Animated/Interpolation.js b/Libraries/Animation/Animated/Interpolation.js index b4939846f..fecbf0d4a 100644 --- a/Libraries/Animation/Animated/Interpolation.js +++ b/Libraries/Animation/Animated/Interpolation.js @@ -243,7 +243,7 @@ function checkValidInputRange(arr: Array) { * mean this implicit string conversion, you can do something like * String(myThing) */ - 'inputRange must be monolithically increasing ' + arr + 'inputRange must be monotonically increasing ' + arr ); } } diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index 7a629ce9a..63534af3e 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -104,7 +104,33 @@ var Image = React.createClass({ * * {nativeEvent: { layout: {x, y, width, height}}}. */ - onLayout: PropTypes.func, + onLayout: PropTypes.func, + /** + * Invoked on load start + */ + onLoadStart: PropTypes.func, + /** + * Invoked on download progress with + * + * {nativeEvent: { written, total}}. + */ + onLoadProgress: PropTypes.func, + /** + * Invoked on load abort + */ + onLoadAbort: PropTypes.func, + /** + * Invoked on load error + * + * {nativeEvent: { error}}. + */ + onLoadError: PropTypes.func, + /** + * Invoked on load end + * + */ + onLoaded: PropTypes.func + }, statics: { @@ -161,6 +187,7 @@ var Image = React.createClass({ if (this.props.defaultSource) { nativeProps.defaultImageSrc = this.props.defaultSource.uri; } + nativeProps.progressHandlerRegistered = isNetwork && this.props.onLoadProgress; return ; } }); @@ -178,6 +205,7 @@ var nativeOnlyProps = { src: true, defaultImageSrc: true, imageTag: true, + progressHandlerRegistered: true }; if (__DEV__) { verifyPropTypes(Image, RCTStaticImage.viewConfig, nativeOnlyProps); diff --git a/Libraries/Image/RCTDownloadTaskWrapper.h b/Libraries/Image/RCTDownloadTaskWrapper.h new file mode 100644 index 000000000..41337fac7 --- /dev/null +++ b/Libraries/Image/RCTDownloadTaskWrapper.h @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +typedef void (^RCTDataCompletionBlock)(NSURLResponse *response, NSData *data, NSError *error); +typedef void (^RCTDataProgressBlock)(int64_t written, int64_t total); + +@interface RCTDownloadTaskWrapper : NSObject + +- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration delegateQueue:(NSOperationQueue *)delegateQueue; + +- (NSURLSessionDownloadTask *)downloadData:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock completionBlock:(RCTDataCompletionBlock)completionBlock; + +@end diff --git a/Libraries/Image/RCTDownloadTaskWrapper.m b/Libraries/Image/RCTDownloadTaskWrapper.m new file mode 100644 index 000000000..21c5f6d7a --- /dev/null +++ b/Libraries/Image/RCTDownloadTaskWrapper.m @@ -0,0 +1,103 @@ +/** + * 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 "RCTDownloadTaskWrapper.h" + +#import + +static void *const RCTDownloadTaskWrapperCompletionBlockKey = (void *)&RCTDownloadTaskWrapperCompletionBlockKey; +static void *const RCTDownloadTaskWrapperProgressBlockKey = (void *)&RCTDownloadTaskWrapperProgressBlockKey; + +@interface NSURLSessionTask (RCTDownloadTaskWrapper) + +@property (nonatomic, copy, setter=rct_setCompletionBlock:) RCTDataCompletionBlock rct_completionBlock; +@property (nonatomic, copy, setter=rct_setProgressBlock:) RCTDataProgressBlock rct_progressBlock; + +@end + +@implementation NSURLSessionTask (RCTDownloadTaskWrapper) + +- (RCTDataCompletionBlock)rct_completionBlock +{ + return objc_getAssociatedObject(self, RCTDownloadTaskWrapperCompletionBlockKey); +} + +- (void)rct_setCompletionBlock:(RCTDataCompletionBlock)completionBlock +{ + objc_setAssociatedObject(self, RCTDownloadTaskWrapperCompletionBlockKey, completionBlock, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + +- (RCTDataProgressBlock)rct_progressBlock +{ + return objc_getAssociatedObject(self, RCTDownloadTaskWrapperProgressBlockKey); +} + +- (void)rct_setProgressBlock:(RCTDataProgressBlock)progressBlock +{ + objc_setAssociatedObject(self, RCTDownloadTaskWrapperProgressBlockKey, progressBlock, OBJC_ASSOCIATION_COPY_NONATOMIC); +} + +@end + +@implementation RCTDownloadTaskWrapper +{ + NSURLSession *_URLSession; +} + +- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration delegateQueue:(NSOperationQueue *)delegateQueue +{ + if ((self = [super init])) { + _URLSession = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; + } + + return self; +} + +- (NSURLSessionDownloadTask *)downloadData:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock completionBlock:(RCTDataCompletionBlock)completionBlock +{ + NSURLSessionDownloadTask *task = [_URLSession downloadTaskWithURL:url completionHandler:nil]; + task.rct_completionBlock = completionBlock; + task.rct_progressBlock = progressBlock; + + [task resume]; + return task; +} + +#pragma mark - NSURLSessionTaskDelegate methods + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location +{ + if (downloadTask.rct_completionBlock) { + NSData *data = [NSData dataWithContentsOfURL:location]; + dispatch_async(dispatch_get_main_queue(), ^{ + downloadTask.rct_completionBlock(downloadTask.response, data, nil); + }); + } +} + +- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)didWriteData totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite; +{ + if (downloadTask.rct_progressBlock) { + dispatch_async(dispatch_get_main_queue(), ^{ + downloadTask.rct_progressBlock(totalBytesWritten, totalBytesExpectedToWrite); + }); + } +} + +- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error +{ + if (error && task.rct_completionBlock) { + dispatch_async(dispatch_get_main_queue(), ^{ + task.rct_completionBlock(nil, nil, error); + }); + } +} + +@end diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj index 1735f33b4..0127cfd8e 100644 --- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj +++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */; }; 1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */; }; 1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; }; 1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; }; @@ -32,6 +33,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 03559E7D1B064D3A00730281 /* RCTDownloadTaskWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTaskWrapper.h; sourceTree = ""; }; + 03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTaskWrapper.m; sourceTree = ""; }; 1304D5A71AA8C4A30002E2BE /* RCTStaticImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImage.h; sourceTree = ""; }; 1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStaticImage.m; sourceTree = ""; }; 1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStaticImageManager.h; sourceTree = ""; }; @@ -71,6 +74,8 @@ children = ( 143879331AAD238D00F088A5 /* RCTCameraRollManager.h */, 143879341AAD238D00F088A5 /* RCTCameraRollManager.m */, + 03559E7D1B064D3A00730281 /* RCTDownloadTaskWrapper.h */, + 03559E7E1B064DAF00730281 /* RCTDownloadTaskWrapper.m */, 1304D5B01AA8C50D0002E2BE /* RCTGIFImage.h */, 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */, 58B511891A9E6BD600147676 /* RCTImageDownloader.h */, @@ -168,6 +173,7 @@ 1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */, 143879351AAD238D00F088A5 /* RCTCameraRollManager.m in Sources */, 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */, + 03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */, 1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Libraries/Image/RCTImageDownloader.h b/Libraries/Image/RCTImageDownloader.h index 5a4dd1987..44b2b7369 100644 --- a/Libraries/Image/RCTImageDownloader.h +++ b/Libraries/Image/RCTImageDownloader.h @@ -9,8 +9,11 @@ #import +#import "RCTDownloadTaskWrapper.h" + typedef void (^RCTDataDownloadBlock)(NSData *data, NSError *error); typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error); +typedef void (^RCTImageDownloadCancellationBlock)(void); @interface RCTImageDownloader : NSObject @@ -21,8 +24,9 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error); * 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. */ -- (id)downloadDataForURL:(NSURL *)url - block:(RCTDataDownloadBlock)block; +- (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url + progressBlock:(RCTDataProgressBlock)progressBlock + block:(RCTDataDownloadBlock)block; /** * Downloads an image and decompresses it a the size specified. The compressed @@ -30,18 +34,19 @@ typedef void (^RCTImageDownloadBlock)(UIImage *image, NSError *error); * 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. */ -- (id)downloadImageForURL:(NSURL *)url - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(UIViewContentMode)resizeMode - backgroundColor:(UIColor *)backgroundColor - block:(RCTImageDownloadBlock)block; +- (RCTImageDownloadCancellationBlock)downloadImageForURL:(NSURL *)url + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(UIViewContentMode)resizeMode + backgroundColor:(UIColor *)backgroundColor + progressBlock:(RCTDataProgressBlock)progressBlock + block:(RCTImageDownloadBlock)block; /** * Cancel an in-flight download. If multiple requets have been made for the * same image, only the request that relates to the token passed will be * cancelled. */ -- (void)cancelDownload:(id)downloadToken; +- (void)cancelDownload:(RCTImageDownloadCancellationBlock)downloadToken; @end diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m index ea448220b..fc519c4f2 100644 --- a/Libraries/Image/RCTImageDownloader.m +++ b/Libraries/Image/RCTImageDownloader.m @@ -9,6 +9,7 @@ #import "RCTImageDownloader.h" +#import "RCTDownloadTaskWrapper.h" #import "RCTLog.h" #import "RCTUtils.h" @@ -22,6 +23,7 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); NSURLCache *_cache; dispatch_queue_t _processingQueue; NSMutableDictionary *_pendingBlocks; + RCTDownloadTaskWrapper *_downloadTaskWrapper; } + (RCTImageDownloader *)sharedInstance @@ -40,19 +42,22 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); _cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 diskCapacity:200 * 1024 * 1024 diskPath:@"React/RCTImageDownloader"]; _processingQueue = dispatch_queue_create("com.facebook.React.DownloadProcessingQueue", DISPATCH_QUEUE_SERIAL); _pendingBlocks = [[NSMutableDictionary alloc] init]; + + NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; + _downloadTaskWrapper = [[RCTDownloadTaskWrapper alloc] initWithSessionConfiguration:config delegateQueue:nil]; } return self; } -- (id)_downloadDataForURL:(NSURL *)url block:(RCTCachedDataDownloadBlock)block +- (RCTImageDownloadCancellationBlock)_downloadDataForURL:(NSURL *)url progressBlock:progressBlock block:(RCTCachedDataDownloadBlock)block { - NSString *cacheKey = url.absoluteString; + NSString *const cacheKey = url.absoluteString; __block BOOL cancelled = NO; - __block NSURLSessionDataTask *task = nil; + __block NSURLSessionDownloadTask *task = nil; - dispatch_block_t cancel = ^{ + RCTImageDownloadCancellationBlock cancel = ^{ cancelled = YES; dispatch_async(_processingQueue, ^{ @@ -85,7 +90,8 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); }); }; - task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + task = [_downloadTaskWrapper downloadData:url progressBlock:progressBlock completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) { if (!cancelled) { runBlocks(NO, data, error); } @@ -93,12 +99,12 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); if (response) { RCTImageDownloader *strongSelf = weakSelf; NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed]; - [strongSelf->_cache storeCachedResponse:cachedResponse forDataTask:task]; + [strongSelf->_cache storeCachedResponse:cachedResponse forRequest:request]; } task = nil; }]; - [_cache getCachedResponseForDataTask:task completionHandler:^(NSCachedURLResponse *cachedResponse) { + NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request]; if (cancelled) { return; } @@ -108,28 +114,29 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); } else { [task resume]; } - }]; + } }); return [cancel copy]; } -- (id)downloadDataForURL:(NSURL *)url block:(RCTDataDownloadBlock)block +- (RCTImageDownloadCancellationBlock)downloadDataForURL:(NSURL *)url progressBlock:(RCTDataProgressBlock)progressBlock block:(RCTDataDownloadBlock)block { - return [self _downloadDataForURL:url block:^(BOOL cached, NSData *data, NSError *error) { + return [self _downloadDataForURL:url progressBlock:progressBlock block:^(BOOL cached, NSData *data, NSError *error) { block(data, error); }]; } -- (id)downloadImageForURL:(NSURL *)url - size:(CGSize)size - scale:(CGFloat)scale - resizeMode:(UIViewContentMode)resizeMode - backgroundColor:(UIColor *)backgroundColor - block:(RCTImageDownloadBlock)block +- (RCTImageDownloadCancellationBlock)downloadImageForURL:(NSURL *)url + size:(CGSize)size + scale:(CGFloat)scale + resizeMode:(UIViewContentMode)resizeMode + backgroundColor:(UIColor *)backgroundColor + progressBlock:(RCTDataProgressBlock)progressBlock + block:(RCTImageDownloadBlock)block { - return [self downloadDataForURL:url block:^(NSData *data, NSError *error) { + return [self downloadDataForURL:url progressBlock:progressBlock block:^(NSData *data, NSError *error) { if (!data || error) { block(nil, error); return; @@ -176,10 +183,10 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode); }]; } -- (void)cancelDownload:(id)downloadToken +- (void)cancelDownload:(RCTImageDownloadCancellationBlock)downloadToken { if (downloadToken) { - ((dispatch_block_t)downloadToken)(); + downloadToken(); } } diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 04fa17f5d..0e4a9c171 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -121,7 +121,7 @@ static dispatch_queue_t RCTImageLoaderQueue(void) RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil); return; } - [[RCTImageDownloader sharedInstance] downloadDataForURL:url block:^(NSData *data, NSError *error) { + [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) { if (error) { RCTDispatchCallbackOnMainQueue(callback, error, nil); } else { diff --git a/Libraries/Image/RCTNetworkImageView.h b/Libraries/Image/RCTNetworkImageView.h index e04f71e32..6cdf31216 100644 --- a/Libraries/Image/RCTNetworkImageView.h +++ b/Libraries/Image/RCTNetworkImageView.h @@ -9,11 +9,13 @@ #import +@class RCTEventDispatcher; @class RCTImageDownloader; @interface RCTNetworkImageView : UIView -- (instancetype)initWithImageDownloader:(RCTImageDownloader *)imageDownloader NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher + imageDownloader:(RCTImageDownloader *)imageDownloader NS_DESIGNATED_INITIALIZER; /** * An image that will appear while the view is loading the image from the network, diff --git a/Libraries/Image/RCTNetworkImageView.m b/Libraries/Image/RCTNetworkImageView.m index 5a286e020..8c6748f76 100644 --- a/Libraries/Image/RCTNetworkImageView.m +++ b/Libraries/Image/RCTNetworkImageView.m @@ -14,23 +14,27 @@ #import "RCTGIFImage.h" #import "RCTImageDownloader.h" #import "RCTUtils.h" +#import "RCTBridgeModule.h" +#import "RCTEventDispatcher.h" #import "UIView+React.h" @implementation RCTNetworkImageView { BOOL _deferred; + BOOL _progressHandlerRegistered; NSURL *_imageURL; NSURL *_deferredImageURL; NSUInteger _deferSentinel; RCTImageDownloader *_imageDownloader; id _downloadToken; + RCTEventDispatcher *_eventDispatcher; } -- (instancetype)initWithImageDownloader:(RCTImageDownloader *)imageDownloader +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher imageDownloader:(RCTImageDownloader *)imageDownloader { - RCTAssertParam(imageDownloader); - if ((self = [super initWithFrame:CGRectZero])) { + _eventDispatcher = eventDispatcher; + _progressHandlerRegistered = NO; _deferSentinel = 0; _imageDownloader = imageDownloader; self.userInteractionEnabled = NO; @@ -56,6 +60,11 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) [self _updateImage]; } +- (void)setProgressHandlerRegistered:(BOOL)progressHandlerRegistered +{ + _progressHandlerRegistered = progressHandlerRegistered; +} + - (void)reactSetFrame:(CGRect)frame { [super reactSetFrame:frame]; @@ -89,8 +98,34 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) self.layer.minificationFilter = kCAFilterTrilinear; self.layer.magnificationFilter = kCAFilterTrilinear; } + [_eventDispatcher sendInputEventWithName:@"loadStart" body:@{ @"target": self.reactTag }]; + + RCTDataProgressBlock progressHandler = ^(int64_t written, int64_t total) { + if (_progressHandlerRegistered) { + NSDictionary *event = @{ + @"target": self.reactTag, + @"written": @(written), + @"total": @(total), + }; + [_eventDispatcher sendInputEventWithName:@"loadProgress" body:event]; + } + }; + + void (^errorHandler)(NSString *errorDescription) = ^(NSString *errorDescription) { + NSDictionary *event = @{ + @"target": self.reactTag, + @"error": errorDescription, + }; + [_eventDispatcher sendInputEventWithName:@"loadError" body:event]; + }; + + void (^loadEndHandler)(void) = ^(void) { + NSDictionary *event = @{ @"target": self.reactTag }; + [_eventDispatcher sendInputEventWithName:@"loaded" body:event]; + }; + if ([imageURL.pathExtension caseInsensitiveCompare:@"gif"] == NSOrderedSame) { - _downloadToken = [_imageDownloader downloadDataForURL:imageURL block:^(NSData *data, NSError *error) { + _downloadToken = [_imageDownloader downloadDataForURL:imageURL progressBlock:progressHandler block:^(NSData *data, NSError *error) { if (data) { dispatch_async(dispatch_get_main_queue(), ^{ if (imageURL != self.imageURL) { @@ -102,13 +137,16 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) self.layer.minificationFilter = kCAFilterLinear; self.layer.magnificationFilter = kCAFilterLinear; [self.layer addAnimation:animation forKey:@"contents"]; + loadEndHandler(); }); } else if (error) { - RCTLogWarn(@"Unable to download image data. Error: %@", error); + errorHandler([error description]); } }]; } else { - _downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() resizeMode:self.contentMode backgroundColor:self.backgroundColor block:^(UIImage *image, NSError *error) { + _downloadToken = [_imageDownloader downloadImageForURL:imageURL size:self.bounds.size scale:RCTScreenScale() + resizeMode:self.contentMode backgroundColor:self.backgroundColor + progressBlock:progressHandler block:^(UIImage *image, NSError *error) { if (image) { dispatch_async(dispatch_get_main_queue(), ^{ if (imageURL != self.imageURL) { @@ -118,9 +156,10 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) [self.layer removeAnimationForKey:@"contents"]; self.layer.contentsScale = image.scale; self.layer.contents = (__bridge id)image.CGImage; + loadEndHandler(); }); } else if (error) { - RCTLogWarn(@"Unable to download image. Error: %@", error); + errorHandler([error description]); } }]; } diff --git a/Libraries/Image/RCTNetworkImageViewManager.m b/Libraries/Image/RCTNetworkImageViewManager.m index 3fcd4a75b..706496e34 100644 --- a/Libraries/Image/RCTNetworkImageViewManager.m +++ b/Libraries/Image/RCTNetworkImageViewManager.m @@ -9,24 +9,38 @@ #import "RCTNetworkImageViewManager.h" -#import "RCTNetworkImageView.h" - +#import "RCTBridge.h" #import "RCTConvert.h" -#import "RCTUtils.h" - #import "RCTImageDownloader.h" +#import "RCTNetworkImageView.h" +#import "RCTUtils.h" @implementation RCTNetworkImageViewManager RCT_EXPORT_MODULE() +@synthesize bridge = _bridge; +@synthesize methodQueue = _methodQueue; + - (UIView *)view { - return [[RCTNetworkImageView alloc] initWithImageDownloader:[RCTImageDownloader sharedInstance]]; + return [[RCTNetworkImageView alloc] initWithEventDispatcher:self.bridge.eventDispatcher imageDownloader:[RCTImageDownloader sharedInstance]]; } RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage) RCT_REMAP_VIEW_PROPERTY(src, imageURL, NSURL) RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode) +RCT_EXPORT_VIEW_PROPERTY(progressHandlerRegistered, BOOL) + +- (NSDictionary *)customDirectEventTypes +{ + return @{ + @"loadStart": @{ @"registrationName": @"onLoadStart" }, + @"loadProgress": @{ @"registrationName": @"onLoadProgress" }, + @"loaded": @{ @"registrationName": @"onLoaded" }, + @"loadError": @{ @"registrationName": @"onLoadError" }, + @"loadAbort": @{ @"registrationName": @"onLoadAbort" }, + }; +} @end diff --git a/Libraries/Text/RCTTextView.m b/Libraries/Text/RCTTextView.m index 31850be5a..f32debd47 100644 --- a/Libraries/Text/RCTTextView.m +++ b/Libraries/Text/RCTTextView.m @@ -222,7 +222,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder) - (UIFont *)defaultPlaceholderFont { - return [UIFont fontWithName:@"Helvetica" size:17]; + return [UIFont systemFontOfSize:17]; } - (UIColor *)defaultPlaceholderTextColor diff --git a/Libraries/Utilities/MessageQueue.js b/Libraries/Utilities/MessageQueue.js index 660d09876..7163b1caf 100644 --- a/Libraries/Utilities/MessageQueue.js +++ b/Libraries/Utilities/MessageQueue.js @@ -26,6 +26,8 @@ let MODULE_IDS = 0; let METHOD_IDS = 1; let PARAMS = 2; +let SPY_MODE = false; + let MethodTypes = keyMirror({ local: null, remote: null, @@ -120,6 +122,10 @@ class MessageQueue { (this._debugInfo[this._callbackID >> 5] = null); this._debugInfo[this._callbackID >> 1] = [module, method]; + if (SPY_MODE && isFinite(module)) { + console.log('JS->N : ' + this._remoteModuleTable[module] + '.' + + this._remoteMethodTable[module][method] + '(' + JSON.stringify(params) + ')'); + } } onFail && params.push(this._callbackID); this._callbacks[this._callbackID++] = onFail; @@ -137,6 +143,9 @@ class MessageQueue { method = this._methodTable[module][method]; module = this._moduleTable[module]; } + if (__DEV__ && SPY_MODE) { + console.log('N->JS : ' + module + '.' + method + '(' + JSON.stringify(args) + ')'); + } module = this._require(module); module[method].apply(module, args); BridgeProfiling.profileEnd(); @@ -146,11 +155,15 @@ class MessageQueue { BridgeProfiling.profile( () => `MessageQueue.invokeCallback(${cbID}, ${stringifySafe(args)})`); let callback = this._callbacks[cbID]; - if (__DEV__ && !callback) { + if (__DEV__) { let debug = this._debugInfo[cbID >> 1]; let module = this._remoteModuleTable[debug[0]]; let method = this._remoteMethodTable[debug[0]][debug[1]]; - console.error(`Callback with id ${cbID}: ${module}.${method}() not found`); + if (!callback) { + console.error(`Callback with id ${cbID}: ${module}.${method}() not found`); + } else if (SPY_MODE) { + console.log('N->JS : (' + JSON.stringify(args) + ')'); + } } this._callbacks[cbID & ~1] = null; this._callbacks[cbID | 1] = null; diff --git a/React/Base/RCTBridgeModule.h b/React/Base/RCTBridgeModule.h index 15937a9f6..8efbef462 100644 --- a/React/Base/RCTBridgeModule.h +++ b/React/Base/RCTBridgeModule.h @@ -38,7 +38,6 @@ typedef void (^RCTPromiseResolveBlock)(id result); */ typedef void (^RCTPromiseRejectBlock)(NSError *error); - /** * This constant can be returned from +methodQueue to force module * methods to be called on the JavaScript thread. This can have serious diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index 1424d0d81..1b010accd 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -39,6 +39,7 @@ + (NSUInteger)NSUInteger:(id)json; + (NSArray *)NSArray:(id)json; ++ (NSSet *)NSSet:(id)json; + (NSDictionary *)NSDictionary:(id)json; + (NSString *)NSString:(id)json; + (NSNumber *)NSNumber:(id)json; diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 60316f740..9ffbbaf8d 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -35,6 +35,7 @@ RCT_NUMBER_CONVERTER(NSInteger, integerValue) RCT_NUMBER_CONVERTER(NSUInteger, unsignedIntegerValue) RCT_CUSTOM_CONVERTER(NSArray *, NSArray, [NSArray arrayWithArray:json]) +RCT_CUSTOM_CONVERTER(NSSet *, NSSet, [NSSet setWithArray:json]) RCT_CUSTOM_CONVERTER(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json]) RCT_CONVERTER(NSString *, NSString, description) @@ -671,8 +672,8 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[ NSURL *url = [NSURL URLWithString:path]; NSData *imageData = [NSData dataWithContentsOfURL:url]; image = [UIImage imageWithData:imageData]; - } else if ([path isAbsolutePath]) { - image = [UIImage imageWithContentsOfFile:path]; + } else if ([path isAbsolutePath] || [path hasPrefix:@"~"]) { + image = [UIImage imageWithContentsOfFile:path.stringByExpandingTildeInPath]; } else { image = [UIImage imageNamed:path]; if (!image) { @@ -788,7 +789,8 @@ static BOOL RCTFontIsCondensed(UIFont *font) size:(id)size weight:(id)weight style:(id)style { // Defaults - NSString *const RCTDefaultFontFamily = @"Helvetica Neue"; + NSString *const RCTDefaultFontFamily = @"System"; + NSString *const RCTIOS8SystemFontFamily = @"Helvetica Neue"; const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular; const CGFloat RCTDefaultFontSize = 14; @@ -807,11 +809,36 @@ static BOOL RCTFontIsCondensed(UIFont *font) isCondensed = RCTFontIsCondensed(font); } - // Get font size + // Get font attributes fontSize = [self CGFloat:size] ?: fontSize; - - // Get font family familyName = [self NSString:family] ?: familyName; + isItalic = style ? [self RCTFontStyle:style] : isItalic; + fontWeight = weight ? [self RCTFontWeight:weight] : fontWeight; + + // Handle system font as special case. This ensures that we preserve + // the specific metrics of the standard system font as closely as possible. + if ([familyName isEqual:RCTDefaultFontFamily]) { + if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) { + font = [UIFont systemFontOfSize:fontSize weight:fontWeight]; + if (isItalic || isCondensed) { + UIFontDescriptor *fontDescriptor = [font fontDescriptor]; + UIFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits; + if (isItalic) { + symbolicTraits |= UIFontDescriptorTraitItalic; + } + if (isCondensed) { + symbolicTraits |= UIFontDescriptorTraitCondensed; + } + fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits]; + font = [UIFont fontWithDescriptor:fontDescriptor size:fontSize]; + } + return font; + } else { + // systemFontOfSize:weight: isn't available prior to iOS 8.2, so we + // fall back to finding the correct font manually, by linear search. + familyName = RCTIOS8SystemFontFamily; + } + } // Gracefully handle being given a font name rather than font family, for // example: "Helvetica Light Oblique" rather than just "Helvetica". @@ -821,30 +848,25 @@ static BOOL RCTFontIsCondensed(UIFont *font) // It's actually a font name, not a font family name, // but we'll do what was meant, not what was said. familyName = font.familyName; - fontWeight = RCTWeightOfFont(font); - isItalic = RCTFontIsItalic(font); + fontWeight = weight ? fontWeight : RCTWeightOfFont(font); + isItalic = style ? isItalic : RCTFontIsItalic(font); isCondensed = RCTFontIsCondensed(font); } else { // Not a valid font or family RCTLogError(@"Unrecognized font family '%@'", familyName); - familyName = RCTDefaultFontFamily; + if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) { + font = [UIFont systemFontOfSize:fontSize weight:fontWeight]; + } else if (fontWeight > UIFontWeightRegular) { + font = [UIFont boldSystemFontOfSize:fontSize]; + } else { + font = [UIFont systemFontOfSize:fontSize]; + } } } - // Get font style - if (style) { - isItalic = [self RCTFontStyle:style]; - } - - // Get font weight - if (weight) { - fontWeight = [self RCTFontWeight:weight]; - } - // Get the closest font that matches the given weight for the fontFamily - UIFont *bestMatch = [UIFont fontWithName:font.fontName size: fontSize]; + UIFont *bestMatch = font; CGFloat closestWeight = INFINITY; - for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) { UIFont *match = [UIFont fontWithName:name size:fontSize]; if (isItalic == RCTFontIsItalic(match) && @@ -857,14 +879,6 @@ static BOOL RCTFontIsCondensed(UIFont *font) } } - // Safety net - if (!bestMatch) { - RCTLogError(@"Could not find font with family: '%@', size: %@, \ - weight: %@, style: %@", family, size, weight, style); - bestMatch = [UIFont fontWithName:[[UIFont fontNamesForFamilyName:familyName] firstObject] - size:fontSize]; - } - return bestMatch; } diff --git a/React/Base/RCTModuleMethod.m b/React/Base/RCTModuleMethod.m index 46e329538..e56b11a8e 100644 --- a/React/Base/RCTModuleMethod.m +++ b/React/Base/RCTModuleMethod.m @@ -32,6 +32,8 @@ NSArray *_argumentBlocks; } +RCT_NOT_IMPLEMENTED(-init) + - (instancetype)initWithObjCMethodName:(NSString *)objCMethodName JSMethodName:(NSString *)JSMethodName moduleClass:(Class)moduleClass diff --git a/React/Base/RCTPerformanceLogger.m b/React/Base/RCTPerformanceLogger.m index b43ab2252..c19f4938a 100644 --- a/React/Base/RCTPerformanceLogger.m +++ b/React/Base/RCTPerformanceLogger.m @@ -64,6 +64,8 @@ RCT_EXPORT_MODULE() - (void)sendTimespans { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [_bridge enqueueJSCall:@"PerformanceLogger.addTimespans" args:@[ RCTPerformanceLoggerOutput(), @[ diff --git a/package.json b/package.json index da164ea48..2b8b33496 100644 --- a/package.json +++ b/package.json @@ -64,10 +64,12 @@ "react-tools": "0.13.2", "rebound": "^0.0.12", "sane": "^1.1.2", + "semver": "^4.3.6", "source-map": "0.1.31", "stacktrace-parser": "frantic/stacktrace-parser#493c5e5638", "uglify-js": "~2.4.16", "underscore": "1.7.0", + "wordwrap": "^1.0.0", "worker-farm": "^1.3.1", "ws": "0.4.31", "yargs": "1.3.2" diff --git a/packager/checkNodeVersion.js b/packager/checkNodeVersion.js new file mode 100644 index 000000000..f1a07c030 --- /dev/null +++ b/packager/checkNodeVersion.js @@ -0,0 +1,40 @@ +/** + * 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. + */ +'use strict'; + +var chalk = require('chalk'); +var semver = require('semver'); + +var formatBanner = require('./formatBanner'); + +function checkNodeVersion() { + if (!semver.satisfies(process.version, '>=2.0.0')) { + var engine = semver.lt(process.version, '1.0.0') ? 'Node' : 'io.js'; + var message = 'You are currently running ' + engine + ' ' + + process.version + '.\n' + + '\n' + + 'React Native is moving to io.js 2.x. There are several ways to upgrade' + + 'to io.js depending on your preference.\n' + + '\n' + + 'nvm: nvm install iojs && nvm alias default iojs\n' + + 'Homebrew: brew unlink node; brew install iojs && brew ln iojs --force\n' + + 'Installer: download the Mac .pkg from https://iojs.org/\n' + + '\n' + + 'About io.js: https://iojs.org\n' + + 'Follow along at: https://github.com/facebook/react-native/issues/1737'; + console.log(formatBanner(message, { + chalkFunction: chalk.green, + marginLeft: 1, + marginRight: 1, + paddingBottom: 1, + })); + } +} + +module.exports = checkNodeVersion; diff --git a/packager/formatBanner.js b/packager/formatBanner.js new file mode 100644 index 000000000..f54095b05 --- /dev/null +++ b/packager/formatBanner.js @@ -0,0 +1,108 @@ +/** + * 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. + */ +'use strict'; + +var _ = require('underscore'); +var wordwrap = require('wordwrap'); + +var HORIZONTAL_LINE = '\u2500'; +var VERTICAL_LINE = '\u2502'; +var TOP_LEFT = '\u250c'; +var TOP_RIGHT = '\u2510'; +var BOTTOM_LEFT = '\u2514'; +var BOTTOM_RIGHT = '\u2518'; + +/** + * Prints a banner with a border around it containing the given message. The + * following options are supported: + * + * type Options = { + * // A function to apply to each line of text to decorate it + * chalkFunction: (string: message) => string; + * // The total width (max line length) of the banner, including margin and + * // padding (default = 80) + * width: number; + * // How much leading space to prepend to each line (default = 0) + * marginLeft: number; + * // How much trailing space to append to each line (default = 0) + * marginRight: number; + * // Space between the top banner border and the text (default = 0) + * paddingTop: number; + * // Space between the bottom banner border and the text (default = 0) + * paddingBottom: number; + * // Space between the left banner border and the text (default = 2) + * paddingLeft: number; + * // Space between the right banner border and the text (default = 2) + * paddingRight: number; + * }; + */ +function formatBanner(message, options) { + options = options || {}; + _.defaults(options, { + chalkFunction: _.identity, + width: 80, + marginLeft: 0, + marginRight: 0, + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 2, + paddingRight: 2, + }); + + var width = options.width; + var marginLeft = options.marginLeft; + var marginRight = options.marginRight; + var paddingTop = options.paddingTop; + var paddingBottom = options.paddingBottom; + var paddingLeft = options.paddingLeft; + var paddingRight = options.paddingRight; + + var horizSpacing = marginLeft + paddingLeft + paddingRight + marginRight; + // 2 for the banner borders + var maxLineWidth = width - horizSpacing - 2; + var wrap = wordwrap(maxLineWidth); + var body = wrap(message); + + var left = spaces(marginLeft) + VERTICAL_LINE + spaces(paddingLeft); + var right = spaces(paddingRight) + VERTICAL_LINE + spaces(marginRight); + var bodyLines = _.flatten([ + arrayOf('', paddingTop), + body.split('\n'), + arrayOf('', paddingBottom), + ]).map(function(line) { + var padding = spaces(Math.max(0, maxLineWidth - line.length)); + return left + options.chalkFunction(line) + padding + right; + }); + + var horizontalBorderLine = repeatString( + HORIZONTAL_LINE, + width - marginLeft - marginRight - 2 + ); + var top = spaces(marginLeft) + TOP_LEFT + horizontalBorderLine + TOP_RIGHT + + spaces(marginRight); + var bottom = spaces(marginLeft) + BOTTOM_LEFT + horizontalBorderLine + + BOTTOM_RIGHT + spaces(marginRight); + return _.flatten([top, bodyLines, bottom]).join('\n'); +} + +function spaces(number) { + return repeatString(' ', number); +} + +function repeatString(string, number) { + return new Array(number + 1).join(string); +} + +function arrayOf(value, number) { + return _.range(number).map(function() { + return value; + }); +} + +module.exports = formatBanner; diff --git a/packager/package.json b/packager/package.json index cc3f4fc6f..f9927a87c 100644 --- a/packager/package.json +++ b/packager/package.json @@ -23,7 +23,9 @@ "lint": "node linter.js Examples/", "start": "./packager/packager.sh" }, - "dependencies": {}, + "dependencies": { + "wordwrap": "^1.0.0" + }, "devDependencies": { "jest-cli": "git://github.com/facebook/jest#0.5.x", "eslint": "0.9.2" diff --git a/packager/packager.js b/packager/packager.js index ff0faa315..fcff7e58f 100644 --- a/packager/packager.js +++ b/packager/packager.js @@ -30,6 +30,8 @@ var chalk = require('chalk'); var connect = require('connect'); var ReactPackager = require('./react-packager'); var blacklist = require('./blacklist.js'); +var checkNodeVersion = require('./checkNodeVersion'); +var formatBanner = require('./formatBanner'); var launchEditor = require('./launchEditor.js'); var parseCommandLine = require('./parseCommandLine.js'); var webSocketProxy = require('./webSocketProxy.js'); @@ -108,16 +110,19 @@ if (options.assetRoots) { } } -console.log('\n' + -' ===============================================================\n' + -' | Running packager on port ' + options.port + '. \n' + -' | Keep this packager running while developing on any JS \n' + -' | projects. Feel free to close this tab and run your own \n' + -' | packager instance if you prefer. \n' + -' | \n' + -' | https://github.com/facebook/react-native \n' + -' | \n' + -' ===============================================================\n' +checkNodeVersion(); + +console.log(formatBanner( + 'Running packager on port ' + options.port + '.\n'+ + '\n' + + 'Keep this packager running while developing on any JS projects. Feel free ' + + 'to close this tab and run your own packager instance if you prefer.\n' + + '\n' + + 'https://github.com/facebook/react-native', { + marginLeft: 1, + marginRight: 1, + paddingBottom: 1, + }) ); console.log(