mirror of
https://github.com/status-im/react-native.git
synced 2025-02-15 10:57:00 +00:00
Summary: RCTNetworkImageView and RCTStaticImage had significant overlap in functionality, but each had a different subset of features and bugs. This diff merges most of the functionality of RCTNetworkImageView into RCTStaticImage, eliminating some bugs in the former, such as constant redrawing when properties were changed. I've also removed the onLoadAbort event for now (as it wasn't implemented), and renamed the other events to match the web specs for `<img>` and XHMLHttpRequest. The API is essentially what Adobe proposed here: http://blogs.adobe.com/webplatform/2012/01/13/html5-image-progress-events/ The following features have not yet been ported from RCTNetworkImageView: - Background color compositing. It's not clear that this adds much value and it increases memory consumption, etc. - Image request cancelling when images are removed from view. Again, it's not clear if this is a huge benefit, but if it is it should be combined with other optimisations, such as unloading offscreen images. (Note that this only affects the open source fork. For now, internal apps will still use FBNetworkImageView for remote images.)
181 lines
4.9 KiB
Objective-C
181 lines
4.9 KiB
Objective-C
/**
|
|
* 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 "RCTImageView.h"
|
|
|
|
#import "RCTBridge.h"
|
|
#import "RCTConvert.h"
|
|
#import "RCTEventDispatcher.h"
|
|
#import "RCTGIFImage.h"
|
|
#import "RCTImageLoader.h"
|
|
#import "RCTUtils.h"
|
|
|
|
#import "UIView+React.h"
|
|
|
|
@interface RCTImageView ()
|
|
|
|
@property (nonatomic, assign) BOOL onLoadStart;
|
|
@property (nonatomic, assign) BOOL onProgress;
|
|
@property (nonatomic, assign) BOOL onError;
|
|
@property (nonatomic, assign) BOOL onLoad;
|
|
@property (nonatomic, assign) BOOL onLoadEnd;
|
|
|
|
@end
|
|
|
|
@implementation RCTImageView
|
|
{
|
|
RCTBridge *_bridge;
|
|
}
|
|
|
|
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
|
{
|
|
if ((self = [super init])) {
|
|
_bridge = bridge;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(-init)
|
|
|
|
- (void)_updateImage
|
|
{
|
|
UIImage *image = self.image;
|
|
if (!image) {
|
|
return;
|
|
}
|
|
|
|
// Apply rendering mode
|
|
if (_renderingMode != image.renderingMode) {
|
|
image = [image imageWithRenderingMode:_renderingMode];
|
|
}
|
|
|
|
// Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired
|
|
if (!UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, _capInsets)) {
|
|
image = [image resizableImageWithCapInsets:_capInsets resizingMode:UIImageResizingModeStretch];
|
|
}
|
|
|
|
// Apply trilinear filtering to smooth out mis-sized images
|
|
self.layer.minificationFilter = kCAFilterTrilinear;
|
|
self.layer.magnificationFilter = kCAFilterTrilinear;
|
|
|
|
super.image = image;
|
|
}
|
|
|
|
- (void)setImage:(UIImage *)image
|
|
{
|
|
if (image != super.image) {
|
|
super.image = image ?: _defaultImage;
|
|
[self _updateImage];
|
|
}
|
|
}
|
|
|
|
- (void)setCapInsets:(UIEdgeInsets)capInsets
|
|
{
|
|
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, capInsets)) {
|
|
_capInsets = capInsets;
|
|
[self _updateImage];
|
|
}
|
|
}
|
|
|
|
- (void)setRenderingMode:(UIImageRenderingMode)renderingMode
|
|
{
|
|
if (_renderingMode != renderingMode) {
|
|
_renderingMode = renderingMode;
|
|
[self _updateImage];
|
|
}
|
|
}
|
|
|
|
- (void)setSrc:(NSString *)src
|
|
{
|
|
if (![src isEqual:_src]) {
|
|
_src = [src copy];
|
|
[self reloadImage];
|
|
}
|
|
}
|
|
|
|
- (void)reloadImage
|
|
{
|
|
if (_src && !CGSizeEqualToSize(self.frame.size, CGSizeZero)) {
|
|
|
|
if (_onLoadStart) {
|
|
NSDictionary *event = @{ @"target": self.reactTag };
|
|
[_bridge.eventDispatcher sendInputEventWithName:@"loadStart" body:event];
|
|
}
|
|
|
|
RCTImageLoaderProgressBlock progressHandler = nil;
|
|
if (_onProgress) {
|
|
progressHandler = ^(int64_t loaded, int64_t total) {
|
|
NSDictionary *event = @{
|
|
@"target": self.reactTag,
|
|
@"loaded": @(loaded),
|
|
@"total": @(total),
|
|
};
|
|
[_bridge.eventDispatcher sendInputEventWithName:@"progress" body:event];
|
|
};
|
|
}
|
|
|
|
[RCTImageLoader loadImageWithTag:_src
|
|
size:self.bounds.size
|
|
scale:RCTScreenScale()
|
|
resizeMode:self.contentMode
|
|
progressBlock:progressHandler
|
|
completionBlock:^(NSError *error, id image) {
|
|
|
|
if ([image isKindOfClass:[CAAnimation class]]) {
|
|
[self.layer addAnimation:image forKey:@"contents"];
|
|
} else {
|
|
[self.layer removeAnimationForKey:@"contents"];
|
|
self.image = image;
|
|
}
|
|
if (error) {
|
|
if (_onError) {
|
|
NSDictionary *event = @{
|
|
@"target": self.reactTag,
|
|
@"error": error.localizedDescription,
|
|
};
|
|
[_bridge.eventDispatcher sendInputEventWithName:@"error" body:event];
|
|
}
|
|
} else {
|
|
if (_onLoad) {
|
|
NSDictionary *event = @{ @"target": self.reactTag };
|
|
[_bridge.eventDispatcher sendInputEventWithName:@"load" body:event];
|
|
}
|
|
}
|
|
if (_onLoadEnd) {
|
|
NSDictionary *event = @{ @"target": self.reactTag };
|
|
[_bridge.eventDispatcher sendInputEventWithName:@"loadEnd" body:event];
|
|
}
|
|
}];
|
|
} else {
|
|
[self.layer removeAnimationForKey:@"contents"];
|
|
self.image = nil;
|
|
}
|
|
}
|
|
|
|
- (void)reactSetFrame:(CGRect)frame
|
|
{
|
|
[super reactSetFrame:frame];
|
|
if (self.image == nil) {
|
|
[self reloadImage];
|
|
} else if ([RCTImageLoader isAssetLibraryImage:_src] || [RCTImageLoader isRemoteImage:_src]) {
|
|
CGSize imageSize = {
|
|
self.image.size.width / RCTScreenScale(),
|
|
self.image.size.height / RCTScreenScale()
|
|
};
|
|
CGFloat widthChangeFraction = imageSize.width ? ABS(imageSize.width - frame.size.width) / imageSize.width : 1;
|
|
CGFloat heightChangeFraction = imageSize.height ? ABS(imageSize.height - frame.size.height) / imageSize.height : 1;
|
|
// If the combined change is more than 20%, reload the asset in case there is a better size.
|
|
if (widthChangeFraction + heightChangeFraction > 0.2) {
|
|
[self reloadImage];
|
|
}
|
|
}
|
|
}
|
|
|
|
@end
|