| /* |
| * 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 () |
| { |
| BOOL _shouldUpdateImage; |
| BOOL _mainImageSuccess; |
| } |
| |
| @property (atomic, strong) NSString *src; |
| @property (atomic, strong) NSString *darkThemeSrc; |
| @property (atomic, strong) NSString *placeholdSrc; |
| @property (atomic, strong) NSString *darkThemePlaceholderSrc; |
| @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); |
| } |
| |
| if (attributes[@"src"]) { |
| self.src = [[WXConvert NSString:attributes[@"src"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
| } else { |
| WXLogWarning(@"image src is nil"); |
| } |
| if (attributes[@"darkThemeSrc"]) { |
| self.darkThemeSrc = [[WXConvert NSString:attributes[@"darkThemeSrc"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
| } |
| |
| [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]]; |
| } |
| if (attributes[@"darkThemePlaceholder"]) { |
| self.darkThemePlaceholderSrc = [[WXConvert NSString:attributes[@"darkThemePlaceholder"]] 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]; |
| } |
| |
| - (void)updateAttributes:(NSDictionary *)attributes |
| { |
| if (attributes[@"darkThemeSrc"]) { |
| self.darkThemeSrc = [[WXConvert NSString:attributes[@"darkThemeSrc"]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
| } |
| 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]; |
| } |
| |
| - (void)themeDidChange:(NSString*)theme |
| { |
| [super themeDidChange:theme]; |
| if (_view) { |
| if (self.darkThemeSrc || self.darkThemePlaceholderSrc) { |
| [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]; |
| }); |
| } |
| } |
| |
| - (void)setImageSrc:(NSString*)src |
| { |
| if ([src isEqualToString:self.src]) { |
| // if image src is equal to then ignore it. |
| return; |
| } |
| self.src = src; |
| _imageDownloadFinish = NO; |
| ((UIImageView*)self.view).image = nil; |
| |
| [self updateImage]; |
| } |
| |
| - (void)layoutDidFinish |
| { |
| [super layoutDidFinish]; |
| if (_shouldUpdateImage) { |
| [self updateImage]; |
| _shouldUpdateImage = NO; |
| } |
| } |
| |
| - (void)updateImage |
| { |
| if (CGSizeEqualToSize(_view.frame.size, CGSizeZero)) { |
| _shouldUpdateImage = YES; |
| return; |
| } |
| |
| BOOL isDarkMode = [self.weexInstance isDarkTheme]; |
| NSString* choosedSrc = isDarkMode ? (self.darkThemeSrc ?: self.src) : self.src; |
| NSString* choosedPlaceholder = isDarkMode ? (self.darkThemePlaceholderSrc ?: self.placeholdSrc) : self.placeholdSrc; |
| |
| __weak typeof(self) weakSelf = self; |
| if (_downloadImageWithURL && [[self imageLoader] respondsToSelector:@selector(setImageViewWithURL:url:placeholderImage:options:progress:completed:)]) { |
| _mainImageSuccess = NO; |
| |
| NSString *newURL = nil; |
| if (choosedPlaceholder) { |
| newURL = [choosedPlaceholder copy]; |
| WX_REWRITE_URL(choosedPlaceholder, 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 = [choosedSrc copy]; |
| WX_REWRITE_URL(choosedSrc, 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 = [choosedSrc copy]; |
| if ([newURL length] == 0) { |
| return; |
| } |
| WX_REWRITE_URL(choosedSrc, 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 (choosedPlaceholder) { |
| NSString *newURL = [choosedPlaceholder copy]; |
| WX_REWRITE_URL(choosedPlaceholder, 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 (!choosedSrc && !choosedPlaceholder) { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| strongSelf.layer.contents = nil; |
| strongSelf.imageDownloadFinish = YES; |
| [strongSelf readyToRender]; |
| }); |
| } |
| }); |
| } |
| } |
| |
| - (void)updatePlaceHolderWithFailedBlock:(void(^)(NSString *, NSError *))downloadFailedBlock |
| { |
| BOOL isDarkMode = [self.weexInstance isDarkTheme]; |
| NSString* choosedPlaceholder = isDarkMode ? (self.darkThemePlaceholderSrc ?: self.placeholdSrc) : self.placeholdSrc; |
| |
| if ([WXUtility isBlankString:choosedPlaceholder]) { |
| return; |
| } |
| |
| WXLogDebug(@"Updating image, component:%@, placeholder:%@ ", self.ref, choosedPlaceholder); |
| NSString *newURL = [choosedPlaceholder copy]; |
| WX_REWRITE_URL(choosedPlaceholder, 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; |
| if (strongSelf == nil) { |
| return; |
| } |
| UIImage *viewImage = ((UIImageView *)strongSelf.view).image; |
| if (error) { |
| downloadFailedBlock(choosedPlaceholder, error); |
| if ([strongSelf isViewLoaded] && !viewImage) { |
| ((UIImageView *)(strongSelf.view)).image = nil; |
| [strongSelf readyToRender]; |
| } |
| return; |
| } |
| |
| NSString* currentPlaceholder = [strongSelf.weexInstance isDarkTheme] ? (strongSelf.darkThemePlaceholderSrc ?: strongSelf.placeholdSrc) : strongSelf.placeholdSrc; |
| if (![choosedPlaceholder isEqualToString:currentPlaceholder]) { |
| 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 |
| { |
| BOOL isDarkMode = [self.weexInstance isDarkTheme]; |
| NSString* choosedSrc = isDarkMode ? (self.darkThemeSrc ?: self.src) : self.src; |
| |
| if ([WXUtility isBlankString:choosedSrc]) { |
| WXLogError(@"image src is empty"); |
| return; |
| } |
| |
| WXLogDebug(@"Updating image:%@, component:%@", choosedSrc, self.ref); |
| NSDictionary *userInfo = @{@"imageQuality":@(self.imageQuality), @"imageSharp":@(self.imageSharp), @"blurRadius":@(self.blurRadius), @"instanceId":[self _safeInstanceId], @"pageURL": self.weexInstance.scriptURL ?: @""}; |
| NSString * newURL = [choosedSrc copy]; |
| WX_REWRITE_URL(choosedSrc, 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 == nil) { |
| return; |
| } |
| |
| 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(choosedSrc, error); |
| [strongSelf readyToRender]; |
| return ; |
| } |
| |
| NSString* currentSrc = [strongSelf.weexInstance isDarkTheme] ? (strongSelf.darkThemeSrc ?: strongSelf.src) : strongSelf.src; |
| if (![choosedSrc isEqualToString:currentSrc]) { |
| 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 |