From 281e5420b94f6b0cef25f190615c89bece5c1283 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 27 Oct 2019 02:48:54 +0800 Subject: [PATCH] Fix the issue that on watchOS, AnimatedImage when disappear, the CGImage decoding and animation does not stop issue. Increase performance --- SDWebImageSwiftUI/Classes/AnimatedImage.swift | 13 ++-- .../Classes/ObjC/SDAnimatedImageInterface.h | 3 + .../Classes/ObjC/SDAnimatedImageInterface.m | 77 ++++++++++++++----- 3 files changed, 68 insertions(+), 25 deletions(-) diff --git a/SDWebImageSwiftUI/Classes/AnimatedImage.swift b/SDWebImageSwiftUI/Classes/AnimatedImage.swift index 8475f930..00cbc61e 100644 --- a/SDWebImageSwiftUI/Classes/AnimatedImage.swift +++ b/SDWebImageSwiftUI/Classes/AnimatedImage.swift @@ -231,7 +231,7 @@ public struct AnimatedImage : PlatformViewRepresentable { if self.isAnimating != view.wrapped.animates { view.wrapped.animates = self.isAnimating } - #elseif os(iOS) || os(tvOS) + #else if self.isAnimating != view.wrapped.isAnimating { if self.isAnimating { view.wrapped.startAnimating() @@ -239,13 +239,14 @@ public struct AnimatedImage : PlatformViewRepresentable { view.wrapped.stopAnimating() } } - #elseif os(watchOS) - if self.isAnimating { - view.wrapped.startAnimating() - } else { - view.wrapped.stopAnimating() + #if os(watchOS) + // when onAppear/onDisappear, SwiftUI will call this `updateView(_:context:)` + // we use this to start/stop animation, implements `SDAnimatedImageView` like behavior + DispatchQueue.main.async { + view.wrapped.updateAnimation() } #endif + #endif configureView(view, context: context) layoutView(view, context: context) diff --git a/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.h b/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.h index 08e0b7a9..775cc782 100644 --- a/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.h +++ b/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.h @@ -14,9 +14,12 @@ NS_ASSUME_NONNULL_BEGIN /// Do not use this class directly in WatchKit or Storyboard. This class is implementation detail and will be removed in the future. @interface SDAnimatedImageInterface : WKInterfaceImage +@property (nonatomic, assign, getter=isAnimating, readonly) BOOL animating; + - (instancetype)init WK_AVAILABLE_WATCHOS_ONLY(6.0); - (void)setContentMode:(SDImageScaleMode)contentMode; - (void)setAnimationRepeatCount:(nullable NSNumber *)repeatCount; +- (void)updateAnimation; @end diff --git a/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.m b/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.m index 2fabf582..33e9ce63 100644 --- a/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.m +++ b/SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.m @@ -16,6 +16,8 @@ #pragma mark - SPI +#define kCGImageAnimationStatus_Uninitialized -1 + @protocol CALayerProtocol @property (nullable, strong) id contents; @property CGFloat contentsScale; @@ -24,6 +26,13 @@ @protocol CALayerProtocol @protocol UIViewProtocol @property (nonatomic, strong, readonly) id layer; @property (nonatomic, assign) SDImageScaleMode contentMode; +@property (nonatomic, readonly) id superview; +@property (nonatomic, readonly, copy) NSArray> *subviews; +@property (nonatomic, readonly) id window; +@property (nonatomic) CGFloat alpha; +@property (nonatomic, getter=isHidden) BOOL hidden; +@property (nonatomic, getter=isOpaque) BOOL opaque; + @end @interface WKInterfaceObject () @@ -44,6 +53,14 @@ @interface SDAnimatedImageStatus : NSObject @implementation SDAnimatedImageStatus +- (instancetype)init { + self = [super init]; + if (self) { + _animationStatus = kCGImageAnimationStatus_Uninitialized; + } + return self; +} + @end @interface SDAnimatedImageInterface () { @@ -59,6 +76,8 @@ @interface SDAnimatedImageInterface () { @property (nonatomic, assign) CGFloat animatedImageScale; @property (nonatomic, strong) SDAnimatedImageStatus *currentStatus; @property (nonatomic, strong) NSNumber *animationRepeatCount; +@property (nonatomic, assign, getter=isAnimatedFormat) BOOL animatedFormat; +@property (nonatomic, assign, getter=isAnimating) BOOL animating; @end @@ -105,6 +124,8 @@ - (void)setImage:(UIImage *)image { } _image = image; + // Stop animating + [self stopBuiltInAnimation]; // Reset all value [self resetAnimatedImage]; @@ -126,15 +147,29 @@ - (void)setImage:(UIImage *)image { NSData *animatedImageData = animatedImage.animatedImageData; SDImageFormat format = [NSData sd_imageFormatForImageData:animatedImageData]; if (format == SDImageFormatGIF || format == SDImageFormatPNG) { - [self startBuiltInAnimationWithImage:animatedImage]; + self.animatedFormat = YES; + [self startBuiltInAnimation]; + } else { + self.animatedFormat = NO; + [self stopBuiltInAnimation]; } - - // Update should animate - [self updateShouldAnimate]; } } -- (void)startBuiltInAnimationWithImage:(UIImage *)animatedImage { +- (void)updateAnimation { + [self updateShouldAnimate]; + if (self.currentStatus.shouldAnimate) { + [self startBuiltInAnimation]; + } else { + [self stopBuiltInAnimation]; + } +} + +- (void)startBuiltInAnimation { + if (self.currentStatus && self.currentStatus.animationStatus == 0) { + return; + } + UIImage *animatedImage = self.animatedImage; NSData *animatedImageData = animatedImage.animatedImageData; NSUInteger maxLoopCount; if (self.animationRepeatCount != nil) { @@ -148,7 +183,7 @@ - (void)startBuiltInAnimationWithImage:(UIImage *)animatedImage maxLoopCount = ((__bridge NSNumber *)kCFNumberPositiveInfinity).unsignedIntegerValue - 1; } NSDictionary *options = @{(__bridge NSString *)kCGImageAnimationLoopCount : @(maxLoopCount)}; - SDAnimatedImageStatus *status = [SDAnimatedImageStatus new]; + SDAnimatedImageStatus *status = [[SDAnimatedImageStatus alloc] init]; status.shouldAnimate = YES; __weak typeof(self) wself = self; status.animationStatus = CGAnimateImageDataWithBlock((__bridge CFDataRef)animatedImageData, (__bridge CFDictionaryRef)options, ^(size_t index, CGImageRef _Nonnull imageRef, bool * _Nonnull stop) { @@ -171,6 +206,11 @@ - (void)startBuiltInAnimationWithImage:(UIImage *)animatedImage self.currentStatus = status; } +- (void)stopBuiltInAnimation { + self.currentStatus.shouldAnimate = NO; + self.currentStatus.animationStatus = kCGImageAnimationStatus_Uninitialized; +} + - (void)displayLayer { if (self.currentFrame) { id layer = [self _interfaceView].layer; @@ -184,44 +224,43 @@ - (void)resetAnimatedImage self.animatedImage = nil; self.totalFrameCount = 0; self.totalLoopCount = 0; - // reset current state - self.currentStatus.shouldAnimate = NO; - self.currentStatus = nil; - [self resetCurrentFrameIndex]; - self.animatedImageScale = 1; -} - -- (void)resetCurrentFrameIndex -{ self.currentFrame = nil; self.currentFrameIndex = 0; self.currentLoopCount = 0; + self.animatedImageScale = 1; + self.animatedFormat = NO; + self.currentStatus = nil; } - (void)updateShouldAnimate { - self.currentStatus.shouldAnimate = self.animatedImage && self.totalFrameCount > 1; + id view = [self _interfaceView]; + BOOL isVisible = view.window && view.superview && ![view isHidden] && view.alpha > 0.0; + self.currentStatus.shouldAnimate = self.animatedImage && self.totalFrameCount > 1 && self.isAnimatedFormat && isVisible; } - (void)startAnimating { + self.animating = YES; if (self.animatedImage) { - self.currentStatus.shouldAnimate = YES; + [self startBuiltInAnimation]; } else if (_image.images.count > 0) { [super startAnimating]; } } - (void)startAnimatingWithImagesInRange:(NSRange)imageRange duration:(NSTimeInterval)duration repeatCount:(NSInteger)repeatCount { + self.animating = YES; if (self.animatedImage) { - self.currentStatus.shouldAnimate = YES; + [self startBuiltInAnimation]; } else if (_image.images.count > 0) { [super startAnimatingWithImagesInRange:imageRange duration:duration repeatCount:repeatCount]; } } - (void)stopAnimating { + self.animating = NO; if (self.animatedImage) { - self.currentStatus.shouldAnimate = NO; + [self stopBuiltInAnimation]; } else if (_image.images.count > 0) { [super stopAnimating]; }