New file |
| | |
| | | // |
| | | // YYAnimatedImageView.m |
| | | // YYImage <https://github.com/ibireme/YYImage> |
| | | // |
| | | // Created by ibireme on 14/10/19. |
| | | // Copyright (c) 2015 ibireme. |
| | | // |
| | | // This source code is licensed under the MIT-style license found in the |
| | | // LICENSE file in the root directory of this source tree. |
| | | // |
| | | |
| | | #import "YYAnimatedImageView.h" |
| | | #import "YYImageCoder.h" |
| | | #import <pthread.h> |
| | | #import <mach/mach.h> |
| | | |
| | | |
| | | #define BUFFER_SIZE (10 * 1024 * 1024) // 10MB (minimum memory buffer size) |
| | | |
| | | #define LOCK(...) dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER); \ |
| | | __VA_ARGS__; \ |
| | | dispatch_semaphore_signal(self->_lock); |
| | | |
| | | #define LOCK_VIEW(...) dispatch_semaphore_wait(view->_lock, DISPATCH_TIME_FOREVER); \ |
| | | __VA_ARGS__; \ |
| | | dispatch_semaphore_signal(view->_lock); |
| | | |
| | | |
| | | static int64_t _YYDeviceMemoryTotal() { |
| | | int64_t mem = [[NSProcessInfo processInfo] physicalMemory]; |
| | | if (mem < -1) mem = -1; |
| | | return mem; |
| | | } |
| | | |
| | | static int64_t _YYDeviceMemoryFree() { |
| | | mach_port_t host_port = mach_host_self(); |
| | | mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t); |
| | | vm_size_t page_size; |
| | | vm_statistics_data_t vm_stat; |
| | | kern_return_t kern; |
| | | |
| | | kern = host_page_size(host_port, &page_size); |
| | | if (kern != KERN_SUCCESS) return -1; |
| | | kern = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); |
| | | if (kern != KERN_SUCCESS) return -1; |
| | | return vm_stat.free_count * page_size; |
| | | } |
| | | |
| | | /** |
| | | A proxy used to hold a weak object. |
| | | It can be used to avoid retain cycles, such as the target in NSTimer or CADisplayLink. |
| | | */ |
| | | @interface _YYImageWeakProxy : NSProxy |
| | | @property (nonatomic, weak, readonly) id target; |
| | | - (instancetype)initWithTarget:(id)target; |
| | | + (instancetype)proxyWithTarget:(id)target; |
| | | @end |
| | | |
| | | @implementation _YYImageWeakProxy |
| | | - (instancetype)initWithTarget:(id)target { |
| | | _target = target; |
| | | return self; |
| | | } |
| | | + (instancetype)proxyWithTarget:(id)target { |
| | | return [[_YYImageWeakProxy alloc] initWithTarget:target]; |
| | | } |
| | | - (id)forwardingTargetForSelector:(SEL)selector { |
| | | return _target; |
| | | } |
| | | - (void)forwardInvocation:(NSInvocation *)invocation { |
| | | void *null = NULL; |
| | | [invocation setReturnValue:&null]; |
| | | } |
| | | - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { |
| | | return [NSObject instanceMethodSignatureForSelector:@selector(init)]; |
| | | } |
| | | - (BOOL)respondsToSelector:(SEL)aSelector { |
| | | return [_target respondsToSelector:aSelector]; |
| | | } |
| | | - (BOOL)isEqual:(id)object { |
| | | return [_target isEqual:object]; |
| | | } |
| | | - (NSUInteger)hash { |
| | | return [_target hash]; |
| | | } |
| | | - (Class)superclass { |
| | | return [_target superclass]; |
| | | } |
| | | - (Class)class { |
| | | return [_target class]; |
| | | } |
| | | - (BOOL)isKindOfClass:(Class)aClass { |
| | | return [_target isKindOfClass:aClass]; |
| | | } |
| | | - (BOOL)isMemberOfClass:(Class)aClass { |
| | | return [_target isMemberOfClass:aClass]; |
| | | } |
| | | - (BOOL)conformsToProtocol:(Protocol *)aProtocol { |
| | | return [_target conformsToProtocol:aProtocol]; |
| | | } |
| | | - (BOOL)isProxy { |
| | | return YES; |
| | | } |
| | | - (NSString *)description { |
| | | return [_target description]; |
| | | } |
| | | - (NSString *)debugDescription { |
| | | return [_target debugDescription]; |
| | | } |
| | | @end |
| | | |
| | | |
| | | |
| | | |
| | | typedef NS_ENUM(NSUInteger, YYAnimatedImageType) { |
| | | YYAnimatedImageTypeNone = 0, |
| | | YYAnimatedImageTypeImage, |
| | | YYAnimatedImageTypeHighlightedImage, |
| | | YYAnimatedImageTypeImages, |
| | | YYAnimatedImageTypeHighlightedImages, |
| | | }; |
| | | |
| | | @interface YYAnimatedImageView() { |
| | | @package |
| | | UIImage <YYAnimatedImage> *_curAnimatedImage; |
| | | |
| | | dispatch_once_t _onceToken; |
| | | dispatch_semaphore_t _lock; ///< lock for _buffer |
| | | NSOperationQueue *_requestQueue; ///< image request queue, serial |
| | | |
| | | CADisplayLink *_link; ///< ticker for change frame |
| | | NSTimeInterval _time; ///< time after last frame |
| | | |
| | | UIImage *_curFrame; ///< current frame to display |
| | | NSUInteger _curIndex; ///< current frame index (from 0) |
| | | NSUInteger _totalFrameCount; ///< total frame count |
| | | |
| | | BOOL _loopEnd; ///< whether the loop is end. |
| | | NSUInteger _curLoop; ///< current loop count (from 0) |
| | | NSUInteger _totalLoop; ///< total loop count, 0 means infinity |
| | | |
| | | NSMutableDictionary *_buffer; ///< frame buffer |
| | | BOOL _bufferMiss; ///< whether miss frame on last opportunity |
| | | NSUInteger _maxBufferCount; ///< maximum buffer count |
| | | NSInteger _incrBufferCount; ///< current allowed buffer count (will increase by step) |
| | | |
| | | CGRect _curContentsRect; |
| | | BOOL _curImageHasContentsRect; ///< image has implementated "animatedImageContentsRectAtIndex:" |
| | | } |
| | | @property (nonatomic, readwrite) BOOL currentIsPlayingAnimation; |
| | | - (void)calcMaxBufferCount; |
| | | @end |
| | | |
| | | /// An operation for image fetch |
| | | @interface _YYAnimatedImageViewFetchOperation : NSOperation |
| | | @property (nonatomic, weak) YYAnimatedImageView *view; |
| | | @property (nonatomic, assign) NSUInteger nextIndex; |
| | | @property (nonatomic, strong) UIImage <YYAnimatedImage> *curImage; |
| | | @end |
| | | |
| | | @implementation _YYAnimatedImageViewFetchOperation |
| | | - (void)main { |
| | | __strong YYAnimatedImageView *view = _view; |
| | | if (!view) return; |
| | | if ([self isCancelled]) return; |
| | | view->_incrBufferCount++; |
| | | if (view->_incrBufferCount == 0) [view calcMaxBufferCount]; |
| | | if (view->_incrBufferCount > (NSInteger)view->_maxBufferCount) { |
| | | view->_incrBufferCount = view->_maxBufferCount; |
| | | } |
| | | NSUInteger idx = _nextIndex; |
| | | NSUInteger max = view->_incrBufferCount < 1 ? 1 : view->_incrBufferCount; |
| | | NSUInteger total = view->_totalFrameCount; |
| | | view = nil; |
| | | |
| | | for (int i = 0; i < max; i++, idx++) { |
| | | @autoreleasepool { |
| | | if (idx >= total) idx = 0; |
| | | if ([self isCancelled]) break; |
| | | __strong YYAnimatedImageView *view = _view; |
| | | if (!view) break; |
| | | LOCK_VIEW(BOOL miss = (view->_buffer[@(idx)] == nil)); |
| | | |
| | | if (miss) { |
| | | UIImage *img = [_curImage animatedImageFrameAtIndex:idx]; |
| | | img = img.yy_imageByDecoded; |
| | | if ([self isCancelled]) break; |
| | | LOCK_VIEW(view->_buffer[@(idx)] = img ? img : [NSNull null]); |
| | | view = nil; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | @end |
| | | |
| | | @implementation YYAnimatedImageView |
| | | |
| | | - (instancetype)init { |
| | | self = [super init]; |
| | | _runloopMode = NSRunLoopCommonModes; |
| | | _autoPlayAnimatedImage = YES; |
| | | return self; |
| | | } |
| | | |
| | | - (instancetype)initWithFrame:(CGRect)frame { |
| | | self = [super initWithFrame:frame]; |
| | | _runloopMode = NSRunLoopCommonModes; |
| | | _autoPlayAnimatedImage = YES; |
| | | return self; |
| | | } |
| | | |
| | | - (instancetype)initWithImage:(UIImage *)image { |
| | | self = [super init]; |
| | | _runloopMode = NSRunLoopCommonModes; |
| | | _autoPlayAnimatedImage = YES; |
| | | self.frame = (CGRect) {CGPointZero, image.size }; |
| | | self.image = image; |
| | | return self; |
| | | } |
| | | |
| | | - (instancetype)initWithImage:(UIImage *)image highlightedImage:(UIImage *)highlightedImage { |
| | | self = [super init]; |
| | | _runloopMode = NSRunLoopCommonModes; |
| | | _autoPlayAnimatedImage = YES; |
| | | CGSize size = image ? image.size : highlightedImage.size; |
| | | self.frame = (CGRect) {CGPointZero, size }; |
| | | self.image = image; |
| | | self.highlightedImage = highlightedImage; |
| | | return self; |
| | | } |
| | | |
| | | // init the animated params. |
| | | - (void)resetAnimated { |
| | | dispatch_once(&_onceToken, ^{ |
| | | _lock = dispatch_semaphore_create(1); |
| | | _buffer = [NSMutableDictionary new]; |
| | | _requestQueue = [[NSOperationQueue alloc] init]; |
| | | _requestQueue.maxConcurrentOperationCount = 1; |
| | | _link = [CADisplayLink displayLinkWithTarget:[_YYImageWeakProxy proxyWithTarget:self] selector:@selector(step:)]; |
| | | if (_runloopMode) { |
| | | [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode]; |
| | | } |
| | | _link.paused = YES; |
| | | |
| | | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; |
| | | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; |
| | | }); |
| | | |
| | | [_requestQueue cancelAllOperations]; |
| | | LOCK( |
| | | if (_buffer.count) { |
| | | NSMutableDictionary *holder = _buffer; |
| | | _buffer = [NSMutableDictionary new]; |
| | | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ |
| | | // Capture the dictionary to global queue, |
| | | // release these images in background to avoid blocking UI thread. |
| | | [holder class]; |
| | | }); |
| | | } |
| | | ); |
| | | _link.paused = YES; |
| | | _time = 0; |
| | | if (_curIndex != 0) { |
| | | [self willChangeValueForKey:@"currentAnimatedImageIndex"]; |
| | | _curIndex = 0; |
| | | [self didChangeValueForKey:@"currentAnimatedImageIndex"]; |
| | | } |
| | | _curAnimatedImage = nil; |
| | | _curFrame = nil; |
| | | _curLoop = 0; |
| | | _totalLoop = 0; |
| | | _totalFrameCount = 1; |
| | | _loopEnd = NO; |
| | | _bufferMiss = NO; |
| | | _incrBufferCount = 0; |
| | | } |
| | | |
| | | - (void)setImage:(UIImage *)image { |
| | | if (self.image == image) return; |
| | | [self setImage:image withType:YYAnimatedImageTypeImage]; |
| | | } |
| | | |
| | | - (void)setHighlightedImage:(UIImage *)highlightedImage { |
| | | if (self.highlightedImage == highlightedImage) return; |
| | | [self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage]; |
| | | } |
| | | |
| | | - (void)setAnimationImages:(NSArray *)animationImages { |
| | | if (self.animationImages == animationImages) return; |
| | | [self setImage:animationImages withType:YYAnimatedImageTypeImages]; |
| | | } |
| | | |
| | | - (void)setHighlightedAnimationImages:(NSArray *)highlightedAnimationImages { |
| | | if (self.highlightedAnimationImages == highlightedAnimationImages) return; |
| | | [self setImage:highlightedAnimationImages withType:YYAnimatedImageTypeHighlightedImages]; |
| | | } |
| | | |
| | | - (void)setHighlighted:(BOOL)highlighted { |
| | | [super setHighlighted:highlighted]; |
| | | if (_link) [self resetAnimated]; |
| | | [self imageChanged]; |
| | | } |
| | | |
| | | - (id)imageForType:(YYAnimatedImageType)type { |
| | | switch (type) { |
| | | case YYAnimatedImageTypeNone: return nil; |
| | | case YYAnimatedImageTypeImage: return self.image; |
| | | case YYAnimatedImageTypeHighlightedImage: return self.highlightedImage; |
| | | case YYAnimatedImageTypeImages: return self.animationImages; |
| | | case YYAnimatedImageTypeHighlightedImages: return self.highlightedAnimationImages; |
| | | } |
| | | return nil; |
| | | } |
| | | |
| | | - (YYAnimatedImageType)currentImageType { |
| | | YYAnimatedImageType curType = YYAnimatedImageTypeNone; |
| | | if (self.highlighted) { |
| | | if (self.highlightedAnimationImages.count) curType = YYAnimatedImageTypeHighlightedImages; |
| | | else if (self.highlightedImage) curType = YYAnimatedImageTypeHighlightedImage; |
| | | } |
| | | if (curType == YYAnimatedImageTypeNone) { |
| | | if (self.animationImages.count) curType = YYAnimatedImageTypeImages; |
| | | else if (self.image) curType = YYAnimatedImageTypeImage; |
| | | } |
| | | return curType; |
| | | } |
| | | |
| | | - (void)setImage:(id)image withType:(YYAnimatedImageType)type { |
| | | [self stopAnimating]; |
| | | if (_link) [self resetAnimated]; |
| | | _curFrame = nil; |
| | | switch (type) { |
| | | case YYAnimatedImageTypeNone: break; |
| | | case YYAnimatedImageTypeImage: super.image = image; break; |
| | | case YYAnimatedImageTypeHighlightedImage: super.highlightedImage = image; break; |
| | | case YYAnimatedImageTypeImages: super.animationImages = image; break; |
| | | case YYAnimatedImageTypeHighlightedImages: super.highlightedAnimationImages = image; break; |
| | | } |
| | | [self imageChanged]; |
| | | } |
| | | |
| | | - (void)imageChanged { |
| | | YYAnimatedImageType newType = [self currentImageType]; |
| | | id newVisibleImage = [self imageForType:newType]; |
| | | NSUInteger newImageFrameCount = 0; |
| | | BOOL hasContentsRect = NO; |
| | | if ([newVisibleImage isKindOfClass:[UIImage class]] && |
| | | [newVisibleImage conformsToProtocol:@protocol(YYAnimatedImage)]) { |
| | | newImageFrameCount = ((UIImage<YYAnimatedImage> *) newVisibleImage).animatedImageFrameCount; |
| | | if (newImageFrameCount > 1) { |
| | | hasContentsRect = [((UIImage<YYAnimatedImage> *) newVisibleImage) respondsToSelector:@selector(animatedImageContentsRectAtIndex:)]; |
| | | } |
| | | } |
| | | if (!hasContentsRect && _curImageHasContentsRect) { |
| | | if (!CGRectEqualToRect(self.layer.contentsRect, CGRectMake(0, 0, 1, 1)) ) { |
| | | [CATransaction begin]; |
| | | [CATransaction setDisableActions:YES]; |
| | | self.layer.contentsRect = CGRectMake(0, 0, 1, 1); |
| | | [CATransaction commit]; |
| | | } |
| | | } |
| | | _curImageHasContentsRect = hasContentsRect; |
| | | if (hasContentsRect) { |
| | | CGRect rect = [((UIImage<YYAnimatedImage> *) newVisibleImage) animatedImageContentsRectAtIndex:0]; |
| | | [self setContentsRect:rect forImage:newVisibleImage]; |
| | | } |
| | | |
| | | if (newImageFrameCount > 1) { |
| | | [self resetAnimated]; |
| | | _curAnimatedImage = newVisibleImage; |
| | | _curFrame = newVisibleImage; |
| | | _totalLoop = _curAnimatedImage.animatedImageLoopCount; |
| | | _totalFrameCount = _curAnimatedImage.animatedImageFrameCount; |
| | | [self calcMaxBufferCount]; |
| | | } |
| | | [self setNeedsDisplay]; |
| | | [self didMoved]; |
| | | } |
| | | |
| | | // dynamically adjust buffer size for current memory. |
| | | - (void)calcMaxBufferCount { |
| | | int64_t bytes = (int64_t)_curAnimatedImage.animatedImageBytesPerFrame; |
| | | if (bytes == 0) bytes = 1024; |
| | | |
| | | int64_t total = _YYDeviceMemoryTotal(); |
| | | int64_t free = _YYDeviceMemoryFree(); |
| | | int64_t max = MIN(total * 0.2, free * 0.6); |
| | | max = MAX(max, BUFFER_SIZE); |
| | | if (_maxBufferSize) max = max > _maxBufferSize ? _maxBufferSize : max; |
| | | double maxBufferCount = (double)max / (double)bytes; |
| | | if (maxBufferCount < 1) maxBufferCount = 1; |
| | | else if (maxBufferCount > 512) maxBufferCount = 512; |
| | | _maxBufferCount = maxBufferCount; |
| | | } |
| | | |
| | | - (void)dealloc { |
| | | [_requestQueue cancelAllOperations]; |
| | | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; |
| | | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; |
| | | [_link invalidate]; |
| | | } |
| | | |
| | | - (BOOL)isAnimating { |
| | | return self.currentIsPlayingAnimation; |
| | | } |
| | | |
| | | - (void)stopAnimating { |
| | | [super stopAnimating]; |
| | | [_requestQueue cancelAllOperations]; |
| | | _link.paused = YES; |
| | | self.currentIsPlayingAnimation = NO; |
| | | } |
| | | |
| | | - (void)startAnimating { |
| | | YYAnimatedImageType type = [self currentImageType]; |
| | | if (type == YYAnimatedImageTypeImages || type == YYAnimatedImageTypeHighlightedImages) { |
| | | NSArray *images = [self imageForType:type]; |
| | | if (images.count > 0) { |
| | | [super startAnimating]; |
| | | self.currentIsPlayingAnimation = YES; |
| | | } |
| | | } else { |
| | | if (_curAnimatedImage && _link.paused) { |
| | | _curLoop = 0; |
| | | _loopEnd = NO; |
| | | _link.paused = NO; |
| | | self.currentIsPlayingAnimation = YES; |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (void)didReceiveMemoryWarning:(NSNotification *)notification { |
| | | [_requestQueue cancelAllOperations]; |
| | | [_requestQueue addOperationWithBlock: ^{ |
| | | _incrBufferCount = -60 - (int)(arc4random() % 120); // about 1~3 seconds to grow back.. |
| | | NSNumber *next = @((_curIndex + 1) % _totalFrameCount); |
| | | LOCK( |
| | | NSArray * keys = _buffer.allKeys; |
| | | for (NSNumber * key in keys) { |
| | | if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation |
| | | [_buffer removeObjectForKey:key]; |
| | | } |
| | | } |
| | | )//LOCK |
| | | }]; |
| | | } |
| | | |
| | | - (void)didEnterBackground:(NSNotification *)notification { |
| | | [_requestQueue cancelAllOperations]; |
| | | NSNumber *next = @((_curIndex + 1) % _totalFrameCount); |
| | | LOCK( |
| | | NSArray * keys = _buffer.allKeys; |
| | | for (NSNumber * key in keys) { |
| | | if (![key isEqualToNumber:next]) { // keep the next frame for smoothly animation |
| | | [_buffer removeObjectForKey:key]; |
| | | } |
| | | } |
| | | )//LOCK |
| | | } |
| | | |
| | | - (void)step:(CADisplayLink *)link { |
| | | UIImage <YYAnimatedImage> *image = _curAnimatedImage; |
| | | NSMutableDictionary *buffer = _buffer; |
| | | UIImage *bufferedImage = nil; |
| | | NSUInteger nextIndex = (_curIndex + 1) % _totalFrameCount; |
| | | BOOL bufferIsFull = NO; |
| | | |
| | | if (!image) return; |
| | | if (_loopEnd) { // view will keep in last frame |
| | | [self stopAnimating]; |
| | | return; |
| | | } |
| | | |
| | | NSTimeInterval delay = 0; |
| | | if (!_bufferMiss) { |
| | | _time += link.duration; |
| | | delay = [image animatedImageDurationAtIndex:_curIndex]; |
| | | if (_time < delay) return; |
| | | _time -= delay; |
| | | if (nextIndex == 0) { |
| | | _curLoop++; |
| | | if (_curLoop >= _totalLoop && _totalLoop != 0) { |
| | | _loopEnd = YES; |
| | | [self stopAnimating]; |
| | | [self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep |
| | | return; // stop at last frame |
| | | } |
| | | } |
| | | delay = [image animatedImageDurationAtIndex:nextIndex]; |
| | | if (_time > delay) _time = delay; // do not jump over frame |
| | | } |
| | | LOCK( |
| | | bufferedImage = buffer[@(nextIndex)]; |
| | | if (bufferedImage) { |
| | | if ((int)_incrBufferCount < _totalFrameCount) { |
| | | [buffer removeObjectForKey:@(nextIndex)]; |
| | | } |
| | | [self willChangeValueForKey:@"currentAnimatedImageIndex"]; |
| | | _curIndex = nextIndex; |
| | | [self didChangeValueForKey:@"currentAnimatedImageIndex"]; |
| | | _curFrame = bufferedImage == (id)[NSNull null] ? nil : bufferedImage; |
| | | if (_curImageHasContentsRect) { |
| | | _curContentsRect = [image animatedImageContentsRectAtIndex:_curIndex]; |
| | | [self setContentsRect:_curContentsRect forImage:_curFrame]; |
| | | } |
| | | nextIndex = (_curIndex + 1) % _totalFrameCount; |
| | | _bufferMiss = NO; |
| | | if (buffer.count == _totalFrameCount) { |
| | | bufferIsFull = YES; |
| | | } |
| | | } else { |
| | | _bufferMiss = YES; |
| | | } |
| | | )//LOCK |
| | | |
| | | if (!_bufferMiss) { |
| | | [self.layer setNeedsDisplay]; // let system call `displayLayer:` before runloop sleep |
| | | } |
| | | |
| | | if (!bufferIsFull && _requestQueue.operationCount == 0) { // if some work not finished, wait for next opportunity |
| | | _YYAnimatedImageViewFetchOperation *operation = [_YYAnimatedImageViewFetchOperation new]; |
| | | operation.view = self; |
| | | operation.nextIndex = nextIndex; |
| | | operation.curImage = image; |
| | | [_requestQueue addOperation:operation]; |
| | | } |
| | | } |
| | | |
| | | - (void)displayLayer:(CALayer *)layer { |
| | | if (_curFrame) { |
| | | layer.contents = (__bridge id)_curFrame.CGImage; |
| | | } |
| | | } |
| | | |
| | | - (void)setContentsRect:(CGRect)rect forImage:(UIImage *)image{ |
| | | CGRect layerRect = CGRectMake(0, 0, 1, 1); |
| | | if (image) { |
| | | CGSize imageSize = image.size; |
| | | if (imageSize.width > 0.01 && imageSize.height > 0.01) { |
| | | layerRect.origin.x = rect.origin.x / imageSize.width; |
| | | layerRect.origin.y = rect.origin.y / imageSize.height; |
| | | layerRect.size.width = rect.size.width / imageSize.width; |
| | | layerRect.size.height = rect.size.height / imageSize.height; |
| | | layerRect = CGRectIntersection(layerRect, CGRectMake(0, 0, 1, 1)); |
| | | if (CGRectIsNull(layerRect) || CGRectIsEmpty(layerRect)) { |
| | | layerRect = CGRectMake(0, 0, 1, 1); |
| | | } |
| | | } |
| | | } |
| | | [CATransaction begin]; |
| | | [CATransaction setDisableActions:YES]; |
| | | self.layer.contentsRect = layerRect; |
| | | [CATransaction commit]; |
| | | } |
| | | |
| | | - (void)didMoved { |
| | | if (self.autoPlayAnimatedImage) { |
| | | if(self.superview && self.window) { |
| | | [self startAnimating]; |
| | | } else { |
| | | [self stopAnimating]; |
| | | } |
| | | } |
| | | } |
| | | |
| | | - (void)didMoveToWindow { |
| | | [super didMoveToWindow]; |
| | | [self didMoved]; |
| | | } |
| | | |
| | | - (void)didMoveToSuperview { |
| | | [super didMoveToSuperview]; |
| | | [self didMoved]; |
| | | } |
| | | |
| | | - (void)setCurrentAnimatedImageIndex:(NSUInteger)currentAnimatedImageIndex { |
| | | if (!_curAnimatedImage) return; |
| | | if (currentAnimatedImageIndex >= _curAnimatedImage.animatedImageFrameCount) return; |
| | | if (_curIndex == currentAnimatedImageIndex) return; |
| | | |
| | | void (^block)() = ^{ |
| | | LOCK( |
| | | [_requestQueue cancelAllOperations]; |
| | | [_buffer removeAllObjects]; |
| | | [self willChangeValueForKey:@"currentAnimatedImageIndex"]; |
| | | _curIndex = currentAnimatedImageIndex; |
| | | [self didChangeValueForKey:@"currentAnimatedImageIndex"]; |
| | | _curFrame = [_curAnimatedImage animatedImageFrameAtIndex:_curIndex]; |
| | | if (_curImageHasContentsRect) { |
| | | _curContentsRect = [_curAnimatedImage animatedImageContentsRectAtIndex:_curIndex]; |
| | | } |
| | | _time = 0; |
| | | _loopEnd = NO; |
| | | _bufferMiss = NO; |
| | | [self.layer setNeedsDisplay]; |
| | | )//LOCK |
| | | }; |
| | | |
| | | if (pthread_main_np()) { |
| | | block(); |
| | | } else { |
| | | dispatch_async(dispatch_get_main_queue(), block); |
| | | } |
| | | } |
| | | |
| | | - (NSUInteger)currentAnimatedImageIndex { |
| | | return _curIndex; |
| | | } |
| | | |
| | | - (void)setRunloopMode:(NSString *)runloopMode { |
| | | if ([_runloopMode isEqual:runloopMode]) return; |
| | | if (_link) { |
| | | if (_runloopMode) { |
| | | [_link removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:_runloopMode]; |
| | | } |
| | | if (runloopMode.length) { |
| | | [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:runloopMode]; |
| | | } |
| | | } |
| | | _runloopMode = runloopMode.copy; |
| | | } |
| | | |
| | | #pragma mark - Override NSObject(NSKeyValueObservingCustomization) |
| | | |
| | | + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { |
| | | if ([key isEqualToString:@"currentAnimatedImageIndex"]) { |
| | | return NO; |
| | | } |
| | | return [super automaticallyNotifiesObserversForKey:key]; |
| | | } |
| | | |
| | | #pragma mark - NSCoding |
| | | |
| | | - (instancetype)initWithCoder:(NSCoder *)aDecoder { |
| | | self = [super initWithCoder:aDecoder]; |
| | | _runloopMode = [aDecoder decodeObjectForKey:@"runloopMode"]; |
| | | if (_runloopMode.length == 0) _runloopMode = NSRunLoopCommonModes; |
| | | if ([aDecoder containsValueForKey:@"autoPlayAnimatedImage"]) { |
| | | _autoPlayAnimatedImage = [aDecoder decodeBoolForKey:@"autoPlayAnimatedImage"]; |
| | | } else { |
| | | _autoPlayAnimatedImage = YES; |
| | | } |
| | | |
| | | UIImage *image = [aDecoder decodeObjectForKey:@"YYAnimatedImage"]; |
| | | UIImage *highlightedImage = [aDecoder decodeObjectForKey:@"YYHighlightedAnimatedImage"]; |
| | | if (image) { |
| | | self.image = image; |
| | | [self setImage:image withType:YYAnimatedImageTypeImage]; |
| | | } |
| | | if (highlightedImage) { |
| | | self.highlightedImage = highlightedImage; |
| | | [self setImage:highlightedImage withType:YYAnimatedImageTypeHighlightedImage]; |
| | | } |
| | | return self; |
| | | } |
| | | |
| | | - (void)encodeWithCoder:(NSCoder *)aCoder { |
| | | [super encodeWithCoder:aCoder]; |
| | | [aCoder encodeObject:_runloopMode forKey:@"runloopMode"]; |
| | | [aCoder encodeBool:_autoPlayAnimatedImage forKey:@"autoPlayAnimatedImage"]; |
| | | |
| | | BOOL ani, multi; |
| | | ani = [self.image conformsToProtocol:@protocol(YYAnimatedImage)]; |
| | | multi = (ani && ((UIImage <YYAnimatedImage> *)self.image).animatedImageFrameCount > 1); |
| | | if (multi) [aCoder encodeObject:self.image forKey:@"YYAnimatedImage"]; |
| | | |
| | | ani = [self.highlightedImage conformsToProtocol:@protocol(YYAnimatedImage)]; |
| | | multi = (ani && ((UIImage <YYAnimatedImage> *)self.highlightedImage).animatedImageFrameCount > 1); |
| | | if (multi) [aCoder encodeObject:self.highlightedImage forKey:@"YYHighlightedAnimatedImage"]; |
| | | } |
| | | |
| | | @end |