diff --git a/Libraries/Components/Navigation/NavigatorIOS.ios.js b/Libraries/Components/Navigation/NavigatorIOS.ios.js
index 6d86930b7..87f49232b 100644
--- a/Libraries/Components/Navigation/NavigatorIOS.ios.js
+++ b/Libraries/Components/Navigation/NavigatorIOS.ios.js
@@ -312,6 +312,12 @@ var NavigatorIOS = React.createClass({
this.navigationContext = new NavigationContext();
},
+ getDefaultProps: function(): Object {
+ return {
+ translucent: true,
+ };
+ },
+
getInitialState: function(): State {
return {
idStack: [getuid()],
@@ -591,37 +597,26 @@ var NavigatorIOS = React.createClass({
},
_routeToStackItem: function(route: Route, i: number) {
- var Component = route.component;
- var shouldUpdateChild = this.state.updatingAllIndicesAtOrBeyond != null &&
+ var {component, wrapperStyle, passProps, ...route} = route;
+ var {itemWrapperStyle, ...props} = this.props;
+ var shouldUpdateChild =
+ this.state.updatingAllIndicesAtOrBeyond &&
this.state.updatingAllIndicesAtOrBeyond >= i;
-
+ var Component = component;
return (
+ itemWrapperStyle,
+ wrapperStyle
+ ]}>
diff --git a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js
index 20e0cf624..f3fb6964c 100644
--- a/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js
+++ b/Libraries/Components/TabBarIOS/TabBarItemIOS.ios.js
@@ -16,7 +16,6 @@ var React = require('React');
var StaticContainer = require('StaticContainer.react');
var StyleSheet = require('StyleSheet');
var View = require('View');
-var resolveAssetSource = require('resolveAssetSource');
var requireNativeComponent = require('requireNativeComponent');
@@ -52,10 +51,7 @@ var TabBarItemIOS = React.createClass({
/**
* A custom icon for the tab. It is ignored when a system icon is defined.
*/
- icon: React.PropTypes.oneOfType([
- React.PropTypes.string,
- Image.propTypes.source,
- ]),
+ icon: Image.propTypes.source,
/**
* A custom icon when the tab is selected. It is ignored when a system
* icon is defined. If left empty, the icon will be tinted in blue.
@@ -101,29 +97,23 @@ var TabBarItemIOS = React.createClass({
},
render: function() {
- var tabContents = null;
+ var {style, children, ...props} = this.props;
+
// if the tab has already been shown once, always continue to show it so we
// preserve state between tab transitions
if (this.state.hasBeenSelected) {
- tabContents =
+ var tabContents =
- {this.props.children}
+ {children}
;
} else {
- tabContents = ;
+ var tabContents = ;
}
-
- var badge = typeof this.props.badge === 'number' ?
- '' + this.props.badge :
- this.props.badge;
-
+
return (
+ {...props}
+ style={[styles.tab, style]}>
{tabContents}
);
diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js
index 6943bf909..2b649bf9c 100644
--- a/Libraries/Image/Image.ios.js
+++ b/Libraries/Image/Image.ios.js
@@ -69,13 +69,16 @@ var Image = React.createClass({
PropTypes.number,
]),
/**
- * A static image to display while downloading the final image off the
- * network.
+ * A static image to display while loading the image source.
* @platform ios
*/
- defaultSource: PropTypes.shape({
- uri: PropTypes.string,
- }),
+ defaultSource: PropTypes.oneOfType([
+ PropTypes.shape({
+ uri: PropTypes.string,
+ }),
+ // Opaque type returned by require('./image.jpg')
+ PropTypes.number,
+ ]),
/**
* When true, indicates the image is an accessibility element.
* @platform ios
@@ -155,23 +158,10 @@ var Image = React.createClass({
},
render: function() {
- for (var prop in cfg.nativeOnly) {
- if (this.props[prop] !== undefined) {
- console.warn('Prop `' + prop + ' = ' + this.props[prop] + '` should ' +
- 'not be set directly on Image.');
- }
- }
var source = resolveAssetSource(this.props.source) || {};
- var defaultSource = (this.props.defaultSource && resolveAssetSource(this.props.defaultSource)) || {};
-
var {width, height} = source;
var style = flattenStyle([{width, height}, styles.base, this.props.style]) || {};
- if (source.uri === '') {
- console.warn('source.uri should not be an empty string');
- return ;
- }
-
var isNetwork = source.uri && source.uri.match(/^https?:/);
var RawImage = isNetwork ? RCTNetworkImageView : RCTImageView;
var resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108
@@ -192,8 +182,7 @@ var Image = React.createClass({
style={style}
resizeMode={resizeMode}
tintColor={tintColor}
- src={source.uri}
- defaultImageSrc={defaultSource.uri}
+ source={source}
/>
);
}
@@ -206,16 +195,8 @@ var styles = StyleSheet.create({
},
});
-var cfg = {
- nativeOnly: {
- src: true,
- defaultImageSrc: true,
- imageTag: true,
- progressHandlerRegistered: true,
- },
-};
-var RCTImageView = requireNativeComponent('RCTImageView', Image, cfg);
-var RCTNetworkImageView = NativeModules.NetworkImageViewManager ? requireNativeComponent('RCTNetworkImageView', Image, cfg) : RCTImageView;
+var RCTImageView = requireNativeComponent('RCTImageView', Image);
+var RCTNetworkImageView = NativeModules.NetworkImageViewManager ? requireNativeComponent('RCTNetworkImageView', Image) : RCTImageView;
var RCTVirtualImage = requireNativeComponent('RCTVirtualImage', Image);
module.exports = Image;
diff --git a/Libraries/Image/RCTImageUtils.m b/Libraries/Image/RCTImageUtils.m
index 3146a5b56..a6c2ccd46 100644
--- a/Libraries/Image/RCTImageUtils.m
+++ b/Libraries/Image/RCTImageUtils.m
@@ -231,6 +231,11 @@ UIImage *RCTDecodeImageWithData(NSData *data,
if (CGSizeEqualToSize(destSize, CGSizeZero)) {
destSize = sourceSize;
+ if (!destScale) {
+ destScale = 1;
+ }
+ } else if (!destScale) {
+ destScale = RCTScreenScale();
}
// calculate target size
@@ -253,13 +258,9 @@ UIImage *RCTDecodeImageWithData(NSData *data,
return nil;
}
- // adjust scale
- size_t actualWidth = CGImageGetWidth(imageRef);
- CGFloat scale = actualWidth / targetSize.width * destScale;
-
// return image
UIImage *image = [UIImage imageWithCGImage:imageRef
- scale:scale
+ scale:destScale
orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
return image;
diff --git a/Libraries/Image/RCTImageView.h b/Libraries/Image/RCTImageView.h
index a561c0f7f..8b5b993a7 100644
--- a/Libraries/Image/RCTImageView.h
+++ b/Libraries/Image/RCTImageView.h
@@ -11,6 +11,7 @@
#import "RCTImageComponent.h"
@class RCTBridge;
+@class RCTImageSource;
@interface RCTImageView : UIImageView
@@ -19,6 +20,6 @@
@property (nonatomic, assign) UIEdgeInsets capInsets;
@property (nonatomic, strong) UIImage *defaultImage;
@property (nonatomic, assign) UIImageRenderingMode renderingMode;
-@property (nonatomic, copy) NSString *src;
+@property (nonatomic, strong) RCTImageSource *source;
@end
diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m
index 246220840..656dd61e8 100644
--- a/Libraries/Image/RCTImageView.m
+++ b/Libraries/Image/RCTImageView.m
@@ -13,6 +13,7 @@
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTImageLoader.h"
+#import "RCTImageSource.h"
#import "RCTImageUtils.h"
#import "RCTUtils.h"
@@ -107,6 +108,12 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
}
}
+- (void)setTintColor:(UIColor *)tintColor
+{
+ super.tintColor = tintColor;
+ self.renderingMode = tintColor ? UIImageRenderingModeAlwaysTemplate : UIImageRenderingModeAlwaysOriginal;
+}
+
- (void)setRenderingMode:(UIImageRenderingMode)renderingMode
{
if (_renderingMode != renderingMode) {
@@ -115,28 +122,29 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
}
}
-- (void)setSrc:(NSString *)src
+- (void)setSource:(RCTImageSource *)source
{
- if (![src isEqual:_src]) {
- _src = [src copy];
+ if (![source isEqual:_source]) {
+ _source = source;
[self reloadImage];
}
}
-+ (BOOL)srcNeedsReload:(NSString *)src
+- (BOOL)sourceNeedsReload
{
+ NSString *scheme = _source.imageURL.scheme;
return
- [src hasPrefix:@"http://"] ||
- [src hasPrefix:@"https://"] ||
- [src hasPrefix:@"assets-library://"] ||
- [src hasPrefix:@"ph://"];
+ [scheme isEqualToString:@"http"] ||
+ [scheme isEqualToString:@"https"] ||
+ [scheme isEqualToString:@"assets-library"] ||
+ [scheme isEqualToString:@"ph"];
}
- (void)setContentMode:(UIViewContentMode)contentMode
{
if (self.contentMode != contentMode) {
super.contentMode = contentMode;
- if ([RCTImageView srcNeedsReload:_src]) {
+ if ([self sourceNeedsReload]) {
[self reloadImage];
}
}
@@ -162,7 +170,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
{
[self cancelImageLoad];
- if (_src && self.frame.size.width > 0 && self.frame.size.height > 0) {
+ if (_source && self.frame.size.width > 0 && self.frame.size.height > 0) {
if (_onLoadStart) {
_onLoadStart(nil);
}
@@ -177,31 +185,37 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
};
}
- _reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithTag:_src
+ RCTImageSource *source = _source;
+ __weak RCTImageView *weakSelf = self;
+ _reloadImageCancellationBlock = [_bridge.imageLoader loadImageWithTag:_source.imageURL.absoluteString
size:self.bounds.size
scale:RCTScreenScale()
resizeMode:self.contentMode
progressBlock:progressHandler
completionBlock:^(NSError *error, UIImage *image) {
- if (error) {
- if (_onError) {
- _onError(@{ @"error": error.localizedDescription });
- }
- } else {
- if (_onLoad) {
- _onLoad(nil);
- }
- }
- if (_onLoadEnd) {
- _onLoadEnd(nil);
- }
-
dispatch_async(dispatch_get_main_queue(), ^{
+ RCTImageView *strongSelf = weakSelf;
+ if (![source isEqual:strongSelf.source]) {
+ // Bail out if source has changed since we started loading
+ return;
+ }
if (image.reactKeyframeAnimation) {
- [self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
+ [strongSelf.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
} else {
- [self.layer removeAnimationForKey:@"contents"];
- self.image = image;
+ [strongSelf.layer removeAnimationForKey:@"contents"];
+ strongSelf.image = image;
+ }
+ if (error) {
+ if (strongSelf->_onError) {
+ strongSelf->_onError(@{ @"error": error.localizedDescription });
+ }
+ } else {
+ if (strongSelf->_onLoad) {
+ strongSelf->_onLoad(nil);
+ }
+ }
+ if (strongSelf->_onLoadEnd) {
+ strongSelf->_onLoadEnd(nil);
}
});
}];
@@ -217,13 +231,13 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
if (!self.image || self.image == _defaultImage) {
_targetSize = frame.size;
[self reloadImage];
- } else if ([RCTImageView srcNeedsReload:_src]) {
+ } else if ([self sourceNeedsReload]) {
CGSize imageSize = self.image.size;
CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size, RCTScreenScale(), self.contentMode, YES);
if (RCTShouldReloadImageForSizeChange(imageSize, idealSize)) {
if (RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) {
- RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _src, NSStringFromCGSize(idealSize));
+ RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _source.imageURL, NSStringFromCGSize(idealSize));
// If the existing image or an image being loaded are not the right size, reload the asset in case there is a
// better size available.
diff --git a/Libraries/Image/RCTImageViewManager.m b/Libraries/Image/RCTImageViewManager.m
index bcf2f0bdb..c20678895 100644
--- a/Libraries/Image/RCTImageViewManager.m
+++ b/Libraries/Image/RCTImageViewManager.m
@@ -12,6 +12,7 @@
#import
#import "RCTConvert.h"
+#import "RCTImageSource.h"
#import "RCTImageView.h"
@implementation RCTImageViewManager
@@ -24,23 +25,14 @@ RCT_EXPORT_MODULE()
}
RCT_EXPORT_VIEW_PROPERTY(capInsets, UIEdgeInsets)
-RCT_REMAP_VIEW_PROPERTY(defaultImageSrc, defaultImage, UIImage)
-RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
-RCT_EXPORT_VIEW_PROPERTY(src, NSString)
+RCT_REMAP_VIEW_PROPERTY(defaultSource, defaultImage, UIImage)
RCT_EXPORT_VIEW_PROPERTY(onLoadStart, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onLoadEnd, RCTDirectEventBlock)
-RCT_CUSTOM_VIEW_PROPERTY(tintColor, UIColor, RCTImageView)
-{
- if (json) {
- view.renderingMode = UIImageRenderingModeAlwaysTemplate;
- view.tintColor = [RCTConvert UIColor:json];
- } else {
- view.renderingMode = defaultView.renderingMode;
- view.tintColor = defaultView.tintColor;
- }
-}
+RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode, UIViewContentMode)
+RCT_EXPORT_VIEW_PROPERTY(source, RCTImageSource)
+RCT_EXPORT_VIEW_PROPERTY(tintColor, UIColor)
@end
diff --git a/Libraries/Image/RCTShadowVirtualImage.h b/Libraries/Image/RCTShadowVirtualImage.h
index 841c215e2..bd8bb51ae 100644
--- a/Libraries/Image/RCTShadowVirtualImage.h
+++ b/Libraries/Image/RCTShadowVirtualImage.h
@@ -9,6 +9,7 @@
#import "RCTShadowView.h"
#import "RCTImageComponent.h"
+#import "RCTImageSource.h"
@class RCTBridge;
@@ -20,6 +21,6 @@
- (instancetype)initWithBridge:(RCTBridge *)bridge;
-@property (nonatomic, copy) NSDictionary *source;
+@property (nonatomic, strong) RCTImageSource *source;
@end
diff --git a/Libraries/Image/RCTShadowVirtualImage.m b/Libraries/Image/RCTShadowVirtualImage.m
index 431fbac07..f3486f9a2 100644
--- a/Libraries/Image/RCTShadowVirtualImage.m
+++ b/Libraries/Image/RCTShadowVirtualImage.m
@@ -16,6 +16,7 @@
@implementation RCTShadowVirtualImage
{
RCTBridge *_bridge;
+ RCTImageLoaderCancellationBlock _cancellationBlock;
}
@synthesize image = _image;
@@ -30,23 +31,31 @@
RCT_NOT_IMPLEMENTED(-(instancetype)init)
-- (void)setSource:(NSDictionary *)source
+- (void)setSource:(RCTImageSource *)source
{
if (![source isEqual:_source]) {
- _source = [source copy];
- NSString *imageTag = [RCTConvert NSString:_source[@"uri"]];
- CGFloat scale = [RCTConvert CGFloat:_source[@"scale"]] ?: 1;
+
+ // Cancel previous request
+ if (_cancellationBlock) {
+ _cancellationBlock();
+ }
+
+ _source = source;
__weak RCTShadowVirtualImage *weakSelf = self;
- [_bridge.imageLoader loadImageWithTag:imageTag
- size:CGSizeZero
- scale:scale
- resizeMode:UIViewContentModeScaleToFill
- progressBlock:nil
- completionBlock:^(NSError *error, UIImage *image) {
+ _cancellationBlock = [_bridge.imageLoader loadImageWithTag:source.imageURL.absoluteString
+ size:source.size
+ scale:source.scale
+ resizeMode:UIViewContentModeScaleToFill
+ progressBlock:nil
+ completionBlock:^(NSError *error, UIImage *image) {
dispatch_async(_bridge.uiManager.methodQueue, ^{
RCTShadowVirtualImage *strongSelf = weakSelf;
+ if (![source isEqual:strongSelf.source]) {
+ // Bail out if source has changed since we started loading
+ return;
+ }
strongSelf->_image = image;
[strongSelf dirtyText];
});
@@ -54,4 +63,11 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
}
}
+- (void)dealloc
+{
+ if (_cancellationBlock) {
+ _cancellationBlock();
+ }
+}
+
@end
diff --git a/Libraries/Image/RCTVirtualImageManager.m b/Libraries/Image/RCTVirtualImageManager.m
index 2800a92b0..d497026ab 100644
--- a/Libraries/Image/RCTVirtualImageManager.m
+++ b/Libraries/Image/RCTVirtualImageManager.m
@@ -19,6 +19,6 @@ RCT_EXPORT_MODULE()
return [[RCTShadowVirtualImage alloc] initWithBridge:self.bridge];
}
-RCT_EXPORT_SHADOW_PROPERTY(source, NSDictionary)
+RCT_EXPORT_SHADOW_PROPERTY(source, RCTImageSource)
@end
diff --git a/Libraries/ReactIOS/requireNativeComponent.js b/Libraries/ReactIOS/requireNativeComponent.js
index a046fcbd1..84789e6cf 100644
--- a/Libraries/ReactIOS/requireNativeComponent.js
+++ b/Libraries/ReactIOS/requireNativeComponent.js
@@ -16,10 +16,12 @@ var UIManager = require('UIManager');
var UnimplementedView = require('UnimplementedView');
var createReactNativeComponentClass = require('createReactNativeComponentClass');
+
var insetsDiffer = require('insetsDiffer');
var pointsDiffer = require('pointsDiffer');
var matricesDiffer = require('matricesDiffer');
var processColor = require('processColor');
+var resolveAssetSource = require('resolveAssetSource');
var sizesDiffer = require('sizesDiffer');
var verifyPropTypes = require('verifyPropTypes');
var warning = require('warning');
@@ -110,6 +112,9 @@ var TypeToProcessorMap = {
CGColorArray: processColor,
UIColor: processColor,
UIColorArray: processColor,
+ CGImage: resolveAssetSource,
+ UIImage: resolveAssetSource,
+ RCTImageSource: resolveAssetSource,
// Android Types
Color: processColor,
};
diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h
index 3d1c4291d..6347cc9ca 100644
--- a/React/Base/RCTConvert.h
+++ b/React/Base/RCTConvert.h
@@ -85,9 +85,6 @@ typedef NSURL RCTFileURL;
+ (UIColor *)UIColor:(id)json;
+ (CGColorRef)CGColor:(id)json CF_RETURNS_NOT_RETAINED;
-+ (UIImage *)UIImage:(id)json;
-+ (CGImageRef)CGImage:(id)json CF_RETURNS_NOT_RETAINED;
-
+ (UIFont *)UIFont:(id)json;
+ (UIFont *)UIFont:(UIFont *)font withSize:(id)json;
+ (UIFont *)UIFont:(UIFont *)font withWeight:(id)json;
@@ -146,6 +143,18 @@ typedef BOOL css_clip_t, css_backface_visibility_t;
@end
+@interface RCTConvert (Deprecated)
+
+/**
+ * Synchronous image loading is generally a bad idea for performance reasons.
+ * If you need to pass image references, try to use `RCTImageSource` and then
+ * `RCTImageLoader` instead of converting directly to a UIImage.
+ */
++ (UIImage *)UIImage:(id)json;
++ (CGImageRef)CGImage:(id)json CF_RETURNS_NOT_RETAINED;
+
+@end
+
/**
* Underlying implementations of RCT_XXX_CONVERTER macros. Ignore these.
*/
diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m
index b2c033b38..244fe6c0a 100644
--- a/React/Base/RCTConvert.m
+++ b/React/Base/RCTConvert.m
@@ -12,6 +12,7 @@
#import
#import "RCTDefines.h"
+#import "RCTImageSource.h"
#import "RCTUtils.h"
@implementation RCTConvert
@@ -421,7 +422,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
CGFloat b = (argb & 0xFF) / 255.0;
return [UIColor colorWithRed:r green:g blue:b alpha:a];
} else {
- RCTLogConvertError(json, @"a color");
+ RCTLogConvertError(json, @"a UIColor. Did you forget to call processColor() on the JS side?");
return nil;
}
}
@@ -431,78 +432,6 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
return [self UIColor:json].CGColor;
}
-/* This method is only used when loading images synchronously, e.g. for tabbar icons */
-+ (UIImage *)UIImage:(id)json
-{
- // TODO: we might as well cache the result of these checks (and possibly the
- // image itself) so as to reduce overhead on subsequent checks of the same input
-
- if (!json) {
- return nil;
- }
-
- __block UIImage *image;
- if (![NSThread isMainThread]) {
- // It seems that none of the UIImage loading methods can be guaranteed
- // thread safe, so we'll pick the lesser of two evils here and block rather
- // than run the risk of crashing
- RCTLogWarn(@"Calling [RCTConvert UIImage:] on a background thread is not recommended");
- dispatch_sync(dispatch_get_main_queue(), ^{
- image = [self UIImage:json];
- });
- return image;
- }
-
- NSString *path;
- CGFloat scale = 0.0;
- BOOL isPackagerAsset = NO;
- if ([json isKindOfClass:[NSString class]]) {
- path = json;
- } else if ([json isKindOfClass:[NSDictionary class]]) {
- if (!(path = [self NSString:json[@"uri"]])) {
- return nil;
- }
- scale = [self CGFloat:json[@"scale"]];
- isPackagerAsset = [self BOOL:json[@"__packager_asset"]];
- } else {
- RCTLogConvertError(json, @"an image");
- return nil;
- }
-
- NSURL *URL = [self NSURL:path];
- NSString *scheme = URL.scheme.lowercaseString;
- if ([scheme isEqualToString:@"file"]) {
- NSString *assetName = RCTBundlePathForURL(URL);
- image = [UIImage imageNamed:assetName];
- if (!image) {
- // Attempt to load from the file system
- NSString *filePath = URL.path;
- if (filePath.pathExtension.length == 0) {
- filePath = [filePath stringByAppendingPathExtension:@"png"];
- }
- image = [UIImage imageWithContentsOfFile:filePath];
- }
- } else if ([scheme isEqualToString:@"data"]) {
- image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
- } else if ([scheme isEqualToString:@"http"] && isPackagerAsset) {
- image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
- } else {
- RCTLogConvertError(json, @"an image. Only local files or data URIs are supported");
- }
-
- if (scale > 0) {
- image = [UIImage imageWithCGImage:image.CGImage
- scale:scale
- orientation:image.imageOrientation];
- }
- return image;
-}
-
-+ (CGImageRef)CGImage:(id)json
-{
- return [self UIImage:json].CGImage;
-}
-
#if !defined(__IPHONE_8_2) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_2
// These constants are defined in iPhone SDK 8.2, but the app cannot run on
@@ -860,3 +789,84 @@ RCT_ENUM_CONVERTER(RCTAnimationType, (@{
}), RCTAnimationTypeEaseInEaseOut, integerValue)
@end
+
+@interface RCTImageSource (Packager)
+
+@property (nonatomic, assign) BOOL packagerAsset;
+
+@end
+
+@implementation RCTConvert (Deprecated)
+
+/* This method is only used when loading images synchronously, e.g. for tabbar icons */
++ (UIImage *)UIImage:(id)json
+{
+ if (!json) {
+ return nil;
+ }
+
+ RCTImageSource *imageSource = [self RCTImageSource:json];
+ if (!imageSource) {
+ return nil;
+ }
+
+ __block UIImage *image;
+ if (![NSThread isMainThread]) {
+ // It seems that none of the UIImage loading methods can be guaranteed
+ // thread safe, so we'll pick the lesser of two evils here and block rather
+ // than run the risk of crashing
+ RCTLogWarn(@"Calling [RCTConvert UIImage:] on a background thread is not recommended");
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ image = [self UIImage:json];
+ });
+ return image;
+ }
+
+ NSURL *URL = imageSource.imageURL;
+ NSString *scheme = URL.scheme.lowercaseString;
+ if ([scheme isEqualToString:@"file"]) {
+ NSString *assetName = RCTBundlePathForURL(URL);
+ image = [UIImage imageNamed:assetName];
+ if (!image) {
+ // Attempt to load from the file system
+ NSString *filePath = URL.path;
+ if (filePath.pathExtension.length == 0) {
+ filePath = [filePath stringByAppendingPathExtension:@"png"];
+ }
+ image = [UIImage imageWithContentsOfFile:filePath];
+ }
+ } else if ([scheme isEqualToString:@"data"]) {
+ image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
+ } else if ([scheme isEqualToString:@"http"] && imageSource.packagerAsset) {
+ image = [UIImage imageWithData:[NSData dataWithContentsOfURL:URL]];
+ } else {
+ RCTLogConvertError(json, @"an image. Only local files or data URIs are supported");
+ }
+
+ CGFloat scale = imageSource.scale;
+ if (!scale && imageSource.size.width) {
+ // If no scale provided, set scale to image width / source width
+ scale = CGImageGetWidth(image.CGImage) / imageSource.size.width;
+ }
+
+ if (scale) {
+ image = [UIImage imageWithCGImage:image.CGImage
+ scale:scale
+ orientation:image.imageOrientation];
+ }
+
+ if (!CGSizeEqualToSize(imageSource.size, CGSizeZero) &&
+ !CGSizeEqualToSize(imageSource.size, image.size)) {
+ RCTLogError(@"Image source size %@ does not match loaded image size %@.",
+ NSStringFromCGSize(imageSource.size), NSStringFromCGSize(image.size));
+ }
+
+ return image;
+}
+
++ (CGImageRef)CGImage:(id)json
+{
+ return [self UIImage:json].CGImage;
+}
+
+@end
diff --git a/React/Base/RCTImageSource.h b/React/Base/RCTImageSource.h
new file mode 100644
index 000000000..efcefaa2b
--- /dev/null
+++ b/React/Base/RCTImageSource.h
@@ -0,0 +1,43 @@
+/**
+ * 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 "RCTConvert.h"
+
+/**
+ * Object containing an image URL and associated metadata.
+ */
+@interface RCTImageSource : NSObject
+
+@property (nonatomic, strong, readonly) NSURL *imageURL;
+@property (nonatomic, assign, readonly) CGSize size;
+@property (nonatomic, assign, readonly) CGFloat scale;
+
+/**
+ * Create a new image source object.
+ * Pass a size of CGSizeZero if you do not know or wish to specify the image
+ * size. Pass a scale of zero if you do not know or wish to specify the scale.
+ */
+- (instancetype)initWithURL:(NSURL *)url
+ size:(CGSize)size
+ scale:(CGFloat)scale;
+
+/**
+ * Create a copy of the image source with the specified size and scale.
+ */
+- (instancetype)imageSourceWithSize:(CGSize)size scale:(CGFloat)scale;
+
+@end
+
+@interface RCTConvert (ImageSource)
+
++ (RCTImageSource *)RCTImageSource:(id)json;
+
+@end
diff --git a/React/Base/RCTImageSource.m b/React/Base/RCTImageSource.m
new file mode 100644
index 000000000..3392385f8
--- /dev/null
+++ b/React/Base/RCTImageSource.m
@@ -0,0 +1,83 @@
+/**
+ * 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 "RCTImageSource.h"
+
+@interface RCTImageSource ()
+
+@property (nonatomic, assign) BOOL packagerAsset;
+
+@end
+
+@implementation RCTImageSource
+
+- (instancetype)initWithURL:(NSURL *)url size:(CGSize)size scale:(CGFloat)scale
+{
+ if ((self = [super init])) {
+ _imageURL = url;
+ _size = size;
+ _scale = scale;
+ }
+ return self;
+}
+
+- (instancetype)imageSourceWithSize:(CGSize)size scale:(CGFloat)scale
+{
+ RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:_imageURL
+ size:size
+ scale:scale];
+ imageSource.packagerAsset = _packagerAsset;
+ return imageSource;
+}
+
+- (BOOL)isEqual:(RCTImageSource *)object
+{
+ if (![object isKindOfClass:[RCTImageSource class]]) {
+ return NO;
+ }
+ return [_imageURL isEqual:object.imageURL] && _scale == object.scale &&
+ (CGSizeEqualToSize(_size, object.size) || CGSizeEqualToSize(object.size, CGSizeZero));
+}
+
+@end
+
+@implementation RCTConvert (ImageSource)
+
++ (RCTImageSource *)RCTImageSource:(id)json
+{
+ if (!json) {
+ return nil;
+ }
+
+ NSURL *imageURL;
+ CGSize size = CGSizeZero;
+ CGFloat scale = 1.0;
+ BOOL packagerAsset = NO;
+ if ([json isKindOfClass:[NSDictionary class]]) {
+ if (!(imageURL = [self NSURL:json[@"uri"]])) {
+ return nil;
+ }
+ size = [self CGSize:json];
+ scale = [self CGFloat:json[@"scale"]] ?: [self BOOL:json[@"deprecated"]] ? 0.0 : 1.0;
+ packagerAsset = [self BOOL:json[@"__packager_asset"]];
+ } else if ([json isKindOfClass:[NSString class]]) {
+ imageURL = [self NSURL:json];
+ } else {
+ RCTLogConvertError(json, @"an image. Did you forget to call resolveAssetSource() on the JS side?");
+ return nil;
+ }
+
+ RCTImageSource *imageSource = [[RCTImageSource alloc] initWithURL:imageURL
+ size:size
+ scale:scale];
+ imageSource.packagerAsset = packagerAsset;
+ return imageSource;
+}
+
+@end
diff --git a/React/Base/RCTLog.m b/React/Base/RCTLog.m
index 86392db1f..f09aff78b 100644
--- a/React/Base/RCTLog.m
+++ b/React/Base/RCTLog.m
@@ -206,6 +206,7 @@ void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumb
}
#if RCT_DEBUG
+
// Log to red box in debug mode.
if ([UIApplication sharedApplication] && level >= RCTLOG_REDBOX_LEVEL) {
NSArray *stackSymbols = [NSThread callStackSymbols];
@@ -233,7 +234,9 @@ void _RCTLogNativeInternal(RCTLogLevel level, const char *fileName, int lineNumb
// Log to JS executor
[[RCTBridge currentBridge] logMessage:message level:level ? @(RCTLogLevels[level]) : @"info"];
+
#endif
+
}
}
diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj
index 6d671d58b..53a11ed1d 100644
--- a/React/React.xcodeproj/project.pbxproj
+++ b/React/React.xcodeproj/project.pbxproj
@@ -38,6 +38,7 @@
13B0801D1A69489C00A75B9A /* RCTNavItemManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080131A69489C00A75B9A /* RCTNavItemManager.m */; };
13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */; };
13B080261A694A8400A75B9A /* RCTWrapperViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B080241A694A8400A75B9A /* RCTWrapperViewController.m */; };
+ 13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BB3D011BECD54500932C10 /* RCTImageSource.m */; };
13B202011BFB945300C07393 /* RCTPasteboard.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202001BFB945300C07393 /* RCTPasteboard.m */; };
13B202041BFB948C00C07393 /* RCTMapAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B202031BFB948C00C07393 /* RCTMapAnnotation.m */; };
13C156051AB1A2840079392D /* RCTWebView.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C156021AB1A2840079392D /* RCTWebView.m */; };
@@ -173,7 +174,9 @@
13B080191A69489C00A75B9A /* RCTActivityIndicatorViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActivityIndicatorViewManager.m; sourceTree = ""; };
13B080231A694A8400A75B9A /* RCTWrapperViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWrapperViewController.h; sourceTree = ""; };
13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = ""; };
- 13B201FF1BFB945300C07393 /* RCTPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPasteboard.h; sourceTree = ""; };
+ 13BB3D001BECD54500932C10 /* RCTImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageSource.h; sourceTree = ""; };
+ 13BB3D011BECD54500932C10 /* RCTImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageSource.m; sourceTree = ""; };
+ 13B201FF1BFB945300C07393 /* RCTPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPasteboard.h; sourceTree = ""; };
13B202001BFB945300C07393 /* RCTPasteboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPasteboard.m; sourceTree = ""; };
13B202021BFB948C00C07393 /* RCTMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapAnnotation.h; sourceTree = ""; };
13B202031BFB948C00C07393 /* RCTMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapAnnotation.m; sourceTree = ""; };
@@ -526,6 +529,8 @@
1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */,
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
+ 13BB3D001BECD54500932C10 /* RCTImageSource.h */,
+ 13BB3D011BECD54500932C10 /* RCTImageSource.m */,
);
path = Base;
sourceTree = "";
@@ -645,6 +650,7 @@
14F3620E1AABD06A001CE568 /* RCTSwitchManager.m in Sources */,
13B080201A69489C00A75B9A /* RCTActivityIndicatorViewManager.m in Sources */,
13E067561A70F44B002CDEE1 /* RCTViewManager.m in Sources */,
+ 13BB3D021BECD54500932C10 /* RCTImageSource.m in Sources */,
58C571C11AA56C1900CDF9C8 /* RCTDatePickerManager.m in Sources */,
1450FF8A1BCFF28A00208362 /* RCTProfileTrampoline-x86_64.S in Sources */,
14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */,
diff --git a/React/Views/RCTNavItem.h b/React/Views/RCTNavItem.h
index 418d5c5c9..fdfeb66e4 100644
--- a/React/Views/RCTNavItem.h
+++ b/React/Views/RCTNavItem.h
@@ -31,7 +31,7 @@
@property (nonatomic, readonly) UIBarButtonItem *leftButtonItem;
@property (nonatomic, readonly) UIBarButtonItem *rightButtonItem;
-@property (nonatomic, copy) RCTBubblingEventBlock onNavLeftButtonTap;
-@property (nonatomic, copy) RCTBubblingEventBlock onNavRightButtonTap;
+@property (nonatomic, copy) RCTBubblingEventBlock onLeftButtonPress;
+@property (nonatomic, copy) RCTBubblingEventBlock onRightButtonPress;
@end
diff --git a/React/Views/RCTNavItem.m b/React/Views/RCTNavItem.m
index 5e4043f84..5fa8005c9 100644
--- a/React/Views/RCTNavItem.m
+++ b/React/Views/RCTNavItem.m
@@ -67,14 +67,14 @@
[[UIBarButtonItem alloc] initWithImage:_leftButtonIcon
style:UIBarButtonItemStylePlain
target:self
- action:@selector(handleNavLeftButtonTapped)];
+ action:@selector(handleLeftButtonPress)];
} else if (_leftButtonTitle.length) {
_leftButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_leftButtonTitle
style:UIBarButtonItemStylePlain
target:self
- action:@selector(handleNavLeftButtonTapped)];
+ action:@selector(handleLeftButtonPress)];
} else {
_leftButtonItem = nil;
}
@@ -82,10 +82,10 @@
return _leftButtonItem;
}
-- (void)handleNavLeftButtonTapped
+- (void)handleLeftButtonPress
{
- if (_onNavLeftButtonTap) {
- _onNavLeftButtonTap(nil);
+ if (_onLeftButtonPress) {
+ _onLeftButtonPress(nil);
}
}
@@ -109,14 +109,14 @@
[[UIBarButtonItem alloc] initWithImage:_rightButtonIcon
style:UIBarButtonItemStylePlain
target:self
- action:@selector(handleNavRightButtonTapped)];
+ action:@selector(handleRightButtonPress)];
} else if (_rightButtonTitle.length) {
_rightButtonItem =
[[UIBarButtonItem alloc] initWithTitle:_rightButtonTitle
style:UIBarButtonItemStylePlain
target:self
- action:@selector(handleNavRightButtonTapped)];
+ action:@selector(handleRightButtonPress)];
} else {
_rightButtonItem = nil;
}
@@ -124,10 +124,10 @@
return _rightButtonItem;
}
-- (void)handleNavRightButtonTapped
+- (void)handleRightButtonPress
{
- if (_onNavRightButtonTap) {
- _onNavRightButtonTap(nil);
+ if (_onRightButtonPress) {
+ _onRightButtonPress(nil);
}
}
diff --git a/React/Views/RCTNavItemManager.m b/React/Views/RCTNavItemManager.m
index 040b7ffce..be4f879dd 100644
--- a/React/Views/RCTNavItemManager.m
+++ b/React/Views/RCTNavItemManager.m
@@ -39,7 +39,7 @@ RCT_EXPORT_VIEW_PROPERTY(leftButtonIcon, UIImage)
RCT_EXPORT_VIEW_PROPERTY(rightButtonIcon, UIImage)
RCT_EXPORT_VIEW_PROPERTY(rightButtonTitle, NSString)
-RCT_EXPORT_VIEW_PROPERTY(onNavLeftButtonTap, RCTBubblingEventBlock)
-RCT_EXPORT_VIEW_PROPERTY(onNavRightButtonTap, RCTBubblingEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onLeftButtonPress, RCTBubblingEventBlock)
+RCT_EXPORT_VIEW_PROPERTY(onRightButtonPress, RCTBubblingEventBlock)
@end
diff --git a/React/Views/RCTTabBarItem.h b/React/Views/RCTTabBarItem.h
index def1abf6c..cee2df982 100644
--- a/React/Views/RCTTabBarItem.h
+++ b/React/Views/RCTTabBarItem.h
@@ -13,7 +13,9 @@
@interface RCTTabBarItem : UIView
-@property (nonatomic, copy) id icon;
+@property (nonatomic, copy) id /* NSString or NSNumber */ badge;
+@property (nonatomic, strong) UIImage *icon;
+@property (nonatomic, assign) UITabBarSystemItem systemIcon;
@property (nonatomic, assign, getter=isSelected) BOOL selected;
@property (nonatomic, readonly) UITabBarItem *barItem;
@property (nonatomic, copy) RCTBubblingEventBlock onPress;
diff --git a/React/Views/RCTTabBarItem.m b/React/Views/RCTTabBarItem.m
index 8699ff96d..a05ae75ee 100644
--- a/React/Views/RCTTabBarItem.m
+++ b/React/Views/RCTTabBarItem.m
@@ -13,73 +13,78 @@
#import "RCTLog.h"
#import "UIView+React.h"
+@implementation RCTConvert (UITabBarSystemItem)
+
+RCT_ENUM_CONVERTER(UITabBarSystemItem, (@{
+ @"bookmarks": @(UITabBarSystemItemBookmarks),
+ @"contacts": @(UITabBarSystemItemContacts),
+ @"downloads": @(UITabBarSystemItemDownloads),
+ @"favorites": @(UITabBarSystemItemFavorites),
+ @"featured": @(UITabBarSystemItemFeatured),
+ @"history": @(UITabBarSystemItemHistory),
+ @"more": @(UITabBarSystemItemMore),
+ @"most-recent": @(UITabBarSystemItemMostRecent),
+ @"most-viewed": @(UITabBarSystemItemMostViewed),
+ @"recents": @(UITabBarSystemItemRecents),
+ @"search": @(UITabBarSystemItemSearch),
+ @"top-rated": @(UITabBarSystemItemTopRated),
+}), NSNotFound, integerValue)
+
+@end
+
@implementation RCTTabBarItem
@synthesize barItem = _barItem;
+- (instancetype)initWithFrame:(CGRect)frame
+{
+ if ((self = [super initWithFrame:frame])) {
+ _systemIcon = NSNotFound;
+ }
+ return self;
+}
+
- (UITabBarItem *)barItem
{
if (!_barItem) {
_barItem = [UITabBarItem new];
+ _systemIcon = NSNotFound;
}
return _barItem;
}
-- (void)setIcon:(id)icon
+- (void)setBadge:(id)badge
{
- static NSDictionary *systemIcons;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- systemIcons = @{
- @"bookmarks": @(UITabBarSystemItemBookmarks),
- @"contacts": @(UITabBarSystemItemContacts),
- @"downloads": @(UITabBarSystemItemDownloads),
- @"favorites": @(UITabBarSystemItemFavorites),
- @"featured": @(UITabBarSystemItemFeatured),
- @"history": @(UITabBarSystemItemHistory),
- @"more": @(UITabBarSystemItemMore),
- @"most-recent": @(UITabBarSystemItemMostRecent),
- @"most-viewed": @(UITabBarSystemItemMostViewed),
- @"recents": @(UITabBarSystemItemRecents),
- @"search": @(UITabBarSystemItemSearch),
- @"top-rated": @(UITabBarSystemItemTopRated),
- };
- });
+ _badge = [badge copy];
+ self.barItem.badgeValue = [badge description];
+}
- // Update icon
- BOOL wasSystemIcon = (systemIcons[_icon] != nil);
- _icon = [icon copy];
-
- // Check if string matches any custom images first
- UIImage *image = [RCTConvert UIImage:_icon];
- UITabBarItem *oldItem = _barItem;
- if (image) {
- // Recreate barItem if previous item was a system icon. Calling self.barItem
- // creates a new instance if it wasn't set yet.
- if (wasSystemIcon) {
- _barItem = nil;
- self.barItem.image = image;
- } else {
- self.barItem.image = image;
- return;
- }
- } else if ([icon isKindOfClass:[NSString class]] && [icon length] > 0) {
- // Not a custom image, may be a system item?
- NSNumber *systemIcon = systemIcons[icon];
- if (!systemIcon) {
- RCTLogError(@"The tab bar icon '%@' did not match any known image or system icon", icon);
- return;
- }
- _barItem = [[UITabBarItem alloc] initWithTabBarSystemItem:systemIcon.integerValue tag:oldItem.tag];
- } else {
- self.barItem.image = nil;
+- (void)setSystemIcon:(UITabBarSystemItem)systemIcon
+{
+ if (_systemIcon != systemIcon) {
+ _systemIcon = systemIcon;
+ UITabBarItem *oldItem = _barItem;
+ _barItem = [[UITabBarItem alloc] initWithTabBarSystemItem:_systemIcon
+ tag:oldItem.tag];
+ _barItem.title = oldItem.title;
+ _barItem.imageInsets = oldItem.imageInsets;
+ _barItem.badgeValue = oldItem.badgeValue;
}
+}
- // Reapply previous properties
- _barItem.title = oldItem.title;
- _barItem.imageInsets = oldItem.imageInsets;
- _barItem.selectedImage = oldItem.selectedImage;
- _barItem.badgeValue = oldItem.badgeValue;
+- (void)setIcon:(UIImage *)icon
+{
+ _icon = icon;
+ if (_icon && _systemIcon != NSNotFound) {
+ _systemIcon = NSNotFound;
+ UITabBarItem *oldItem = _barItem;
+ _barItem = [UITabBarItem new];
+ _barItem.title = oldItem.title;
+ _barItem.imageInsets = oldItem.imageInsets;
+ _barItem.selectedImage = oldItem.selectedImage;
+ _barItem.badgeValue = oldItem.badgeValue;
+ }
+ self.barItem.image = _icon;
}
- (UIViewController *)reactViewController
diff --git a/React/Views/RCTTabBarItemManager.m b/React/Views/RCTTabBarItemManager.m
index a926a54f3..d92e3a2d9 100644
--- a/React/Views/RCTTabBarItemManager.m
+++ b/React/Views/RCTTabBarItemManager.m
@@ -21,10 +21,11 @@ RCT_EXPORT_MODULE()
return [RCTTabBarItem new];
}
+RCT_EXPORT_VIEW_PROPERTY(badge, id /* NSString or NSNumber */)
RCT_EXPORT_VIEW_PROPERTY(selected, BOOL)
-RCT_EXPORT_VIEW_PROPERTY(icon, id)
+RCT_EXPORT_VIEW_PROPERTY(icon, UIImage)
RCT_REMAP_VIEW_PROPERTY(selectedIcon, barItem.selectedImage, UIImage)
-RCT_REMAP_VIEW_PROPERTY(badge, barItem.badgeValue, NSString)
+RCT_EXPORT_VIEW_PROPERTY(systemIcon, UITabBarSystemItem)
RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
RCT_CUSTOM_VIEW_PROPERTY(title, NSString, RCTTabBarItem)
{