Added support for width & height for text images
Summary: public Previously, `<Image>` elements embedded inside `<Text>` ignored all style attributes and props apart from `source`. Now, the `width`, `height` and `resizeMode` styles are observed. I've also added a transparent placeholder to be displayed while the image is loading, to prevent the layout from changing after the image has loaded. Reviewed By: javache Differential Revision: D2838659 fb-gh-sync-id: c27f9685b6976705ac2b24075922b2bf247e06ba
This commit is contained in:
parent
f20453baee
commit
2cbc912756
|
@ -419,7 +419,7 @@ exports.examples = [
|
|||
return (
|
||||
<View>
|
||||
<Text>
|
||||
This text contains an inline image <Image source={require('./flux.png')}/>. Neat, huh?
|
||||
This text contains an inline image <Image source={require('./flux.png')} style={{width: 30, height: 11, resizeMode: 'cover'}}/>. Neat, huh?
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
|
|
@ -133,4 +133,52 @@ RCTAssertEqualSizes(a.size, b.size); \
|
|||
RCTAssertEqualRects(expected, result);
|
||||
}
|
||||
|
||||
- (void)testPlaceholderImage
|
||||
{
|
||||
CGSize size = {45, 22};
|
||||
CGFloat expectedScale = 1.0;
|
||||
UIImage *image = RCTGetPlaceholderImage(size, nil);
|
||||
RCTAssertEqualSizes(size, image.size);
|
||||
XCTAssertEqual(expectedScale, image.scale);
|
||||
}
|
||||
|
||||
- (void)testPlaceholderNonintegralSize
|
||||
{
|
||||
CGSize size = {3.0/2, 7.0/3};
|
||||
CGFloat expectedScale = 6;
|
||||
CGSize pixelSize = {
|
||||
round(size.width * expectedScale),
|
||||
round(size.height * expectedScale)
|
||||
};
|
||||
UIImage *image = RCTGetPlaceholderImage(size, nil);
|
||||
RCTAssertEqualSizes(size, image.size);
|
||||
XCTAssertEqual(pixelSize.width, CGImageGetWidth(image.CGImage));
|
||||
XCTAssertEqual(pixelSize.height, CGImageGetHeight(image.CGImage));
|
||||
XCTAssertEqual(expectedScale, image.scale);
|
||||
}
|
||||
|
||||
- (void)testPlaceholderSquareImage
|
||||
{
|
||||
CGSize size = {333, 333};
|
||||
CGFloat expectedScale = 1.0/333;
|
||||
CGSize pixelSize = {1, 1};
|
||||
UIImage *image = RCTGetPlaceholderImage(size, nil);
|
||||
RCTAssertEqualSizes(size, image.size);
|
||||
XCTAssertEqual(pixelSize.width, CGImageGetWidth(image.CGImage));
|
||||
XCTAssertEqual(pixelSize.height, CGImageGetHeight(image.CGImage));
|
||||
XCTAssertEqual(expectedScale, image.scale);
|
||||
}
|
||||
|
||||
- (void)testPlaceholderNonsquareImage
|
||||
{
|
||||
CGSize size = {640, 480};
|
||||
CGFloat expectedScale = 1.0/160;
|
||||
CGSize pixelSize = {4, 3};
|
||||
UIImage *image = RCTGetPlaceholderImage(size, nil);
|
||||
RCTAssertEqualSizes(size, image.size);
|
||||
XCTAssertEqual(pixelSize.width, CGImageGetWidth(image.CGImage));
|
||||
XCTAssertEqual(pixelSize.height, CGImageGetHeight(image.CGImage));
|
||||
XCTAssertEqual(expectedScale, image.scale);
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -196,10 +196,10 @@ var Image = React.createClass({
|
|||
|
||||
render: function() {
|
||||
var source = resolveAssetSource(this.props.source) || {};
|
||||
var {width, height} = source;
|
||||
var {width, height, uri} = source;
|
||||
var style = flattenStyle([{width, height}, styles.base, this.props.style]) || {};
|
||||
|
||||
var isNetwork = source.uri && source.uri.match(/^https?:/);
|
||||
var isNetwork = uri && uri.match(/^https?:/);
|
||||
var RawImage = isNetwork ? RCTNetworkImageView : RCTImageView;
|
||||
var resizeMode = this.props.resizeMode || (style || {}).resizeMode || 'cover'; // Workaround for flow bug t7737108
|
||||
var tintColor = (style || {}).tintColor; // Workaround for flow bug t7737108
|
||||
|
@ -211,18 +211,21 @@ var Image = React.createClass({
|
|||
}
|
||||
|
||||
if (this.context.isInAParentText) {
|
||||
return <RCTVirtualImage source={source}/>;
|
||||
} else {
|
||||
return (
|
||||
<RawImage
|
||||
{...this.props}
|
||||
style={style}
|
||||
resizeMode={resizeMode}
|
||||
tintColor={tintColor}
|
||||
source={source}
|
||||
/>
|
||||
);
|
||||
RawImage = RCTVirtualImage;
|
||||
if (!width || !height) {
|
||||
console.warn('You must specify a width and height for the image %s', uri);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<RawImage
|
||||
{...this.props}
|
||||
style={style}
|
||||
resizeMode={resizeMode}
|
||||
tintColor={tintColor}
|
||||
source={source}
|
||||
/>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -93,4 +93,11 @@ RCT_EXTERN UIImage *__nullable RCTTransformImage(UIImage *image,
|
|||
*/
|
||||
RCT_EXTERN BOOL RCTImageHasAlpha(CGImageRef image);
|
||||
|
||||
/**
|
||||
* Create a solid placeholder image of the specified size and color to display
|
||||
* while loading an image. If color is not specified, image will be transparent.
|
||||
*/
|
||||
RCT_EXTERN UIImage *__nullable RCTGetPlaceholderImage(CGSize size,
|
||||
UIColor *__nullable color);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#import "RCTLog.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static const CGFloat RCTThresholdValue = 0.0001;
|
||||
|
||||
static CGFloat RCTCeilValue(CGFloat value, CGFloat scale)
|
||||
{
|
||||
return ceil(value * scale) / scale;
|
||||
|
@ -334,3 +336,46 @@ BOOL RCTImageHasAlpha(CGImageRef image)
|
|||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
UIImage *__nullable RCTGetPlaceholderImage(CGSize size,
|
||||
UIColor *__nullable color)
|
||||
{
|
||||
if (size.width <= 0 || size.height <= 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// If dimensions are nonintegral, increase scale
|
||||
CGFloat scale = 1;
|
||||
if (size.width - floor(size.width) > RCTThresholdValue) {
|
||||
scale *= round(1.0 / (size.width - floor(size.width)));
|
||||
}
|
||||
if (size.height - floor(size.height) > RCTThresholdValue) {
|
||||
scale *= round(1.0 / (size.height - floor(size.height)));
|
||||
}
|
||||
|
||||
// Use Euclid's algorithm to find the greatest common divisor
|
||||
// between the specified placeholder width and height;
|
||||
NSInteger a = size.width * scale;
|
||||
NSInteger b = size.height * scale;
|
||||
while (a != 0) {
|
||||
NSInteger c = a;
|
||||
a = b % a;
|
||||
b = c;
|
||||
}
|
||||
|
||||
// Divide the placeholder image scale by the GCD we found above. This allows
|
||||
// us to save memory by creating the smallest possible placeholder image
|
||||
// with the correct aspect ratio, then scaling it up at display time.
|
||||
scale /= b;
|
||||
|
||||
// Fill image with specified color
|
||||
CGFloat alpha = CGColorGetAlpha(color.CGColor);
|
||||
UIGraphicsBeginImageContextWithOptions(size, ABS(1.0 - alpha) < RCTThresholdValue, scale);
|
||||
if (alpha > 0) {
|
||||
[color setFill];
|
||||
UIRectFill((CGRect){CGPointZero, size});
|
||||
}
|
||||
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
return image;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#import "RCTShadowView.h"
|
||||
#import "RCTImageComponent.h"
|
||||
#import "RCTImageSource.h"
|
||||
#import "RCTResizeMode.h"
|
||||
|
||||
@class RCTBridge;
|
||||
|
||||
|
@ -22,5 +23,6 @@
|
|||
- (instancetype)initWithBridge:(RCTBridge *)bridge;
|
||||
|
||||
@property (nonatomic, strong) RCTImageSource *source;
|
||||
@property (nonatomic, assign) RCTResizeMode resizeMode;
|
||||
|
||||
@end
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
|
||||
#import "RCTShadowVirtualImage.h"
|
||||
#import "RCTImageLoader.h"
|
||||
#import "RCTImageUtils.h"
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTUIManager.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
@implementation RCTShadowVirtualImage
|
||||
{
|
||||
|
@ -31,36 +33,47 @@
|
|||
|
||||
RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
||||
|
||||
- (void)setSource:(RCTImageSource *)source
|
||||
- (void)didSetProps:(NSArray<NSString *> *)changedProps
|
||||
{
|
||||
if (![source isEqual:_source]) {
|
||||
[super didSetProps:changedProps];
|
||||
|
||||
// Cancel previous request
|
||||
if (_cancellationBlock) {
|
||||
_cancellationBlock();
|
||||
}
|
||||
|
||||
_source = source;
|
||||
|
||||
__weak RCTShadowVirtualImage *weakSelf = self;
|
||||
_cancellationBlock = [_bridge.imageLoader loadImageWithTag:source.imageURL.absoluteString
|
||||
size:source.size
|
||||
scale:source.scale
|
||||
resizeMode:RCTResizeModeStretch
|
||||
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];
|
||||
});
|
||||
}];
|
||||
if (changedProps.count == 0) {
|
||||
// No need to reload image
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel previous request
|
||||
if (_cancellationBlock) {
|
||||
_cancellationBlock();
|
||||
}
|
||||
|
||||
CGSize imageSize = {
|
||||
RCTZeroIfNaN(self.width),
|
||||
RCTZeroIfNaN(self.height),
|
||||
};
|
||||
|
||||
if (!_image) {
|
||||
_image = RCTGetPlaceholderImage(imageSize, nil);
|
||||
}
|
||||
|
||||
__weak RCTShadowVirtualImage *weakSelf = self;
|
||||
_cancellationBlock = [_bridge.imageLoader loadImageWithTag:_source.imageURL.absoluteString
|
||||
size:imageSize
|
||||
scale:RCTScreenScale()
|
||||
resizeMode:_resizeMode
|
||||
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];
|
||||
});
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
|
|
|
@ -20,5 +20,6 @@ RCT_EXPORT_MODULE()
|
|||
}
|
||||
|
||||
RCT_EXPORT_SHADOW_PROPERTY(source, RCTImageSource)
|
||||
RCT_EXPORT_SHADOW_PROPERTY(resizeMode, UIViewContentMode)
|
||||
|
||||
@end
|
||||
|
|
|
@ -213,8 +213,6 @@ static css_dim_t RCTMeasure(void *context, float width, float height)
|
|||
NSTextAttachment *imageAttachment = [NSTextAttachment new];
|
||||
imageAttachment.image = image;
|
||||
[attributedString appendAttributedString:[NSAttributedString attributedStringWithAttachment:imageAttachment]];
|
||||
} else {
|
||||
//TODO: add placeholder image?
|
||||
}
|
||||
} else {
|
||||
RCTLogError(@"<Text> can't have any children except <Text>, <Image> or raw strings");
|
||||
|
|
Loading…
Reference in New Issue