diff --git a/Examples/UIExplorer/TextExample.ios.js b/Examples/UIExplorer/TextExample.ios.js
index 4aff00e73..cbc3e5b41 100644
--- a/Examples/UIExplorer/TextExample.ios.js
+++ b/Examples/UIExplorer/TextExample.ios.js
@@ -369,6 +369,25 @@ exports.examples = [
);
},
+}, {
+ title: 'allowFontScaling attribute',
+ render: function() {
+ return (
+
+
+ By default, text will respect Text Size accessibility setting on iOS.
+ It means that all font sizes will be increased or descreased depending on the value of Text Size setting in
+ {" "}Settings.app - Display & Brightness - Text Size
+
+
+ You can disable scaling for your Text component by passing {"\""}allowFontScaling={"{"}false{"}\""} prop.
+
+
+ This text will not scale.
+
+
+ );
+ },
}];
var styles = StyleSheet.create({
diff --git a/Libraries/ART/RCTConvert+ART.m b/Libraries/ART/RCTConvert+ART.m
index 7a607a12c..e6bc09286 100644
--- a/Libraries/ART/RCTConvert+ART.m
+++ b/Libraries/ART/RCTConvert+ART.m
@@ -87,7 +87,7 @@ RCT_ENUM_CONVERTER(CTTextAlignment, (@{
}
NSDictionary *fontDict = dict[@"font"];
- CTFontRef font = (__bridge CTFontRef)[self UIFont:nil withFamily:fontDict[@"fontFamily"] size:fontDict[@"fontSize"] weight:fontDict[@"fontWeight"] style:fontDict[@"fontStyle"]];
+ CTFontRef font = (__bridge CTFontRef)[self UIFont:nil withFamily:fontDict[@"fontFamily"] size:fontDict[@"fontSize"] weight:fontDict[@"fontWeight"] style:fontDict[@"fontStyle"] scaleMultiplier:1.0];
if (!font) {
return frame;
}
diff --git a/Libraries/Components/ScrollResponder.js b/Libraries/Components/ScrollResponder.js
index 94e0850f2..168cb36fa 100644
--- a/Libraries/Components/ScrollResponder.js
+++ b/Libraries/Components/ScrollResponder.js
@@ -469,9 +469,9 @@ var ScrollResponderMixin = {
this.props.onKeyboardWillHide && this.props.onKeyboardWillHide(e);
},
- scrollResponderKeyboardDidShow: function() {
+ scrollResponderKeyboardDidShow: function(e: Event) {
this.keyboardWillOpenTo = null;
- this.props.onKeyboardDidShow && this.props.onKeyboardDidShow();
+ this.props.onKeyboardDidShow && this.props.onKeyboardDidShow(e);
},
scrollResponderKeyboardDidHide: function() {
diff --git a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
index 1910c67ca..0be39a8f1 100644
--- a/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
+++ b/Libraries/Components/TabBarIOS/TabBarIOS.ios.js
@@ -32,7 +32,11 @@ var TabBarIOS = React.createClass({
/**
* Background color of the tab bar
*/
- barTintColor: React.PropTypes.string
+ barTintColor: React.PropTypes.string,
+ /**
+ * A Boolean value that indicates whether the tab bar is translucent
+ */
+ translucent: React.PropTypes.bool,
},
render: function() {
@@ -40,7 +44,8 @@ var TabBarIOS = React.createClass({
+ barTintColor={this.props.barTintColor}
+ translucent={this.props.translucent !== false}>
{this.props.children}
);
diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js
index 0cb6e4a4f..6912c05db 100644
--- a/Libraries/Components/View/View.js
+++ b/Libraries/Components/View/View.js
@@ -44,6 +44,12 @@ var AccessibilityTraits = [
'pageTurn',
];
+// <<<<< WARNING >>>>>
+// If adding any properties to View that could change the way layout-only status
+// works on iOS, make sure to update ReactNativeViewAttributes.js and
+// RCTShadowView.m (in the -[RCTShadowView isLayoutOnly] method).
+// <<<<< WARNING >>>>>
+
/**
* The most fundamental component for building UI, `View` is a
* container that supports layout with flexbox, style, some touch handling, and
diff --git a/Libraries/CustomComponents/ListView/ListView.js b/Libraries/CustomComponents/ListView/ListView.js
index 496ff4bb2..16faeb100 100644
--- a/Libraries/CustomComponents/ListView/ListView.js
+++ b/Libraries/CustomComponents/ListView/ListView.js
@@ -29,7 +29,7 @@
var ListViewDataSource = require('ListViewDataSource');
var React = require('React');
var RCTUIManager = require('NativeModules').UIManager;
-var RKScrollViewManager = require('NativeModules').ScrollViewManager;
+var RCTScrollViewManager = require('NativeModules').ScrollViewManager;
var ScrollView = require('ScrollView');
var ScrollResponder = require('ScrollResponder');
var StaticRenderer = require('StaticRenderer');
@@ -417,9 +417,10 @@ var ListView = React.createClass({
this._setScrollVisibleHeight
);
- // RKScrollViewManager.calculateChildFrames not available on every platform
- RKScrollViewManager && RKScrollViewManager.calculateChildFrames &&
- RKScrollViewManager.calculateChildFrames(
+ // RCTScrollViewManager.calculateChildFrames is not available on
+ // every platform
+ RCTScrollViewManager && RCTScrollViewManager.calculateChildFrames &&
+ RCTScrollViewManager.calculateChildFrames(
React.findNodeHandle(scrollComponent),
this._updateChildFrames,
);
diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
index 0127cfd8e..8ecabbafd 100644
--- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
+++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
@@ -12,6 +12,7 @@
1304D5AC1AA8C4A30002E2BE /* RCTStaticImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */; };
1304D5B21AA8C50D0002E2BE /* RCTGIFImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */; };
1345A8391B26592900583190 /* RCTImageRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1345A8381B26592900583190 /* RCTImageRequestHandler.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 */; };
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
@@ -43,6 +44,8 @@
1304D5B11AA8C50D0002E2BE /* RCTGIFImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGIFImage.m; sourceTree = ""; };
1345A8371B26592900583190 /* RCTImageRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageRequestHandler.h; sourceTree = ""; };
1345A8381B26592900583190 /* RCTImageRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageRequestHandler.m; sourceTree = ""; };
+ 134B00A01B54232B00EC8DFB /* RCTImageUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageUtils.h; sourceTree = ""; };
+ 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageUtils.m; sourceTree = ""; };
137620331B31C53500677FF0 /* RCTImagePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImagePickerManager.h; sourceTree = ""; };
137620341B31C53500677FF0 /* RCTImagePickerManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImagePickerManager.m; sourceTree = ""; };
143879331AAD238D00F088A5 /* RCTCameraRollManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTCameraRollManager.h; sourceTree = ""; };
@@ -94,6 +97,8 @@
1304D5A81AA8C4A30002E2BE /* RCTStaticImage.m */,
1304D5A91AA8C4A30002E2BE /* RCTStaticImageManager.h */,
1304D5AA1AA8C4A30002E2BE /* RCTStaticImageManager.m */,
+ 134B00A01B54232B00EC8DFB /* RCTImageUtils.h */,
+ 134B00A11B54232B00EC8DFB /* RCTImageUtils.m */,
58B5115E1A9E6B3D00147676 /* Products */,
);
indentWidth = 2;
@@ -175,6 +180,7 @@
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
03559E7F1B064DAF00730281 /* RCTDownloadTaskWrapper.m in Sources */,
1304D5AB1AA8C4A30002E2BE /* RCTStaticImage.m in Sources */,
+ 134B00A21B54232B00EC8DFB /* RCTImageUtils.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m
index 0f9cad198..697214249 100644
--- a/Libraries/Image/RCTImageDownloader.m
+++ b/Libraries/Image/RCTImageDownloader.m
@@ -10,6 +10,7 @@
#import "RCTImageDownloader.h"
#import "RCTDownloadTaskWrapper.h"
+#import "RCTImageUtils.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@@ -195,82 +196,3 @@ CGRect RCTClipRect(CGSize, CGFloat, CGSize, CGFloat, UIViewContentMode);
}
@end
-
-/**
- * Returns the optimal context size for an image drawn using the clip rect
- * returned by RCTClipRect.
- */
-CGSize RCTTargetSizeForClipRect(CGRect clipRect)
-{
- return (CGSize){
- clipRect.size.width + clipRect.origin.x * 2,
- clipRect.size.height + clipRect.origin.y * 2
- };
-}
-
-/**
- * This function takes an input content size & scale (typically from an image),
- * a target size & scale that it will be drawn into (typically a CGContext) and
- * then calculates the optimal rectangle to draw the image into so that it will
- * be sized and positioned correctly if drawn using the specified content mode.
- */
-CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
- CGSize destSize, CGFloat destScale,
- UIViewContentMode resizeMode)
-{
- // Precompensate for scale
- CGFloat scale = sourceScale / destScale;
- sourceSize.width *= scale;
- sourceSize.height *= scale;
-
- // Calculate aspect ratios if needed (don't bother is resizeMode == stretch)
- CGFloat aspect = 0.0, targetAspect = 0.0;
- if (resizeMode != UIViewContentModeScaleToFill) {
- aspect = sourceSize.width / sourceSize.height;
- targetAspect = destSize.width / destSize.height;
- if (aspect == targetAspect) {
- resizeMode = UIViewContentModeScaleToFill;
- }
- }
-
- switch (resizeMode) {
- case UIViewContentModeScaleToFill: // stretch
-
- sourceSize.width = MIN(destSize.width, sourceSize.width);
- sourceSize.height = MIN(destSize.height, sourceSize.height);
- return (CGRect){CGPointZero, sourceSize};
-
- case UIViewContentModeScaleAspectFit: // contain
-
- if (targetAspect <= aspect) { // target is taller than content
- sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
- sourceSize.height = sourceSize.width / aspect;
- } else { // target is wider than content
- sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
- sourceSize.width = sourceSize.height * aspect;
- }
- return (CGRect){CGPointZero, sourceSize};
-
- case UIViewContentModeScaleAspectFill: // cover
-
- if (targetAspect <= aspect) { // target is taller than content
-
- sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
- sourceSize.width = sourceSize.height * aspect;
- destSize.width = destSize.height * targetAspect;
- return (CGRect){{(destSize.width - sourceSize.width) / 2, 0}, sourceSize};
-
- } else { // target is wider than content
-
- sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
- sourceSize.height = sourceSize.width / aspect;
- destSize.height = destSize.width / targetAspect;
- return (CGRect){{0, (destSize.height - sourceSize.height) / 2}, sourceSize};
- }
-
- default:
-
- RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
- return (CGRect){CGPointZero, destSize};
- }
-}
diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h
index 186a53cd1..4337836fd 100644
--- a/Libraries/Image/RCTImageLoader.h
+++ b/Libraries/Image/RCTImageLoader.h
@@ -7,20 +7,37 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
-#import
+#import
@class ALAssetsLibrary;
-@class UIImage;
@interface RCTImageLoader : NSObject
+/**
+ * The shared asset library instance.
+ */
+ (ALAssetsLibrary *)assetsLibrary;
/**
* Can be called from any thread.
* Will always call callback on main thread.
*/
-+ (void)loadImageWithTag:(NSString *)tag
++ (void)loadImageWithTag:(NSString *)imageTag
callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback;
+/**
+ * As above, but includes target size, scale and resizeMode, which are used to
+ * select the optimal dimensions for the loaded image.
+ */
++ (void)loadImageWithTag:(NSString *)imageTag
+ size:(CGSize)size
+ scale:(CGFloat)scale
+ resizeMode:(UIViewContentMode)resizeMode
+ callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback;
+
+/**
+ * Is the specified image tag an asset library image?
+ */
++ (BOOL)isAssetLibraryImage:(NSString *)imageTag;
+
@end
diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m
index 0e4a9c171..69d98a60a 100644
--- a/Libraries/Image/RCTImageLoader.m
+++ b/Libraries/Image/RCTImageLoader.m
@@ -19,6 +19,7 @@
#import "RCTDefines.h"
#import "RCTGIFImage.h"
#import "RCTImageDownloader.h"
+#import "RCTImageUtils.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@@ -56,13 +57,23 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
return assetsLibrary;
}
-/**
- * Can be called from any thread.
- * Will always call callback on main thread.
- */
-+ (void)loadImageWithTag:(NSString *)imageTag callback:(void (^)(NSError *error, id image))callback
++ (void)loadImageWithTag:(NSString *)imageTag
+ callback:(void (^)(NSError *error, id /* UIImage or CAAnimation */ image))callback
{
- if ([imageTag hasPrefix:@"assets-library"]) {
+ return [self loadImageWithTag:imageTag
+ size:CGSizeZero
+ scale:0
+ resizeMode:UIViewContentModeScaleToFill
+ callback:callback];
+}
+
++ (void)loadImageWithTag:(NSString *)imageTag
+ size:(CGSize)size
+ scale:(CGFloat)scale
+ resizeMode:(UIViewContentMode)resizeMode
+ callback:(void (^)(NSError *error, id image))callback
+{
+ if ([imageTag hasPrefix:@"assets-library://"]) {
[[RCTImageLoader assetsLibrary] assetForURL:[NSURL URLWithString:imageTag] resultBlock:^(ALAsset *asset) {
if (asset) {
// ALAssetLibrary API is async and will be multi-threaded. Loading a few full
@@ -73,9 +84,31 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
// Also make sure the image is released immediately after it's used so it
// doesn't spike the memory up during the process.
@autoreleasepool {
- ALAssetRepresentation *representation = [asset defaultRepresentation];
- ALAssetOrientation orientation = [representation orientation];
- UIImage *image = [UIImage imageWithCGImage:[representation fullResolutionImage] scale:1.0f orientation:(UIImageOrientation)orientation];
+
+ BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
+ ALAssetOrientation orientation = ALAssetOrientationUp;
+ CGImageRef imageRef = NULL;
+
+ if (!useMaximumSize) {
+ imageRef = asset.thumbnail;
+ }
+ if (RCTUpscalingRequired((CGSize){CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}, 1, size, scale, resizeMode)) {
+ if (!useMaximumSize) {
+ imageRef = asset.aspectRatioThumbnail;
+ }
+ if (RCTUpscalingRequired((CGSize){CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}, 1, size, scale, resizeMode)) {
+ ALAssetRepresentation *representation = [asset defaultRepresentation];
+ orientation = [representation orientation];
+ if (!useMaximumSize) {
+ imageRef = [representation fullScreenImage];
+ }
+ if (RCTUpscalingRequired((CGSize){CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}, 1, size, scale, resizeMode)) {
+ imageRef = [representation fullResolutionImage];
+ }
+ }
+ }
+
+ UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale orientation:(UIImageOrientation)orientation];
RCTDispatchCallbackOnMainQueue(callback, nil, image);
}
});
@@ -91,9 +124,9 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
}];
} else if ([imageTag hasPrefix:@"ph://"]) {
// Using PhotoKit for iOS 8+
- // '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 NSURL which is what assets-library is based on.
- // This means if we use any FB standard photo picker, we will get this prefix =(
+ // 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) {
@@ -104,7 +137,12 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
}
PHAsset *asset = [results firstObject];
- [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
+ CGSize targetSize = CGSizeEqualToSize(size, CGSizeZero) ? PHImageManagerMaximumSize : size;
+ PHImageContentMode contentMode = PHImageContentModeAspectFill;
+ if (resizeMode == UIViewContentModeScaleAspectFit) {
+ contentMode = PHImageContentModeAspectFit;
+ }
+ [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:targetSize contentMode:contentMode options:nil resultHandler:^(UIImage *result, NSDictionary *info) {
if (result) {
RCTDispatchCallbackOnMainQueue(callback, nil, result);
} else {
@@ -121,13 +159,20 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
RCTDispatchCallbackOnMainQueue(callback, RCTErrorWithMessage(errorMessage), nil);
return;
}
- [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) {
- if (error) {
- RCTDispatchCallbackOnMainQueue(callback, error, nil);
- } else {
- RCTDispatchCallbackOnMainQueue(callback, nil, [UIImage imageWithData:data]);
- }
- }];
+ if ([[imageTag lowercaseString] hasSuffix:@".gif"]) {
+ [[RCTImageDownloader sharedInstance] downloadDataForURL:url progressBlock:nil block:^(NSData *data, NSError *error) {
+ id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
+ if (!image && !error) {
+ NSString *errorMessage = [NSString stringWithFormat:@"Unable to load GIF image: %@", imageTag];
+ error = RCTErrorWithMessage(errorMessage);
+ }
+ RCTDispatchCallbackOnMainQueue(callback, error, image);
+ }];
+ } else {
+ [[RCTImageDownloader sharedInstance] downloadImageForURL:url size:size scale:scale resizeMode:resizeMode tintColor:nil backgroundColor:nil progressBlock:NULL block:^(UIImage *image, NSError *error) {
+ RCTDispatchCallbackOnMainQueue(callback, error, image);
+ }];
+ }
} else if ([[imageTag lowercaseString] hasSuffix:@".gif"]) {
id image = RCTGIFImageWithFileURL([RCTConvert NSURL:imageTag]);
if (image) {
@@ -149,4 +194,9 @@ static dispatch_queue_t RCTImageLoaderQueue(void)
}
}
++ (BOOL)isAssetLibraryImage:(NSString *)imageTag
+{
+ return [imageTag hasPrefix:@"assets-library://"] || [imageTag hasPrefix:@"ph:"];
+}
+
@end
diff --git a/Libraries/Image/RCTImageUtils.h b/Libraries/Image/RCTImageUtils.h
new file mode 100644
index 000000000..cbb38cda8
--- /dev/null
+++ b/Libraries/Image/RCTImageUtils.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013, 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
+
+#import "RCTDefines.h"
+
+/**
+ * Returns the optimal context size for an image drawn using the clip rect
+ * returned by RCTClipRect.
+ */
+RCT_EXTERN CGSize RCTTargetSizeForClipRect(CGRect clipRect);
+
+/**
+ * This function takes an input content size & scale (typically from an image),
+ * a target size & scale that it will be drawn into (typically a CGContext) and
+ * then calculates the optimal rectangle to draw the image into so that it will
+ * be sized and positioned correctly if drawn using the specified content mode.
+ */
+RCT_EXTERN CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
+ CGSize destSize, CGFloat destScale,
+ UIViewContentMode resizeMode);
+
+/**
+ * This function takes an input content size & scale (typically from an image),
+ * a target size & scale that it will be displayed at, and determines if the
+ * source will need to be upscaled to fit (which may result in pixelization).
+ */
+RCT_EXTERN BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
+ CGSize destSize, CGFloat destScale,
+ UIViewContentMode resizeMode);
diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m
new file mode 100644
index 000000000..89d269532
--- /dev/null
+++ b/Libraries/Image/RCTImageUtils.m
@@ -0,0 +1,147 @@
+/**
+ * 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 "RCTImageUtils.h"
+
+#import "RCTLog.h"
+
+CGSize RCTTargetSizeForClipRect(CGRect clipRect)
+{
+ return (CGSize){
+ clipRect.size.width + clipRect.origin.x * 2,
+ clipRect.size.height + clipRect.origin.y * 2
+ };
+}
+
+CGRect RCTClipRect(CGSize sourceSize, CGFloat sourceScale,
+ CGSize destSize, CGFloat destScale,
+ UIViewContentMode resizeMode)
+{
+ if (CGSizeEqualToSize(destSize, CGSizeZero)) {
+ // Assume we require the largest size available
+ return (CGRect){CGPointZero, sourceSize};
+ }
+
+ // Precompensate for scale
+ CGFloat scale = sourceScale / destScale;
+ sourceSize.width *= scale;
+ sourceSize.height *= scale;
+
+ // Calculate aspect ratios if needed (don't bother if resizeMode == stretch)
+ CGFloat aspect = 0.0, targetAspect = 0.0;
+ if (resizeMode != UIViewContentModeScaleToFill) {
+ aspect = sourceSize.width / sourceSize.height;
+ targetAspect = destSize.width / destSize.height;
+ if (aspect == targetAspect) {
+ resizeMode = UIViewContentModeScaleToFill;
+ }
+ }
+
+ switch (resizeMode) {
+ case UIViewContentModeScaleToFill: // stretch
+
+ sourceSize.width = MIN(destSize.width, sourceSize.width);
+ sourceSize.height = MIN(destSize.height, sourceSize.height);
+ return (CGRect){CGPointZero, sourceSize};
+
+ case UIViewContentModeScaleAspectFit: // contain
+
+ if (targetAspect <= aspect) { // target is taller than content
+
+ sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
+ sourceSize.height = sourceSize.width / aspect;
+
+ } else { // target is wider than content
+
+ sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
+ sourceSize.width = sourceSize.height * aspect;
+ }
+ return (CGRect){CGPointZero, sourceSize};
+
+ case UIViewContentModeScaleAspectFill: // cover
+
+ if (targetAspect <= aspect) { // target is taller than content
+
+ sourceSize.height = destSize.height = MIN(sourceSize.height, destSize.height);
+ sourceSize.width = sourceSize.height * aspect;
+ destSize.width = destSize.height * targetAspect;
+ return (CGRect){{(destSize.width - sourceSize.width) / 2, 0}, sourceSize};
+
+ } else { // target is wider than content
+
+ sourceSize.width = destSize.width = MIN(sourceSize.width, destSize.width);
+ sourceSize.height = sourceSize.width / aspect;
+ destSize.height = destSize.width / targetAspect;
+ return (CGRect){{0, (destSize.height - sourceSize.height) / 2}, sourceSize};
+ }
+
+ default:
+
+ RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
+ return (CGRect){CGPointZero, destSize};
+ }
+}
+
+RCT_EXTERN BOOL RCTUpscalingRequired(CGSize sourceSize, CGFloat sourceScale,
+ CGSize destSize, CGFloat destScale,
+ UIViewContentMode resizeMode)
+{
+ if (CGSizeEqualToSize(destSize, CGSizeZero)) {
+ // Assume we require the largest size available
+ return YES;
+ }
+
+ // Precompensate for scale
+ CGFloat scale = sourceScale / destScale;
+ sourceSize.width *= scale;
+ sourceSize.height *= scale;
+
+ // Calculate aspect ratios if needed (don't bother if resizeMode == stretch)
+ CGFloat aspect = 0.0, targetAspect = 0.0;
+ if (resizeMode != UIViewContentModeScaleToFill) {
+ aspect = sourceSize.width / sourceSize.height;
+ targetAspect = destSize.width / destSize.height;
+ if (aspect == targetAspect) {
+ resizeMode = UIViewContentModeScaleToFill;
+ }
+ }
+
+ switch (resizeMode) {
+ case UIViewContentModeScaleToFill: // stretch
+
+ return destSize.width > sourceSize.width || destSize.height > sourceSize.height;
+
+ case UIViewContentModeScaleAspectFit: // contain
+
+ if (targetAspect <= aspect) { // target is taller than content
+
+ return destSize.width > sourceSize.width;
+
+ } else { // target is wider than content
+
+ return destSize.height > sourceSize.height;
+ }
+
+ case UIViewContentModeScaleAspectFill: // cover
+
+ if (targetAspect <= aspect) { // target is taller than content
+
+ return destSize.height > sourceSize.height;
+
+ } else { // target is wider than content
+
+ return destSize.width > sourceSize.width;
+ }
+
+ default:
+
+ RCTLogError(@"A resizeMode value of %zd is not supported", resizeMode);
+ return NO;
+ }
+}
diff --git a/Libraries/Image/RCTStaticImage.h b/Libraries/Image/RCTStaticImage.h
index eb82b597c..c8f46a302 100644
--- a/Libraries/Image/RCTStaticImage.h
+++ b/Libraries/Image/RCTStaticImage.h
@@ -13,5 +13,6 @@
@property (nonatomic, assign) UIEdgeInsets capInsets;
@property (nonatomic, assign) UIImageRenderingMode renderingMode;
+@property (nonatomic, copy) NSString *src;
@end
diff --git a/Libraries/Image/RCTStaticImage.m b/Libraries/Image/RCTStaticImage.m
index f9bef7c5d..0e9d4b608 100644
--- a/Libraries/Image/RCTStaticImage.m
+++ b/Libraries/Image/RCTStaticImage.m
@@ -9,6 +9,13 @@
#import "RCTStaticImage.h"
+#import "RCTConvert.h"
+#import "RCTGIFImage.h"
+#import "RCTImageLoader.h"
+#import "RCTUtils.h"
+
+#import "UIView+React.h"
+
@implementation RCTStaticImage
- (void)_updateImage
@@ -59,4 +66,54 @@
}
}
+- (void)setSrc:(NSString *)src
+{
+ if (![src isEqual:_src]) {
+ _src = [src copy];
+ [self reloadImage];
+ }
+}
+
+- (void)reloadImage
+{
+ if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
+ [RCTImageLoader loadImageWithTag:_src
+ size:self.bounds.size
+ scale:RCTScreenScale()
+ resizeMode:self.contentMode callback:^(NSError *error, id image) {
+ if (error) {
+ RCTLogWarn(@"%@", error.localizedDescription);
+ }
+ if ([image isKindOfClass:[CAAnimation class]]) {
+ [self.layer addAnimation:image forKey:@"contents"];
+ } else {
+ [self.layer removeAnimationForKey:@"contents"];
+ self.image = image;
+ }
+ }];
+ } else {
+ [self.layer removeAnimationForKey:@"contents"];
+ self.image = nil;
+ }
+}
+
+- (void)reactSetFrame:(CGRect)frame
+{
+ [super reactSetFrame:frame];
+ if (self.image == nil) {
+ [self reloadImage];
+ } else if ([RCTImageLoader isAssetLibraryImage:_src]) {
+ CGSize imageSize = {
+ self.image.size.width / RCTScreenScale(),
+ self.image.size.height / RCTScreenScale()
+ };
+ CGFloat widthChangeFraction = imageSize.width ? ABS(imageSize.width - frame.size.width) / imageSize.width : 1;
+ CGFloat heightChangeFraction = imageSize.height ? ABS(imageSize.height - frame.size.height) / imageSize.height : 1;
+ // If the combined change is more than 20%, reload the asset in case there is a better size.
+ if (widthChangeFraction + heightChangeFraction > 0.2) {
+ [self reloadImage];
+ }
+ }
+}
+
@end
diff --git a/Libraries/Image/RCTStaticImageManager.m b/Libraries/Image/RCTStaticImageManager.m
index bdc6f0596..7b3fb16db 100644
--- a/Libraries/Image/RCTStaticImageManager.m
+++ b/Libraries/Image/RCTStaticImageManager.m
@@ -12,8 +12,6 @@
#import
#import "RCTConvert.h"
-#import "RCTGIFImage.h"
-#import "RCTImageLoader.h"
#import "RCTStaticImage.h"
@implementation RCTStaticImageManager
@@ -26,21 +24,9 @@ RCT_EXPORT_MODULE()
}
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
+RCT_REMAP_VIEW_PROPERTY(imageTag, src, NSString)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
-RCT_CUSTOM_VIEW_PROPERTY(src, NSURL, RCTStaticImage)
-{
- if (json) {
- if ([[[json description] pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
- [view.layer addAnimation:RCTGIFImageWithFileURL([RCTConvert NSURL:json]) forKey:@"contents"];
- } else {
- [view.layer removeAnimationForKey:@"contents"];
- view.image = [RCTConvert UIImage:json];
- }
- } else {
- [view.layer removeAnimationForKey:@"contents"];
- view.image = defaultView.image;
- }
-}
+RCT_EXPORT_VIEW_PROPERTY(src, NSString)
RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTStaticImage)
{
if (json) {
@@ -51,24 +37,5 @@ RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTStaticImage)
view.tintColor = defaultView.tintColor;
}
}
-RCT_CUSTOM_VIEW_PROPERTY(imageTag, NSString, RCTStaticImage)
-{
- if (json) {
- [RCTImageLoader loadImageWithTag:[RCTConvert NSString:json] callback:^(NSError *error, id image) {
- if (error) {
- RCTLogWarn(@"%@", error.localizedDescription);
- }
- if ([image isKindOfClass:[CAAnimation class]]) {
- [view.layer addAnimation:image forKey:@"contents"];
- } else {
- [view.layer removeAnimationForKey:@"contents"];
- view.image = image;
- }
- }];
- } else {
- [view.layer removeAnimationForKey:@"contents"];
- view.image = defaultView.image;
- }
-}
@end
diff --git a/Libraries/PushNotificationIOS/PushNotificationIOS.js b/Libraries/PushNotificationIOS/PushNotificationIOS.js
index 1781ddff1..c71722ae8 100644
--- a/Libraries/PushNotificationIOS/PushNotificationIOS.js
+++ b/Libraries/PushNotificationIOS/PushNotificationIOS.js
@@ -36,6 +36,31 @@ class PushNotificationIOS {
_sound: string;
_badgeCount: number;
+ /**
+ * Schedules the localNotification for immediate presentation.
+ *
+ * details is an object containing:
+ *
+ * - `alertBody` : The message displayed in the notification alert.
+ *
+ */
+ static presentLocalNotification(details: Object) {
+ RCTPushNotificationManager.presentLocalNotification(details);
+ }
+
+ /**
+ * Schedules the localNotification for future presentation.
+ *
+ * details is an object containing:
+ *
+ * - `fireDate` : The date and time when the system should deliver the notification.
+ * - `alertBody` : The message displayed in the notification alert.
+ *
+ */
+ static scheduleLocalNotification(details: Object) {
+ RCTPushNotificationManager.scheduleLocalNotification(details);
+ }
+
/**
* Sets the badge number for the app icon on the home screen
*/
diff --git a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
index 6a9420819..ac683fc2a 100644
--- a/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
+++ b/Libraries/PushNotificationIOS/RCTPushNotificationManager.m
@@ -10,6 +10,7 @@
#import "RCTPushNotificationManager.h"
#import "RCTBridge.h"
+#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTUtils.h"
@@ -26,6 +27,19 @@
NSString *const RCTRemoteNotificationReceived = @"RemoteNotificationReceived";
NSString *const RCTRemoteNotificationsRegistered = @"RemoteNotificationsRegistered";
+@implementation RCTConvert (UILocalNotification)
+
++ (UILocalNotification *)UILocalNotification:(id)json
+{
+ NSDictionary *details = [self NSDictionary:json];
+ UILocalNotification *notification = [[UILocalNotification alloc] init];
+ notification.fireDate = [RCTConvert NSDate:details[@"fireDate"]] ?: [NSDate date];
+ notification.alertBody = [RCTConvert NSString:details[@"alertBody"]];
+ return notification;
+}
+
+@end
+
@implementation RCTPushNotificationManager
{
NSDictionary *_initialNotification;
@@ -139,11 +153,15 @@ RCT_EXPORT_METHOD(requestPermissions:(NSDictionary *)permissions)
}
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_8_0
+
id notificationSettings = [UIUserNotificationSettings settingsForTypes:types categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
[[UIApplication sharedApplication] registerForRemoteNotifications];
+
#else
+
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:types];
+
#endif
}
@@ -183,4 +201,15 @@ RCT_EXPORT_METHOD(checkPermissions:(RCTResponseSenderBlock)callback)
};
}
+RCT_EXPORT_METHOD(presentLocalNotification:(UILocalNotification *)notification)
+{
+ [[UIApplication sharedApplication] presentLocalNotificationNow:notification];
+}
+
+
+RCT_EXPORT_METHOD(scheduleLocalNotification:(UILocalNotification *)notification)
+{
+ [[UIApplication sharedApplication] scheduleLocalNotification:notification];
+}
+
@end
diff --git a/Libraries/ReactNative/ReactNativeViewAttributes.js b/Libraries/ReactNative/ReactNativeViewAttributes.js
index eb3a396ca..367c6be2d 100644
--- a/Libraries/ReactNative/ReactNativeViewAttributes.js
+++ b/Libraries/ReactNative/ReactNativeViewAttributes.js
@@ -26,7 +26,11 @@ ReactNativeViewAttributes.UIView = {
onMagicTap: true,
collapsible: true,
- // If any below are set, view should not be collapsible!
+ // If editing layout-only view attributes, make sure
+ // -[RCTShadowView isLayoutOnly] in RCTShadowView.m
+ // is up-to-date! If any property below is set, the
+ // view should not be collapsible, but this is done
+ // on the native side.
onMoveShouldSetResponder: true,
onResponderGrant: true,
onResponderMove: true,
diff --git a/Libraries/Text/RCTShadowRawText.m b/Libraries/Text/RCTShadowRawText.m
index 00a3490bc..4f79d64b5 100644
--- a/Libraries/Text/RCTShadowRawText.m
+++ b/Libraries/Text/RCTShadowRawText.m
@@ -9,8 +9,32 @@
#import "RCTShadowRawText.h"
+#import "RCTUIManager.h"
+
@implementation RCTShadowRawText
+- (instancetype)init
+{
+ if ((self = [super init])) {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(contentSizeMultiplierDidChange:)
+ name:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)contentSizeMultiplierDidChange:(NSNotification *)note
+{
+ [self dirtyLayout];
+ [self dirtyText];
+}
+
- (void)setText:(NSString *)text
{
if (_text != text) {
diff --git a/Libraries/Text/RCTShadowText.h b/Libraries/Text/RCTShadowText.h
index fe87e99c1..abb111879 100644
--- a/Libraries/Text/RCTShadowText.h
+++ b/Libraries/Text/RCTShadowText.h
@@ -30,6 +30,8 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, strong) UIColor *textDecorationColor;
@property (nonatomic, assign) NSUnderlineStyle textDecorationStyle;
@property (nonatomic, assign) RCTTextDecorationLineType textDecorationLine;
+@property (nonatomic, assign) CGFloat fontSizeMultiplier;
+@property (nonatomic, assign) BOOL allowFontScaling;
- (void)recomputeText;
diff --git a/Libraries/Text/RCTShadowText.m b/Libraries/Text/RCTShadowText.m
index 621df1ed6..adcca5aed 100644
--- a/Libraries/Text/RCTShadowText.m
+++ b/Libraries/Text/RCTShadowText.m
@@ -9,6 +9,9 @@
#import "RCTShadowText.h"
+#import "RCTAccessibilityManager.h"
+#import "RCTUIManager.h"
+#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTShadowRawText.h"
@@ -51,16 +54,31 @@ static css_dim_t RCTMeasure(void *context, float width)
_letterSpacing = NAN;
_isHighlighted = NO;
_textDecorationStyle = NSUnderlineStyleSingle;
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(contentSizeMultiplierDidChange:)
+ name:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification
+ object:nil];
}
return self;
}
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
- (NSString *)description
{
NSString *superDescription = super.description;
return [[superDescription substringToIndex:superDescription.length - 1] stringByAppendingFormat:@"; text: %@>", [self attributedString].string];
}
+- (void)contentSizeMultiplierDidChange:(NSNotification *)note
+{
+ [self dirtyLayout];
+ [self dirtyText];
+}
+
- (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks
parentProperties:(NSDictionary *)parentProperties
{
@@ -190,7 +208,9 @@ static css_dim_t RCTMeasure(void *context, float width)
[self _addAttribute:NSBackgroundColorAttributeName withValue:self.backgroundColor toAttributedString:attributedString];
}
- UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily size:fontSize weight:fontWeight style:fontStyle];
+ UIFont *font = [RCTConvert UIFont:nil withFamily:fontFamily
+ size:fontSize weight:fontWeight style:fontStyle
+ scaleMultiplier:(_allowFontScaling && _fontSizeMultiplier > 0.0 ? _fontSizeMultiplier : 1.0)];
[self _addAttribute:NSFontAttributeName withValue:font toAttributedString:attributedString];
[self _addAttribute:NSKernAttributeName withValue:letterSpacing toAttributedString:attributedString];
[self _addAttribute:RCTReactTagAttributeName withValue:self.reactTag toAttributedString:attributedString];
@@ -247,8 +267,9 @@ static css_dim_t RCTMeasure(void *context, float width)
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = _textAlign;
paragraphStyle.baseWritingDirection = _writingDirection;
- paragraphStyle.minimumLineHeight = _lineHeight;
- paragraphStyle.maximumLineHeight = _lineHeight;
+ CGFloat lineHeight = round(_lineHeight * self.fontSizeMultiplier);
+ paragraphStyle.minimumLineHeight = lineHeight;
+ paragraphStyle.maximumLineHeight = lineHeight;
[attributedString addAttribute:NSParagraphStyleAttributeName
value:paragraphStyle
range:(NSRange){0, attributedString.length}];
@@ -321,4 +342,26 @@ RCT_TEXT_PROPERTY(TextDecorationLine, _textDecorationLine, RCTTextDecorationLine
RCT_TEXT_PROPERTY(TextDecorationStyle, _textDecorationStyle, NSUnderlineStyle);
RCT_TEXT_PROPERTY(WritingDirection, _writingDirection, NSWritingDirection)
+- (void)setAllowFontScaling:(BOOL)allowFontScaling
+{
+ _allowFontScaling = allowFontScaling;
+ for (RCTShadowView *child in [self reactSubviews]) {
+ if ([child isKindOfClass:[RCTShadowText class]]) {
+ [(RCTShadowText *)child setAllowFontScaling:allowFontScaling];
+ }
+ }
+ [self dirtyText];
+}
+
+- (void)setFontSizeMultiplier:(CGFloat)fontSizeMultiplier
+{
+ _fontSizeMultiplier = fontSizeMultiplier;
+ for (RCTShadowView *child in [self reactSubviews]) {
+ if ([child isKindOfClass:[RCTShadowText class]]) {
+ [(RCTShadowText *)child setFontSizeMultiplier:fontSizeMultiplier];
+ }
+ }
+ [self dirtyText];
+}
+
@end
diff --git a/Libraries/Text/RCTTextManager.m b/Libraries/Text/RCTTextManager.m
index b5f959b73..06d52088a 100644
--- a/Libraries/Text/RCTTextManager.m
+++ b/Libraries/Text/RCTTextManager.m
@@ -9,6 +9,7 @@
#import "RCTTextManager.h"
+#import "RCTAccessibilityManager.h"
#import "RCTAssert.h"
#import "RCTConvert.h"
#import "RCTLog.h"
@@ -49,6 +50,7 @@ RCT_EXPORT_SHADOW_PROPERTY(textDecorationStyle, NSUnderlineStyle)
RCT_EXPORT_SHADOW_PROPERTY(textDecorationColor, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(textDecorationLine, RCTTextDecorationLineType)
RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection)
+RCT_EXPORT_SHADOW_PROPERTY(allowFontScaling, BOOL)
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
@@ -69,6 +71,7 @@ RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection)
RCTAssert([shadowView isTextDirty], @"Don't process any nodes that don't have dirty text");
if ([shadowView isKindOfClass:[RCTShadowText class]]) {
+ [(RCTShadowText *)shadowView setFontSizeMultiplier:self.bridge.accessibilityManager.multiplier];
[(RCTShadowText *)shadowView recomputeText];
} else if ([shadowView isKindOfClass:[RCTShadowRawText class]]) {
RCTLogError(@"Raw text cannot be used outside of a tag. Not rendering string: '%@'",
diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js
index d02733749..7b370ac39 100644
--- a/Libraries/Text/Text.js
+++ b/Libraries/Text/Text.js
@@ -30,6 +30,7 @@ var viewConfig = {
validAttributes: merge(ReactNativeViewAttributes.UIView, {
isHighlighted: true,
numberOfLines: true,
+ allowFontScaling: true,
}),
uiViewClassName: 'RCTText',
};
@@ -99,16 +100,27 @@ var Text = React.createClass({
*
* {nativeEvent: {layout: {x, y, width, height}}}.
*/
- onLayout: React.PropTypes.func,
+ onLayout: React.PropTypes.func,
+ /**
+ * Specifies should fonts scale to respect Text Size accessibility setting.
+ */
+ allowFontScaling: React.PropTypes.bool,
},
viewConfig: viewConfig,
- getInitialState: function() {
+ getInitialState: function(): Object {
return merge(this.touchableGetInitialState(), {
isHighlighted: false,
});
},
+
+ getDefaultProps: function(): Object {
+ return {
+ numberOfLines: 0,
+ allowFontScaling: true,
+ };
+ },
onStartShouldSetResponder: function(): bool {
var shouldSetFromProps = this.props.onStartShouldSetResponder &&
@@ -231,6 +243,7 @@ if (Platform.OS === 'android') {
RCTVirtualText = createReactNativeComponentClass({
validAttributes: merge(ReactNativeViewAttributes.UIView, {
isHighlighted: true,
+ allowFontScaling: false,
}),
uiViewClassName: 'RCTVirtualText',
});
diff --git a/React/Base/RCTCache.h b/React/Base/RCTCache.h
index 9a4bef4df..67e26c3b7 100644
--- a/React/Base/RCTCache.h
+++ b/React/Base/RCTCache.h
@@ -15,7 +15,7 @@
* outside of the specified cost/count limits, and will be automatically
* cleared in the event of a memory warning.
*/
-@interface RCTCache : NSCache
+@interface RCTCache : NSCache
/**
* The total number of objects currently resident in the cache.
@@ -33,6 +33,11 @@
- (id)objectForKeyedSubscript:(id)key;
- (void)setObject:(id)obj forKeyedSubscript:(id)key;
+/**
+ * Enumerate cached objects
+ */
+- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block;
+
@end
@protocol RCTCacheDelegate
diff --git a/React/Base/RCTCache.m b/React/Base/RCTCache.m
index 073e3faaa..a0646a401 100644
--- a/React/Base/RCTCache.m
+++ b/React/Base/RCTCache.m
@@ -28,15 +28,6 @@
@implementation RCTCacheEntry
-+ (instancetype)entryWithObject:(id)object cost:(NSUInteger)cost sequenceNumber:(NSInteger)sequenceNumber
-{
- RCTCacheEntry *entry = [[self alloc] init];
- entry.object = object;
- entry.cost = cost;
- entry.sequenceNumber = sequenceNumber;
- return entry;
-}
-
@end
@interface RCTCache_Private : NSObject
@@ -46,24 +37,28 @@
@property (nonatomic, assign) NSUInteger totalCostLimit;
@property (nonatomic, copy) NSString *name;
-@property (nonatomic, assign) NSUInteger totalCost;
@property (nonatomic, strong) NSMutableDictionary *cache;
-@property (nonatomic, assign) BOOL delegateRespondsToWillEvictObject;
-@property (nonatomic, assign) BOOL delegateRespondsToShouldEvictObject;
-@property (nonatomic, assign) BOOL currentlyCleaning;
+@property (nonatomic, assign) NSUInteger totalCost;
@property (nonatomic, assign) NSInteger sequenceNumber;
-@property (nonatomic, strong) NSLock *lock;
@end
@implementation RCTCache_Private
+{
+ BOOL _delegateRespondsToWillEvictObject;
+ BOOL _delegateRespondsToShouldEvictObject;
+ BOOL _currentlyCleaning;
+ NSMutableArray *_entryPool;
+ NSLock *_lock;
+}
-- (instancetype)init
+- (id)init
{
if ((self = [super init]))
{
//create storage
_cache = [[NSMutableDictionary alloc] init];
+ _entryPool = [[NSMutableArray alloc] init];
_lock = [[NSLock alloc] init];
_totalCost = 0;
@@ -95,7 +90,7 @@
[_lock lock];
_countLimit = countLimit;
[_lock unlock];
- [self cleanUp];
+ [self cleanUp:NO];
}
- (void)setTotalCostLimit:(NSUInteger)totalCostLimit
@@ -103,7 +98,7 @@
[_lock lock];
_totalCostLimit = totalCostLimit;
[_lock unlock];
- [self cleanUp];
+ [self cleanUp:NO];
}
- (NSUInteger)count
@@ -111,40 +106,51 @@
return [_cache count];
}
-- (void)cleanUp
+- (void)cleanUp:(BOOL)keepEntries
{
[_lock lock];
- NSUInteger maxCount = [self countLimit] ?: INT_MAX;
- NSUInteger maxCost = [self totalCostLimit] ?: INT_MAX;
- NSUInteger totalCount = [_cache count];
- if (totalCount > maxCount || _totalCost > maxCost)
+ NSUInteger maxCount = _countLimit ?: INT_MAX;
+ NSUInteger maxCost = _totalCostLimit ?: INT_MAX;
+ NSUInteger totalCount = _cache.count;
+ NSMutableArray *keys = [_cache.allKeys mutableCopy];
+ while (totalCount > maxCount || _totalCost > maxCost)
{
- //sort, oldest first
- NSArray *keys = [[_cache allKeys] sortedArrayUsingComparator:^NSComparisonResult(id key1, id key2) {
- RCTCacheEntry *entry1 = self.cache[key1];
- RCTCacheEntry *entry2 = self.cache[key2];
- return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
- }];
+ NSInteger lowestSequenceNumber = INT_MAX;
+ RCTCacheEntry *lowestEntry = nil;
+ id lowestKey = nil;
//remove oldest items until within limit
for (id key in keys)
{
- if (totalCount <= maxCount && _totalCost <= maxCost)
- {
- break;
- }
RCTCacheEntry *entry = _cache[key];
- if (!_delegateRespondsToShouldEvictObject || [self.delegate cache:(RCTCache *)self shouldEvictObject:entry])
+ if (entry.sequenceNumber < lowestSequenceNumber)
+ {
+ lowestSequenceNumber = entry.sequenceNumber;
+ lowestEntry = entry;
+ lowestKey = key;
+ }
+ }
+
+ if (lowestKey)
+ {
+ [keys removeObject:lowestKey];
+ if (!_delegateRespondsToShouldEvictObject ||
+ [_delegate cache:(RCTCache *)self shouldEvictObject:lowestEntry.object])
{
if (_delegateRespondsToWillEvictObject)
{
_currentlyCleaning = YES;
- [self.delegate cache:(RCTCache *)self willEvictObject:entry];
+ [self.delegate cache:(RCTCache *)self willEvictObject:lowestEntry.object];
_currentlyCleaning = NO;
}
- [_cache removeObjectForKey:key];
- _totalCost -= entry.cost;
+ [_cache removeObjectForKey:lowestKey];
+ _totalCost -= lowestEntry.cost;
totalCount --;
+ if (keepEntries)
+ {
+ [_entryPool addObject:lowestEntry];
+ lowestEntry.object = nil;
+ }
}
}
}
@@ -161,8 +167,8 @@
{
//sort, oldest first (in case we want to use that information in our eviction test)
keys = [keys sortedArrayUsingComparator:^NSComparisonResult(id key1, id key2) {
- RCTCacheEntry *entry1 = self.cache[key1];
- RCTCacheEntry *entry2 = self.cache[key2];
+ RCTCacheEntry *entry1 = self->_cache[key1];
+ RCTCacheEntry *entry2 = self->_cache[key2];
return (NSComparisonResult)MIN(1, MAX(-1, entry1.sequenceNumber - entry2.sequenceNumber));
}];
}
@@ -171,12 +177,12 @@
for (id key in keys)
{
RCTCacheEntry *entry = _cache[key];
- if (!_delegateRespondsToShouldEvictObject || [self.delegate cache:(RCTCache *)self shouldEvictObject:entry])
+ if (!_delegateRespondsToShouldEvictObject || [_delegate cache:(RCTCache *)self shouldEvictObject:entry.object])
{
if (_delegateRespondsToWillEvictObject)
{
_currentlyCleaning = YES;
- [self.delegate cache:(RCTCache *)self willEvictObject:entry];
+ [_delegate cache:(RCTCache *)self willEvictObject:entry.object];
_currentlyCleaning = NO;
}
[_cache removeObjectForKey:key];
@@ -208,7 +214,7 @@
}
}
-- (id)objectForKey:(id)key
+- (id)objectForKey:(id)key
{
[_lock lock];
RCTCacheEntry *entry = _cache[key];
@@ -227,7 +233,7 @@
return [self objectForKey:key];
}
-- (void)setObject:(id)obj forKey:(id)key
+- (void)setObject:(id)obj forKey:(id)key
{
[self setObject:obj forKey:key cost:0];
}
@@ -237,27 +243,44 @@
[self setObject:obj forKey:key cost:0];
}
-- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g
+- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g
{
+ if (!obj)
+ {
+ [self removeObjectForKey:key];
+ return;
+ }
RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
_totalCost -= [_cache[key] cost];
_totalCost += g;
- _cache[key] = [RCTCacheEntry entryWithObject:obj cost:g sequenceNumber:_sequenceNumber++];
+ RCTCacheEntry *entry = _cache[key];
+ if (!entry) {
+ entry = [[RCTCacheEntry alloc] init];
+ _cache[key] = entry;
+ }
+ entry.object = obj;
+ entry.cost = g;
+ entry.sequenceNumber = _sequenceNumber++;
if (_sequenceNumber < 0)
{
[self resequence];
}
[_lock unlock];
- [self cleanUp];
+ [self cleanUp:YES];
}
-- (void)removeObjectForKey:(id)key
+- (void)removeObjectForKey:(id)key
{
RCTAssert(!_currentlyCleaning, @"It is not possible to modify cache from within the implementation of this delegate method.");
[_lock lock];
- _totalCost -= [_cache[key] cost];
- [_cache removeObjectForKey:key];
+ RCTCacheEntry *entry = _cache[key];
+ if (entry) {
+ _totalCost -= entry.cost;
+ entry.object = nil;
+ [_entryPool addObject:entry];
+ [_cache removeObjectForKey:key];
+ }
[_lock unlock];
}
@@ -267,20 +290,42 @@
[_lock lock];
_totalCost = 0;
_sequenceNumber = 0;
+ for (RCTCacheEntry *entry in _cache.allValues)
+ {
+ entry.object = nil;
+ [_entryPool addObject:entry];
+ }
[_cache removeAllObjects];
[_lock unlock];
}
+- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
+ objects:(id __unsafe_unretained [])buffer
+ count:(NSUInteger)len
+{
+ [_lock lock];
+ NSUInteger count = [_cache countByEnumeratingWithState:state objects:buffer count:len];
+ [_lock unlock];
+ return count;
+}
+
+- (void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))block
+{
+ [_lock lock];
+ [_cache enumerateKeysAndObjectsUsingBlock:block];
+ [_lock unlock];
+}
+
//handle unimplemented methods
-- (BOOL)isKindOfClass:(Class)cls
+- (BOOL)isKindOfClass:(Class)aClass
{
//pretend that we're an RCTCache if anyone asks
- if (cls == [RCTCache class] || cls == [NSCache class])
+ if (aClass == [RCTCache class] || aClass == [NSCache class])
{
return YES;
}
- return [super isKindOfClass:cls];
+ return [super isKindOfClass:aClass];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
@@ -308,19 +353,16 @@
@implementation RCTCache
-@dynamic count;
-@dynamic totalCost;
-
-+ (instancetype)alloc
++ (id)alloc
{
return (RCTCache *)[RCTCache_Private alloc];
}
-- (id)objectForKeyedSubscript:(__unused NSNumber *)key
-{
- return nil;
-}
-
+- (id)objectForKeyedSubscript:(__unused id)key { return nil; }
- (void)setObject:(__unused id)obj forKeyedSubscript:(__unused id)key {}
+- (void)enumerateKeysAndObjectsUsingBlock:(__unused void (^)(id, id, BOOL *))block { }
+- (NSUInteger)countByEnumeratingWithState:(__unused NSFastEnumerationState *)state
+ objects:(__unused __unsafe_unretained id [])buffer
+ count:(__unused NSUInteger)len { return 0; }
@end
diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h
index 1b010accd..d4657ec74 100644
--- a/React/Base/RCTConvert.h
+++ b/React/Base/RCTConvert.h
@@ -91,7 +91,8 @@ typedef NSURL RCTFileURL;
+ (UIFont *)UIFont:(UIFont *)font withStyle:(id)json;
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json;
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)family
- size:(id)size weight:(id)weight style:(id)style;
+ size:(id)size weight:(id)weight style:(id)style
+ scaleMultiplier:(CGFloat)scaleMultiplier;
typedef NSArray NSStringArray;
+ (NSStringArray *)NSStringArray:(id)json;
diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m
index 06e080546..2ff271f64 100644
--- a/React/Base/RCTConvert.m
+++ b/React/Base/RCTConvert.m
@@ -396,7 +396,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
colorCache = [[RCTCache alloc] init];
- colorCache.countLimit = 1024;
+ colorCache.countLimit = 128;
});
UIColor *color = colorCache[json];
if (color) {
@@ -779,31 +779,33 @@ static BOOL RCTFontIsCondensed(UIFont *font)
withFamily:json[@"fontFamily"]
size:json[@"fontSize"]
weight:json[@"fontWeight"]
- style:json[@"fontStyle"]];
+ style:json[@"fontStyle"]
+ scaleMultiplier:1.0f];
}
+ (UIFont *)UIFont:(UIFont *)font withSize:(id)json
{
- return [self UIFont:font withFamily:nil size:json weight:nil style:nil];
+ return [self UIFont:font withFamily:nil size:json weight:nil style:nil scaleMultiplier:1.0];
}
+ (UIFont *)UIFont:(UIFont *)font withWeight:(id)json
{
- return [self UIFont:font withFamily:nil size:nil weight:json style:nil];
+ return [self UIFont:font withFamily:nil size:nil weight:json style:nil scaleMultiplier:1.0];
}
+ (UIFont *)UIFont:(UIFont *)font withStyle:(id)json
{
- return [self UIFont:font withFamily:nil size:nil weight:nil style:json];
+ return [self UIFont:font withFamily:nil size:nil weight:nil style:json scaleMultiplier:1.0];
}
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)json
{
- return [self UIFont:font withFamily:json size:nil weight:nil style:nil];
+ return [self UIFont:font withFamily:json size:nil weight:nil style:nil scaleMultiplier:1.0];
}
+ (UIFont *)UIFont:(UIFont *)font withFamily:(id)family
size:(id)size weight:(id)weight style:(id)style
+ scaleMultiplier:(CGFloat)scaleMultiplier
{
// Defaults
NSString *const RCTDefaultFontFamily = @"System";
@@ -828,6 +830,9 @@ static BOOL RCTFontIsCondensed(UIFont *font)
// Get font attributes
fontSize = [self CGFloat:size] ?: fontSize;
+ if (scaleMultiplier > 0.0 && scaleMultiplier != 1.0) {
+ fontSize = round(fontSize * scaleMultiplier);
+ }
familyName = [self NSString:family] ?: familyName;
isItalic = style ? [self RCTFontStyle:style] : isItalic;
fontWeight = weight ? [self RCTFontWeight:weight] : fontWeight;
diff --git a/React/Modules/RCTAccessibilityManager.h b/React/Modules/RCTAccessibilityManager.h
new file mode 100644
index 000000000..03d22f945
--- /dev/null
+++ b/React/Modules/RCTAccessibilityManager.h
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import
+
+#import "RCTBridgeModule.h"
+#import "RCTBridge.h"
+
+extern NSString *const RCTAccessibilityManagerDidUpdateMultiplierNotification; // posted when multiplier is changed
+
+@interface RCTAccessibilityManager : NSObject
+
+@property (nonatomic, readonly) CGFloat multiplier;
+
+@end
+
+@interface RCTBridge (RCTAccessibilityManager)
+
+@property (nonatomic, readonly) RCTAccessibilityManager *accessibilityManager;
+
+@end
diff --git a/React/Modules/RCTAccessibilityManager.m b/React/Modules/RCTAccessibilityManager.m
new file mode 100644
index 000000000..9271b1e3d
--- /dev/null
+++ b/React/Modules/RCTAccessibilityManager.m
@@ -0,0 +1,144 @@
+/**
+ * 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 "RCTAccessibilityManager.h"
+
+#import "RCTLog.h"
+
+NSString *const RCTAccessibilityManagerDidUpdateMultiplierNotification = @"RCTAccessibilityManagerDidUpdateMultiplierNotification";
+
+@interface RCTAccessibilityManager ()
+
+@property (nonatomic, copy) NSDictionary *multipliers;
+@property (nonatomic, copy) NSString *contentSizeCategory;
+@property (nonatomic, assign) CGFloat multiplier;
+
+@end
+
+@implementation RCTAccessibilityManager
+
+@synthesize bridge = _bridge;
+
+RCT_EXPORT_MODULE()
+
++ (NSDictionary *)JSToUIKitMap
+{
+ static NSDictionary *map = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ map = @{@"extraSmall": UIContentSizeCategoryExtraSmall,
+ @"small": UIContentSizeCategorySmall,
+ @"medium": UIContentSizeCategoryMedium,
+ @"large": UIContentSizeCategoryLarge,
+ @"extraLarge": UIContentSizeCategoryExtraLarge,
+ @"extraExtraLarge": UIContentSizeCategoryExtraExtraLarge,
+ @"extraExtraExtraLarge": UIContentSizeCategoryExtraExtraExtraLarge,
+ @"accessibilityMedium": UIContentSizeCategoryAccessibilityMedium,
+ @"accessibilityLarge": UIContentSizeCategoryAccessibilityLarge,
+ @"accessibilityExtraLarge": UIContentSizeCategoryAccessibilityExtraLarge,
+ @"accessibilityExtraExtraLarge": UIContentSizeCategoryAccessibilityExtraExtraLarge,
+ @"accessibilityExtraExtraExtraLarge": UIContentSizeCategoryAccessibilityExtraExtraExtraLarge};
+ });
+ return map;
+}
+
++ (NSString *)UIKitCategoryFromJSCategory:(NSString *)JSCategory
+{
+ return self.JSToUIKitMap[JSCategory];
+}
+
+- (instancetype)init
+{
+ self = [super init];
+ if (self) {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(didReceiveNewContentSizeCategory:)
+ name:UIContentSizeCategoryDidChangeNotification
+ object:[UIApplication sharedApplication]];
+ self.contentSizeCategory = [[UIApplication sharedApplication] preferredContentSizeCategory];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)didReceiveNewContentSizeCategory:(NSNotification *)note
+{
+ self.contentSizeCategory = note.userInfo[UIContentSizeCategoryNewValueKey];
+}
+
+- (void)setContentSizeCategory:(NSString *)contentSizeCategory
+{
+ if (_contentSizeCategory != contentSizeCategory) {
+ _contentSizeCategory = [contentSizeCategory copy];
+ self.multiplier = [self multiplierForContentSizeCategory:_contentSizeCategory];
+ [[NSNotificationCenter defaultCenter] postNotificationName:RCTAccessibilityManagerDidUpdateMultiplierNotification object:self];
+ }
+}
+
+- (CGFloat)multiplierForContentSizeCategory:(NSString *)category
+{
+ NSNumber *m = self.multipliers[category];
+ if (m.doubleValue <= 0.0) {
+ RCTLogError(@"Can't determinte multiplier for category %@. Using 1.0.", category);
+ m = @1.0;
+ }
+ return m.doubleValue;
+}
+
+- (NSDictionary *)multipliers
+{
+ if (_multipliers == nil) {
+ _multipliers = @{UIContentSizeCategoryExtraSmall: @0.823,
+ UIContentSizeCategorySmall: @0.882,
+ UIContentSizeCategoryMedium: @0.941,
+ UIContentSizeCategoryLarge: @1.0,
+ UIContentSizeCategoryExtraLarge: @1.118,
+ UIContentSizeCategoryExtraExtraLarge: @1.235,
+ UIContentSizeCategoryExtraExtraExtraLarge: @1.353,
+ UIContentSizeCategoryAccessibilityMedium: @1.786,
+ UIContentSizeCategoryAccessibilityLarge: @2.143,
+ UIContentSizeCategoryAccessibilityExtraLarge: @2.643,
+ UIContentSizeCategoryAccessibilityExtraExtraLarge: @3.143,
+ UIContentSizeCategoryAccessibilityExtraExtraExtraLarge: @3.571};
+ }
+ return _multipliers;
+}
+
+RCT_EXPORT_METHOD(setAccessibilityContentSizeMultipliers:(NSDictionary *)JSMultipliers)
+{
+ NSMutableDictionary *multipliers = [[NSMutableDictionary alloc] init];
+ for (NSString *__nonnull JSCategory in JSMultipliers) {
+ NSNumber *m = JSMultipliers[JSCategory];
+ NSString *UIKitCategory = [self.class UIKitCategoryFromJSCategory:JSCategory];
+ multipliers[UIKitCategory] = m;
+ }
+ self.multipliers = multipliers;
+}
+
+RCT_EXPORT_METHOD(getMultiplier:(RCTResponseSenderBlock)callback)
+{
+ if (callback) {
+ callback(@[ @(self.multiplier) ]);
+ }
+}
+
+@end
+
+@implementation RCTBridge (RCTAccessibilityManager)
+
+- (RCTAccessibilityManager *)accessibilityManager
+{
+ return self.modules[RCTBridgeModuleNameForClass([RCTAccessibilityManager class])];
+}
+
+@end
diff --git a/React/Modules/RCTSourceCode.m b/React/Modules/RCTSourceCode.m
index 1b6eb842e..7498728f2 100644
--- a/React/Modules/RCTSourceCode.m
+++ b/React/Modules/RCTSourceCode.m
@@ -20,12 +20,12 @@ RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
RCT_EXPORT_METHOD(getScriptText:(RCTResponseSenderBlock)successCallback
- failureCallback:(RCTResponseSenderBlock)failureCallback)
+ failureCallback:(RCTResponseErrorBlock)failureCallback)
{
if (self.scriptText && self.scriptURL) {
successCallback(@[@{@"text": self.scriptText, @"url":[self.scriptURL absoluteString]}]);
} else {
- failureCallback(@[RCTMakeError(@"Source code is not available", nil, nil)]);
+ failureCallback(RCTErrorWithMessage(@"Source code is not available"));
}
}
diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h
index cbd7c167f..1efedc510 100644
--- a/React/Modules/RCTUIManager.h
+++ b/React/Modules/RCTUIManager.h
@@ -14,6 +14,12 @@
#import "RCTInvalidating.h"
#import "RCTViewManager.h"
+/**
+ * Posted right before re-render happens. This is a chance for views to invalidate their state so
+ * next render cycle will pick up updated views and layout appropriately.
+ */
+extern NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification;
+
@protocol RCTScrollableProtocol;
/**
diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m
index f526f5f8c..39cd38a21 100644
--- a/React/Modules/RCTUIManager.m
+++ b/React/Modules/RCTUIManager.m
@@ -14,6 +14,7 @@
#import
#import "Layout.h"
+#import "RCTAccessibilityManager.h"
#import "RCTAnimationType.h"
#import "RCTAssert.h"
#import "RCTBridge.h"
@@ -35,6 +36,8 @@
static void RCTTraverseViewNodes(id view, void (^block)(id));
static NSDictionary *RCTPropsMerge(NSDictionary *beforeProps, NSDictionary *newProps);
+NSString *const RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification = @"RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification";
+
@interface RCTAnimation : NSObject
@property (nonatomic, readonly) NSTimeInterval duration;
@@ -262,10 +265,33 @@ static NSDictionary *RCTViewConfigForModule(Class managerClass)
_rootViewTags = [[NSMutableSet alloc] init];
_bridgeTransactionListeners = [[NSMutableSet alloc] init];
+
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(didReceiveNewContentSizeMultiplier)
+ name:RCTAccessibilityManagerDidUpdateMultiplierNotification
+ object:nil];
}
return self;
}
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)didReceiveNewContentSizeMultiplier
+{
+ __weak RCTUIManager *weakSelf = self;
+ dispatch_async(self.methodQueue, ^{
+ __weak RCTUIManager *strongSelf = weakSelf;
+ if (strongSelf) {
+ [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerWillUpdateViewsDueToContentSizeMultiplierChangeNotification
+ object:strongSelf];
+ [strongSelf batchDidComplete];
+ }
+ });
+}
+
- (BOOL)isValid
{
return _viewRegistry != nil;
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index 5e0434b30..d0bbda232 100644
--- a/React/React.xcodeproj/project.pbxproj
+++ b/React/React.xcodeproj/project.pbxproj
@@ -75,6 +75,7 @@
83CBBA691A601EF300E9B192 /* RCTEventDispatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA661A601EF300E9B192 /* RCTEventDispatcher.m */; };
83CBBA981A6020BB00E9B192 /* RCTTouchHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBA971A6020BB00E9B192 /* RCTTouchHandler.m */; };
83CBBACC1A6023D300E9B192 /* RCTConvert.m in Sources */ = {isa = PBXBuildFile; fileRef = 83CBBACB1A6023D300E9B192 /* RCTConvert.m */; };
+ E9B20B7B1B500126007A2DA7 /* RCTAccessibilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = E9B20B7A1B500126007A2DA7 /* RCTAccessibilityManager.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -239,6 +240,8 @@
83CBBACA1A6023D300E9B192 /* RCTConvert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTConvert.h; sourceTree = ""; };
83CBBACB1A6023D300E9B192 /* RCTConvert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTConvert.m; sourceTree = ""; };
E3BBC8EB1ADE6F47001BBD81 /* RCTTextDecorationLineType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTTextDecorationLineType.h; sourceTree = ""; };
+ E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAccessibilityManager.h; sourceTree = ""; };
+ E9B20B7A1B500126007A2DA7 /* RCTAccessibilityManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAccessibilityManager.m; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -275,6 +278,8 @@
13B07FE01A69315300A75B9A /* Modules */ = {
isa = PBXGroup;
children = (
+ E9B20B791B500126007A2DA7 /* RCTAccessibilityManager.h */,
+ E9B20B7A1B500126007A2DA7 /* RCTAccessibilityManager.m */,
13B07FE71A69327A00A75B9A /* RCTAlertManager.h */,
13B07FE81A69327A00A75B9A /* RCTAlertManager.m */,
1372B7081AB030C200659ED6 /* RCTAppState.h */,
@@ -567,6 +572,7 @@
137327EA1AA5CF210034F82E /* RCTTabBarManager.m in Sources */,
13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */,
13B080051A6947C200A75B9A /* RCTScrollView.m in Sources */,
+ E9B20B7B1B500126007A2DA7 /* RCTAccessibilityManager.m in Sources */,
13B07FF21A69327A00A75B9A /* RCTTiming.m in Sources */,
1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */,
134FCB3D1A6E7F0800051CC8 /* RCTContextExecutor.m in Sources */,
diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m
index 2c350b18b..e5cccbd3f 100644
--- a/React/Views/RCTShadowView.m
+++ b/React/Views/RCTShadowView.m
@@ -439,12 +439,15 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
@"flex",
};
layoutKeys = [NSSet setWithObjects:layoutKeyStrings count:sizeof(layoutKeyStrings)/sizeof(*layoutKeyStrings)];
+ // layoutKeys are the only keys whose presence does not reject layout-only status.
static NSString *const specialKeyStrings[] = {
@"accessible",
@"collapsible",
};
specialKeys = [NSSet setWithObjects:specialKeyStrings count:sizeof(specialKeyStrings)/sizeof(*specialKeyStrings)];
+ // specialKeys are keys whose presence does not indicate whether layout-only or not
+ // their values must be tested below
}
NSNumber *collapsible = self.allProps[@"collapsible"];
diff --git a/React/Views/RCTTabBar.h b/React/Views/RCTTabBar.h
index 6f491ca08..5c24b9039 100644
--- a/React/Views/RCTTabBar.h
+++ b/React/Views/RCTTabBar.h
@@ -15,6 +15,7 @@
@property (nonatomic, strong) UIColor *tintColor;
@property (nonatomic, strong) UIColor *barTintColor;
+@property (nonatomic, assign) BOOL translucent;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
diff --git a/React/Views/RCTTabBar.m b/React/Views/RCTTabBar.m
index 06df7bad6..a4b55975c 100644
--- a/React/Views/RCTTabBar.m
+++ b/React/Views/RCTTabBar.m
@@ -140,6 +140,14 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:(NSCoder *)aDecoder)
_tabController.tabBar.tintColor = tintColor;
}
+- (BOOL)translucent {
+ return _tabController.tabBar.isTranslucent;
+}
+
+- (void)setTranslucent:(BOOL)translucent {
+ _tabController.tabBar.translucent = translucent;
+}
+
#pragma mark - UITabBarControllerDelegate
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
diff --git a/React/Views/RCTTabBarManager.m b/React/Views/RCTTabBarManager.m
index 2290c78c7..7b9616246 100644
--- a/React/Views/RCTTabBarManager.m
+++ b/React/Views/RCTTabBarManager.m
@@ -23,5 +23,6 @@ RCT_EXPORT_MODULE()
RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
RCT_EXPORT_VIEW_PROPERTY(barTintColor, UIColor)
+RCT_EXPORT_VIEW_PROPERTY(translucent, BOOL)
@end
diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h
index 51d060ca3..6e7019f58 100644
--- a/React/Views/RCTView.h
+++ b/React/Views/RCTView.h
@@ -64,7 +64,7 @@ typedef void (^RCTViewEventHandler)(RCTView *view);
@property (nonatomic, assign) CGFloat borderBottomRightRadius;
/**
- * Border colors.
+ * Border colors (actually retained).
*/
@property (nonatomic, assign) CGColorRef borderTopColor;
@property (nonatomic, assign) CGColorRef borderRightColor;
diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m
index 1acb1b2d6..407eba24f 100644
--- a/React/Views/RCTView.m
+++ b/React/Views/RCTView.m
@@ -480,7 +480,7 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:unused)
_borderTopColor ?: _borderColor,
_borderLeftColor ?: _borderColor,
_borderBottomColor ?: _borderColor,
- _borderRightColor ?: _borderColor
+ _borderRightColor ?: _borderColor,
};
}
@@ -580,14 +580,15 @@ RCT_NOT_IMPLEMENTED(-initWithCoder:unused)
#pragma mark Border Color
-#define setBorderColor(side) \
- - (void)setBorder##side##Color:(CGColorRef)border##side##Color \
- { \
- if (CGColorEqualToColor(_border##side##Color, border##side##Color)) { \
- return; \
- } \
- _border##side##Color = border##side##Color; \
- [self.layer setNeedsDisplay]; \
+#define setBorderColor(side) \
+ - (void)setBorder##side##Color:(CGColorRef)color \
+ { \
+ if (CGColorEqualToColor(_border##side##Color, color)) { \
+ return; \
+ } \
+ CGColorRelease(_border##side##Color); \
+ _border##side##Color = CGColorRetain(color); \
+ [self.layer setNeedsDisplay]; \
}
setBorderColor()
@@ -598,14 +599,14 @@ setBorderColor(Left)
#pragma mark - Border Width
-#define setBorderWidth(side) \
- - (void)setBorder##side##Width:(CGFloat)border##side##Width \
- { \
- if (_border##side##Width == border##side##Width) { \
- return; \
- } \
- _border##side##Width = border##side##Width; \
- [self.layer setNeedsDisplay]; \
+#define setBorderWidth(side) \
+ - (void)setBorder##side##Width:(CGFloat)width \
+ { \
+ if (_border##side##Width == width) { \
+ return; \
+ } \
+ _border##side##Width = width; \
+ [self.layer setNeedsDisplay]; \
}
setBorderWidth()
@@ -614,14 +615,14 @@ setBorderWidth(Right)
setBorderWidth(Bottom)
setBorderWidth(Left)
-#define setBorderRadius(side) \
- - (void)setBorder##side##Radius:(CGFloat)border##side##Radius \
- { \
- if (_border##side##Radius == border##side##Radius) { \
- return; \
- } \
- _border##side##Radius = border##side##Radius; \
- [self.layer setNeedsDisplay]; \
+#define setBorderRadius(side) \
+ - (void)setBorder##side##Radius:(CGFloat)radius \
+ { \
+ if (_border##side##Radius == radius) { \
+ return; \
+ } \
+ _border##side##Radius = radius; \
+ [self.layer setNeedsDisplay]; \
}
setBorderRadius()
@@ -630,4 +631,13 @@ setBorderRadius(TopRight)
setBorderRadius(BottomLeft)
setBorderRadius(BottomRight)
+- (void)dealloc
+{
+ CGColorRelease(_borderColor);
+ CGColorRelease(_borderTopColor);
+ CGColorRelease(_borderRightColor);
+ CGColorRelease(_borderBottomColor);
+ CGColorRelease(_borderLeftColor);
+}
+
@end
diff --git a/package.json b/package.json
index 2b8b33496..c70eda5e0 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,6 @@
"absolute-path": "0.0.0",
"babel": "5.4.3",
"babel-core": "^5.6.4",
- "bluebird": "^2.9.21",
"chalk": "^1.0.0",
"connect": "2.8.3",
"debug": "~2.1.0",