react-native/Libraries/Image/RCTImageView.m

194 lines
5.1 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
{
image = image ?: _defaultImage;
if (image != super.image) {
super.image = image;
[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": @((double)loaded),
@"total": @((double)total),
};
[_bridge.eventDispatcher sendInputEventWithName:@"progress" body:event];
};
}
[_bridge.imageLoader 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];
}
}
}
- (void)didMoveToWindow
{
[super didMoveToWindow];
if (!self.window) {
[self.layer removeAnimationForKey:@"contents"];
self.image = nil;
} else if (self.src) {
[self reloadImage];
}
}
@end