blob: 1cb824516f89b92220e7608006309eb4f5809288 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import "WXImageComponent.h"
#import "WXHandlerFactory.h"
#import "WXComponent_internal.h"
#import "WXImgLoaderProtocol.h"
#import "WXLayer.h"
#import "WXType.h"
#import "WXConvert.h"
#import "WXURLRewriteProtocol.h"
#import "WXRoundedRect.h"
#import "UIBezierPath+Weex.h"
#import "WXSDKEngine.h"
#import "WXUtility.h"
#import "WXAssert.h"
#import "WXConfigCenterProtocol.h"
#import "WXSDKEngine.h"
#import <pthread/pthread.h>
#import "WXMonitor.h"
#import "WXSDKInstance_performance.h"
@interface WXImageView : UIImageView
@end
@implementation WXImageView
+ (Class)layerClass
{
return [WXLayer class];
}
@end
static dispatch_queue_t WXImageUpdateQueue;
@interface WXImageComponent ()
{
NSString * _imageSrc;
pthread_mutex_t _imageSrcMutex;
pthread_mutexattr_t _propertMutexAttr;
BOOL _shouldUpdateImage;
BOOL _mainImageSuccess;
}
@property (atomic, strong) NSString *placeholdSrc;
@property (nonatomic, assign) CGFloat blurRadius;
@property (nonatomic, assign) UIViewContentMode resizeMode;
@property (nonatomic, assign) WXImageQuality imageQuality;
@property (nonatomic, assign) WXImageSharp imageSharp;
@property (nonatomic, strong) UIImage *image;
@property (nonatomic, strong) id<WXImageOperationProtocol> imageOperation;
@property (nonatomic, strong) id<WXImageOperationProtocol> placeholderOperation;
@property (nonatomic) BOOL imageLoadEvent;
@property (nonatomic) BOOL imageDownloadFinish;
@property (nonatomic) BOOL downloadImageWithURL;
@property (nonatomic ,strong) NSString* preUrl;
@end
@implementation WXImageComponent
WX_EXPORT_METHOD(@selector(save:))
- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
{
if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
_shouldUpdateImage = NO;
_async = YES;
if (!WXImageUpdateQueue) {
WXImageUpdateQueue = dispatch_queue_create("com.taobao.weex.ImageUpdateQueue", DISPATCH_QUEUE_SERIAL);
}
pthread_mutexattr_init(&(_propertMutexAttr));
pthread_mutexattr_settype(&(_propertMutexAttr), PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&(_imageSrcMutex), &(_propertMutexAttr));
if (attributes[@"src"]) {
pthread_mutex_lock(&(_imageSrcMutex));
_imageSrc = [[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
pthread_mutex_unlock(&(_imageSrcMutex));
} else {
WXLogWarning(@"image src is nil");
}
[self configPlaceHolder:attributes];
NSString *resizeMode = attributes[@"resize"];
if (!resizeMode) {
resizeMode = styles[@"resizeMode"];
}
_resizeMode = [WXConvert UIViewContentMode:resizeMode];
_imageQuality = WXImageQualityNone;
if (styles[@"quality"]) {
_imageQuality = [WXConvert WXImageQuality:styles[@"quality"]];
}
if (attributes[@"quality"]) {
_imageQuality = [WXConvert WXImageQuality:attributes[@"quality"]];
}
_downloadImageWithURL = YES;
if (attributes[@"compositing"]) {
_downloadImageWithURL = [WXConvert BOOL:attributes[@"compositing"]];
}
[self configFilter:styles needUpdate:NO];
_imageSharp = [WXConvert WXImageSharp:styles[@"sharpen"]];
_imageLoadEvent = NO;
_imageDownloadFinish = NO;
}
return self;
}
- (void)configPlaceHolder:(NSDictionary*)attributes
{
if (attributes[@"placeHolder"] || attributes[@"placeholder"]) {
self.placeholdSrc = [[WXConvert NSString:attributes[@"placeHolder"]?:attributes[@"placeholder"]]stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
}
- (void)configFilter:(NSDictionary *)styles needUpdate:(BOOL)needUpdate
{
if (styles[@"filter"]) {
NSString *filter = styles[@"filter"];
NSString *pattern = @"blur\\((\\d+)(px)?\\)";
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
options:NSRegularExpressionCaseInsensitive
error:&error];
NSArray *matches = [regex matchesInString:filter options:0 range:NSMakeRange(0, filter.length)];
if (matches && matches.count > 0) {
NSTextCheckingResult *match = matches[matches.count - 1];
NSRange matchRange = [match rangeAtIndex:1];
NSString *matchString = [filter substringWithRange:matchRange];
if (matchString && matchString.length > 0) {
_blurRadius = [matchString doubleValue];
if (needUpdate) {
[self updateImage];
}
}
}
}
}
- (void)save:(WXKeepAliveCallback)resultCallback
{
NSDictionary *info = [NSBundle mainBundle].infoDictionary;
if(!info[@"NSPhotoLibraryUsageDescription"]) {
if (resultCallback) {
resultCallback(@{
@"success" : @(false),
@"errorDesc": @"This maybe crash above iOS 10 because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSPhotoLibraryUsageDescription key with a string value explaining to the user how the app uses this data."
}, NO);
}
return ;
}
// iOS 11 needs a NSPhotoLibraryUsageDescription key for permission
if (WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"11.0")) {
if (!info[@"NSPhotoLibraryAddUsageDescription"]) {
if (resultCallback) {
resultCallback(@{
@"success" : @(false),
@"errorDesc": @"This maybe crash above iOS 11 because it attempted to save image without a usage description. The app's Info.plist must contain an NSPhotoLibraryAddUsageDescription key with a string value explaining to the user how the app uses this data."
}, NO);
}
return;
}
}
if (![self isViewLoaded]) {
if (resultCallback) {
resultCallback(@{@"success": @(false),
@"errorDesc":@"the image is not ready"}, NO);
}
return;
}
UIImageView * imageView = (UIImageView*)self.view;
if (!resultCallback) {
// there is no need to callback any result;
UIImageWriteToSavedPhotosAlbum(imageView.image, nil, nil, NULL);
}else {
UIImageWriteToSavedPhotosAlbum(imageView.image, self, @selector(image:didFinishSavingWithError:contextInfo:), (void*)CFBridgingRetain(resultCallback));
}
}
// the callback for PhotoAlbum.
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
if (!contextInfo) {
return;
}
NSMutableDictionary * callbackResult = [NSMutableDictionary new];
BOOL success = false;
if (!error) {
success = true;
} else {
[callbackResult setObject:[error description] forKey:@"errorDesc"];
}
if (contextInfo) {
[callbackResult setObject:@(success) forKey:@"success"];
((__bridge WXKeepAliveCallback)contextInfo)(callbackResult, NO);
CFRelease(contextInfo);
}
}
- (UIView *)loadView
{
return [[WXImageView alloc] init];
}
- (void)addEvent:(NSString *)eventName {
if ([eventName isEqualToString:@"load"]) {
_imageLoadEvent = YES;
}
}
- (void)removeEvent:(NSString *)eventName {
if ([eventName isEqualToString:@"load"]) {
_imageLoadEvent = NO;
}
}
- (void)updateStyles:(NSDictionary *)styles
{
if (styles[@"quality"]) {
_imageQuality = [WXConvert WXImageQuality:styles[@"quality"]];
[self updateImage];
}
if (styles[@"sharpen"]) {
_imageSharp = [WXConvert WXImageSharp:styles[@"sharpen"]];
[self updateImage];
}
[self configFilter:styles needUpdate:YES];
}
- (void)dealloc
{
[self cancelImage];
pthread_mutex_destroy(&(_imageSrcMutex));
pthread_mutexattr_destroy(&_propertMutexAttr);
}
- (void)updateAttributes:(NSDictionary *)attributes
{
if (attributes[@"src"]) {
[self setImageSrc:[[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}
if (attributes[@"quality"]) {
_imageQuality = [WXConvert WXImageQuality:attributes[@"quality"]];
[self updateImage];
}
[self configPlaceHolder:attributes];
if (attributes[@"resize"]) {
_resizeMode = [WXConvert UIViewContentMode:attributes[@"resize"]];
self.view.contentMode = _resizeMode;
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIImageView *imageView = (UIImageView *)self.view;
imageView.contentMode = _resizeMode;
imageView.userInteractionEnabled = YES;
imageView.clipsToBounds = YES;
imageView.exclusiveTouch = YES;
imageView.isAccessibilityElement = YES;
[self _clipsToBounds];
[self updateImage];
}
- (BOOL)_needsDrawBorder
{
return NO;
}
- (void)_resetNativeBorderRadius
{
if (_borderTopLeftRadius == _borderTopRightRadius && _borderTopRightRadius == _borderBottomLeftRadius && _borderBottomLeftRadius == _borderBottomRightRadius)
{
WXRoundedRect *borderRect = [[WXRoundedRect alloc] initWithRect:self.calculatedFrame topLeft:_borderTopLeftRadius topRight:_borderTopRightRadius bottomLeft:_borderBottomLeftRadius bottomRight:_borderBottomRightRadius];
_layer.cornerRadius = borderRect.radii.topLeft;
return;
}
[self _clipsToBounds];
}
- (BOOL)needsDrawRect
{
if (_isCompositingChild) {
return YES;
} else {
return NO;
}
}
- (UIImage *)drawRect:(CGRect)rect;
{
if (!self.image) {
[self updateImage];
return nil;
}
WXRoundedRect *borderRect = [[WXRoundedRect alloc] initWithRect:rect topLeft:_borderTopLeftRadius topRight:_borderTopRightRadius bottomLeft:_borderBottomLeftRadius bottomRight:_borderBottomRightRadius];
WXRadii *radii = borderRect.radii;
if ([radii hasBorderRadius]) {
CGFloat topLeft = radii.topLeft, topRight = radii.topRight, bottomLeft = radii.bottomLeft, bottomRight = radii.bottomRight;
UIBezierPath *bezierPath = [UIBezierPath wx_bezierPathWithRoundedRect:rect topLeft:topLeft topRight:topRight bottomLeft:bottomLeft bottomRight:bottomRight];
[bezierPath addClip];
}
return self.image;
}
- (void)viewWillUnload
{
[super viewWillUnload];
[self cancelImage];
_image = nil;
}
- (void)_frameDidCalculated:(BOOL)isChanged
{
[super _frameDidCalculated:isChanged];
if ([self isViewLoaded] && isChanged) {
__weak typeof(self) weakSelf = self;
WXPerformBlockOnMainThread(^{
[weakSelf _clipsToBounds];
});
}
}
- (NSString *)imageSrc
{
pthread_mutex_lock(&(_imageSrcMutex));
NSString * imageSrcCpy = [_imageSrc copy];
pthread_mutex_unlock(&(_imageSrcMutex));
return imageSrcCpy;
}
- (void)setImageSrc:(NSString*)src
{
if ([src isEqualToString:_imageSrc]) {
// if image src is equal to then ignore it.
return;
}
pthread_mutex_lock(&(_imageSrcMutex));
_imageSrc = src;
_imageDownloadFinish = NO;
((UIImageView*)self.view).image = nil;
pthread_mutex_unlock(&(_imageSrcMutex));
[self updateImage];
}
- (void)layoutDidFinish
{
[super layoutDidFinish];
if (_shouldUpdateImage) {
[self updateImage];
_shouldUpdateImage = NO;
}
}
- (void)updateImage
{
if (CGSizeEqualToSize(_view.frame.size, CGSizeZero)) {
_shouldUpdateImage = YES;
return;
}
__weak typeof(self) weakSelf = self;
if (_downloadImageWithURL && [[self imageLoader] respondsToSelector:@selector(setImageViewWithURL:url:placeholderImage:options:progress:completed:)]) {
_mainImageSuccess = NO;
NSString *newURL = nil;
if (self.placeholdSrc) {
newURL = [self.placeholdSrc copy];
WX_REWRITE_URL([self placeholdSrc], WXResourceTypeImage, self.weexInstance)
NSDictionary* extInfo = @{@"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""};
[[self imageLoader] setImageViewWithURL:(UIImageView*)self.view url:[NSURL URLWithString:newURL] placeholderImage:nil options:extInfo progress:nil completed:^(UIImage *image, NSError *error, WXImageLoaderCacheType cacheType, NSURL *imageURL) {
/* We cannot rely on image library even if we call setImage with placeholer before calling setImage with real url.
The placeholder image may be downloaded and decoded after the real url, so finally we show the placeholder image by wrong. */
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
UIImageView *imageView = (UIImageView *)strongSelf.view;
if (imageView && imageView.image == image && strongSelf->_mainImageSuccess) {
// reload image with main image url
NSString* newURL = [[strongSelf imageSrc] copy];
WX_REWRITE_URL([strongSelf imageSrc], WXResourceTypeImage, strongSelf.weexInstance)
NSDictionary *userInfo = @{@"imageQuality":@(strongSelf.imageQuality), @"imageSharp":@(strongSelf.imageSharp), @"blurRadius":@(strongSelf.blurRadius), @"instanceId":[strongSelf _safeInstanceId], @"pageURL": strongSelf.weexInstance.scriptURL ?: @""};
[[strongSelf imageLoader] setImageViewWithURL:imageView url:[NSURL URLWithString:newURL] placeholderImage:nil options:userInfo progress:nil completed:^(UIImage *image, NSError *error, WXImageLoaderCacheType cacheType, NSURL *imageURL) {
WXLogInfo(@"Image re-requested because placeholder may override main image. %@", imageURL);
}];
}
}
}];
}
newURL = [[self imageSrc] copy];
if ([newURL length] == 0) {
return;
}
WX_REWRITE_URL([self imageSrc], WXResourceTypeImage, self.weexInstance)
NSDictionary *userInfo = @{@"imageQuality":@(self.imageQuality), @"imageSharp":@(self.imageSharp), @"blurRadius":@(self.blurRadius), @"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""};
[[self imageLoader] setImageViewWithURL:(UIImageView*)self.view url:[NSURL URLWithString:newURL] placeholderImage:nil options:userInfo progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progress when loading image
} completed:^(UIImage *image, NSError *error, WXImageLoaderCacheType cacheType, NSURL *imageURL) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
strongSelf.imageDownloadFinish = YES;
if (error) {
// log error message for error
WXLogError(@"Error downloading image: %@, detail:%@", imageURL.absoluteString, [error localizedDescription]);
// retry set placeholder, maybe placeholer image can be downloaded
if (strongSelf.placeholdSrc) {
NSString *newURL = [strongSelf.placeholdSrc copy];
WX_REWRITE_URL([strongSelf placeholdSrc], WXResourceTypeImage, strongSelf.weexInstance)
[[strongSelf imageLoader] setImageViewWithURL:(UIImageView*)strongSelf.view
url:[NSURL URLWithString:newURL]
placeholderImage:nil
options:@{@"instanceId":[strongSelf _safeInstanceId], @"pageURL": strongSelf.weexInstance.scriptURL ?: @""}
progress:nil
completed:nil];
return;
}
strongSelf->_mainImageSuccess = NO;
}
else {
strongSelf->_mainImageSuccess = YES;
}
UIImageView *imageView = (UIImageView *)strongSelf.view;
if (imageView && imageView.image != image) {
imageView.image = image;
}
if (strongSelf.imageLoadEvent) {
NSMutableDictionary *sizeDict = [NSMutableDictionary new];
sizeDict[@"naturalWidth"] = @0;
sizeDict[@"naturalHeight"] = @0;
if (!error) {
sizeDict[@"naturalWidth"] = @(image.size.width * image.scale);
sizeDict[@"naturalHeight"] = @(image.size.height * image.scale);
} else {
[sizeDict setObject:[error description]?:@"" forKey:@"errorDesc"];
}
[strongSelf fireEvent:@"load" params:@{ @"success": error? @false : @true,@"size":sizeDict}];
}
NSString* curUrl = imageURL.absoluteString;
//check view/img size
if (!error && image && imageView && ![curUrl isEqualToString:self.preUrl]) {
self.preUrl = curUrl;
CGFloat screenScale = [[UIScreen mainScreen] scale];
double imageSize = image.size.width*image.scale * image.size.height*image.scale;
double viewSize = imageView.frame.size.height *screenScale* imageView.frame.size.width * screenScale;
CGFloat sizeRatio = imageSize/viewSize;
//minDiffSize limt 40*40
if (sizeRatio>1.2 && (imageSize-viewSize) > 1600) {
self.weexInstance.performance.imgWrongSizeNum++;
[self.weexInstance.apmInstance updateDiffStats:KEY_PAGE_STATS_WRONG_IMG_SIZE_COUNT withDiffValue:1];
}
if (image.size.width* image.scale > 720 && image.size.height * image.scale> 1080) {
[self.weexInstance.apmInstance updateDiffStats:KEY_PAGE_STATS_LARGE_IMG_COUNT withDiffValue:1];
}
}
}];
} else {
dispatch_async(WXImageUpdateQueue, ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
[strongSelf cancelImage];
void(^downloadFailed)(NSString *, NSError *) = ^void(NSString *url, NSError *error) {
weakSelf.imageDownloadFinish = YES;
WXLogError(@"Error downloading image: %@, detail:%@", url, [error localizedDescription]);
};
[strongSelf updatePlaceHolderWithFailedBlock:downloadFailed];
[strongSelf updateContentImageWithFailedBlock:downloadFailed];
if (!strongSelf.imageSrc && !strongSelf.placeholdSrc) {
dispatch_async(dispatch_get_main_queue(), ^{
strongSelf.layer.contents = nil;
strongSelf.imageDownloadFinish = YES;
[strongSelf readyToRender];
});
}
});
}
}
- (void)updatePlaceHolderWithFailedBlock:(void(^)(NSString *, NSError *))downloadFailedBlock
{
NSString *placeholderSrc = self.placeholdSrc;
if ([WXUtility isBlankString:placeholderSrc]) {
return;
}
WXLogDebug(@"Updating image, component:%@, placeholder:%@ ", self.ref, placeholderSrc);
NSString *newURL = [self.placeholdSrc copy];
WX_REWRITE_URL(self.placeholdSrc, WXResourceTypeImage, self.weexInstance)
__weak typeof(self) weakSelf = self;
self.placeholderOperation = [[self imageLoader] downloadImageWithURL:newURL imageFrame:self.calculatedFrame
userInfo:@{@"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""}
completed:^(UIImage *image, NSError *error, BOOL finished)
{
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(self) strongSelf = weakSelf;
UIImage *viewImage = ((UIImageView *)strongSelf.view).image;
if (error) {
downloadFailedBlock(placeholderSrc,error);
if ([strongSelf isViewLoaded] && !viewImage) {
((UIImageView *)(strongSelf.view)).image = nil;
[strongSelf readyToRender];
}
return;
}
if (![placeholderSrc isEqualToString:strongSelf.placeholdSrc]) {
return;
}
if ([strongSelf isViewLoaded] && !viewImage) {
((UIImageView *)strongSelf.view).image = image;
strongSelf.imageDownloadFinish = YES;
[strongSelf readyToRender];
} else if (strongSelf->_isCompositingChild) {
strongSelf->_image = image;
strongSelf.imageDownloadFinish = YES;
}
});
}];
}
- (void)updateContentImageWithFailedBlock:(void(^)(NSString *, NSError *))downloadFailedBlock
{
NSString *imageSrc = [NSString stringWithFormat:@"%@", self.imageSrc?:@""];
if ([WXUtility isBlankString:imageSrc]) {
WXLogError(@"image src is empty");
return;
}
WXLogDebug(@"Updating image:%@, component:%@", self.imageSrc, self.ref);
NSDictionary *userInfo = @{@"imageQuality":@(self.imageQuality), @"imageSharp":@(self.imageSharp), @"blurRadius":@(self.blurRadius), @"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""};
NSString * newURL = [imageSrc copy];
WX_REWRITE_URL(imageSrc, WXResourceTypeImage, self.weexInstance)
__weak typeof(self) weakSelf = self;
weakSelf.imageOperation = [[weakSelf imageLoader] downloadImageWithURL:newURL imageFrame:weakSelf.calculatedFrame userInfo:userInfo completed:^(UIImage *image, NSError *error, BOOL finished) {
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf.imageLoadEvent) {
NSMutableDictionary *sizeDict = [NSMutableDictionary new];
sizeDict[@"naturalWidth"] = @0;
sizeDict[@"naturalHeight"] = @0;
if (!error) {
sizeDict[@"naturalWidth"] = @(image.size.width * image.scale);
sizeDict[@"naturalHeight"] = @(image.size.height * image.scale);
} else {
[sizeDict setObject:[error description]?:@"" forKey:@"errorDesc"];
}
[strongSelf fireEvent:@"load" params:@{ @"success": error? @false : @true,@"size":sizeDict}];
}
if (error) {
downloadFailedBlock(imageSrc, error);
[strongSelf readyToRender];
return ;
}
if (![imageSrc isEqualToString:strongSelf.imageSrc]) {
return ;
}
if ([strongSelf isViewLoaded]) {
strongSelf.imageDownloadFinish = YES;
((UIImageView *)strongSelf.view).image = image;
[strongSelf readyToRender];
} else if (strongSelf->_isCompositingChild) {
strongSelf.imageDownloadFinish = YES;
strongSelf->_image = image;
[strongSelf setNeedsDisplay];
}
});
}];
}
- (NSString*) _safeInstanceId
{
return self.weexInstance.instanceId ? : @"unknown";
}
- (void)readyToRender
{
// when image download completely (success or failed)
if (self.weexInstance.trackComponent && _imageDownloadFinish) {
[super readyToRender];
}
}
- (void)cancelImage
{
if ([[self imageLoader] respondsToSelector:@selector(cancelCurrentImageLoad:)]) {
[[self imageLoader] cancelCurrentImageLoad:(UIImageView*)_view];
}
_shouldUpdateImage = NO;
[_imageOperation cancel];
_imageOperation = nil;
[_placeholderOperation cancel];
_placeholderOperation = nil;
}
- (id<WXImgLoaderProtocol>)imageLoader
{
static id<WXImgLoaderProtocol> imageLoader;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
imageLoader = [WXHandlerFactory handlerForProtocol:@protocol(WXImgLoaderProtocol)];
});
return imageLoader;
}
- (void)_clipsToBounds
{
WXAssertMainThread();
WXRoundedRect *borderRect = [[WXRoundedRect alloc] initWithRect:self.view.bounds topLeft:_borderTopLeftRadius topRight:_borderTopRightRadius bottomLeft:_borderBottomLeftRadius bottomRight:_borderBottomRightRadius];
// here is computed radii, do not use original style
WXRadii *radii = borderRect.radii;
if ([radii radiusesAreEqual]) {
return;
}
CGFloat topLeft = radii.topLeft, topRight = radii.topRight, bottomLeft = radii.bottomLeft, bottomRight = radii.bottomRight;
// clip to border radius
UIBezierPath *bezierPath = [UIBezierPath wx_bezierPathWithRoundedRect:self.view.bounds topLeft:topLeft topRight:topRight bottomLeft:bottomLeft bottomRight:bottomRight];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = bezierPath.CGPath;
self.layer.mask = shapeLayer;
self.layer.cornerRadius = 0;
}
#ifdef UITEST
- (NSString *)description
{
NSString *superDescription = super.description;
NSRange semicolonRange = [superDescription rangeOfString:@";"];
NSString *replacement = [NSString stringWithFormat:@"; imageSrc: %@; imageQuality: %@; imageSharp: %@; ",_imageSrc,_imageQuality,_imageSharp];
return [superDescription stringByReplacingCharactersInRange:semicolonRange withString:replacement];
}
#endif
@end