Added RCTImageSource
Summary: public The +[RCTConvert UIImage:] function, while convenient, is inherently limited by being synchronous, which means that it cannot be used to load remote images, and may not be efficient for local images either. It's also unable to access the bridge, which means that it cannot take advantage of the modular image-loading pipeline. This diff introduces a new RCTImageSource class which can be used to pass image source objects over the bridge and defer loading until later. I've also added automatic application of the `resolveAssetSource()` function based on prop type, and fixed up the image logic in NavigatorIOS and TabBarIOS. Reviewed By: javache Differential Revision: D2631541 fb-gh-sync-id: 6604635e8bb5394425102487f1ee7cd729321877
This commit is contained in:
parent
dcebe8cd37
commit
b672294858
|
@ -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 (
|
||||
<StaticContainer key={'nav' + i} shouldUpdate={shouldUpdateChild}>
|
||||
<RCTNavigatorItem
|
||||
title={route.title}
|
||||
{...route}
|
||||
{...props}
|
||||
style={[
|
||||
styles.stackItem,
|
||||
this.props.itemWrapperStyle,
|
||||
route.wrapperStyle
|
||||
]}
|
||||
backButtonIcon={resolveAssetSource(route.backButtonIcon)}
|
||||
backButtonTitle={route.backButtonTitle}
|
||||
leftButtonIcon={resolveAssetSource(route.leftButtonIcon)}
|
||||
leftButtonTitle={route.leftButtonTitle}
|
||||
onNavLeftButtonTap={route.onLeftButtonPress}
|
||||
rightButtonIcon={resolveAssetSource(route.rightButtonIcon)}
|
||||
rightButtonTitle={route.rightButtonTitle}
|
||||
onNavRightButtonTap={route.onRightButtonPress}
|
||||
navigationBarHidden={this.props.navigationBarHidden}
|
||||
shadowHidden={this.props.shadowHidden}
|
||||
tintColor={this.props.tintColor}
|
||||
barTintColor={this.props.barTintColor}
|
||||
translucent={this.props.translucent !== false}
|
||||
titleTextColor={this.props.titleTextColor}>
|
||||
itemWrapperStyle,
|
||||
wrapperStyle
|
||||
]}>
|
||||
<Component
|
||||
navigator={this.navigator}
|
||||
route={route}
|
||||
{...route.passProps}
|
||||
{...passProps}
|
||||
/>
|
||||
</RCTNavigatorItem>
|
||||
</StaticContainer>
|
||||
|
|
|
@ -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 =
|
||||
<StaticContainer shouldUpdate={this.props.selected}>
|
||||
{this.props.children}
|
||||
{children}
|
||||
</StaticContainer>;
|
||||
} else {
|
||||
tabContents = <View />;
|
||||
var tabContents = <View />;
|
||||
}
|
||||
|
||||
var badge = typeof this.props.badge === 'number' ?
|
||||
'' + this.props.badge :
|
||||
this.props.badge;
|
||||
|
||||
|
||||
return (
|
||||
<RCTTabBarItem
|
||||
{...this.props}
|
||||
icon={this.props.systemIcon || resolveAssetSource(this.props.icon)}
|
||||
selectedIcon={resolveAssetSource(this.props.selectedIcon)}
|
||||
badge={badge}
|
||||
style={[styles.tab, this.props.style]}>
|
||||
{...props}
|
||||
style={[styles.tab, style]}>
|
||||
{tabContents}
|
||||
</RCTTabBarItem>
|
||||
);
|
||||
|
|
|
@ -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 <View {...this.props} style={style} />;
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#import "RCTImageComponent.h"
|
||||
|
||||
@class RCTBridge;
|
||||
@class RCTImageSource;
|
||||
|
||||
@interface RCTImageView : UIImageView <RCTImageComponent>
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#import <UIKit/UIKit.h>
|
||||
|
||||
#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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#import <objc/message.h>
|
||||
|
||||
#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
|
||||
|
|
|
@ -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 <Foundation/Foundation.h>
|
||||
|
||||
#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
|
|
@ -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
|
|
@ -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<NSString *> *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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = "<group>"; };
|
||||
13B080231A694A8400A75B9A /* RCTWrapperViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTWrapperViewController.h; sourceTree = "<group>"; };
|
||||
13B080241A694A8400A75B9A /* RCTWrapperViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTWrapperViewController.m; sourceTree = "<group>"; };
|
||||
13B201FF1BFB945300C07393 /* RCTPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPasteboard.h; sourceTree = "<group>"; };
|
||||
13BB3D001BECD54500932C10 /* RCTImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageSource.h; sourceTree = "<group>"; };
|
||||
13BB3D011BECD54500932C10 /* RCTImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageSource.m; sourceTree = "<group>"; };
|
||||
13B201FF1BFB945300C07393 /* RCTPasteboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPasteboard.h; sourceTree = "<group>"; };
|
||||
13B202001BFB945300C07393 /* RCTPasteboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPasteboard.m; sourceTree = "<group>"; };
|
||||
13B202021BFB948C00C07393 /* RCTMapAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMapAnnotation.h; sourceTree = "<group>"; };
|
||||
13B202031BFB948C00C07393 /* RCTMapAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMapAnnotation.m; sourceTree = "<group>"; };
|
||||
|
@ -526,6 +529,8 @@
|
|||
1345A83B1B265A0E00583190 /* RCTURLRequestHandler.h */,
|
||||
83CBBA4F1A601E3B00E9B192 /* RCTUtils.h */,
|
||||
83CBBA501A601E3B00E9B192 /* RCTUtils.m */,
|
||||
13BB3D001BECD54500932C10 /* RCTImageSource.h */,
|
||||
13BB3D011BECD54500932C10 /* RCTImageSource.m */,
|
||||
);
|
||||
path = Base;
|
||||
sourceTree = "<group>";
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue