//
|
// 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
|