Skip to content

Fix that AnimatedImage on watchOS, does not stop animating when disappear and cause performance issue #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions SDWebImageSwiftUI/Classes/AnimatedImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,21 +231,22 @@ 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()
} else {
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)
Expand Down
3 changes: 3 additions & 0 deletions SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
77 changes: 58 additions & 19 deletions SDWebImageSwiftUI/Classes/ObjC/SDAnimatedImageInterface.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

#pragma mark - SPI

#define kCGImageAnimationStatus_Uninitialized -1

@protocol CALayerProtocol <NSObject>
@property (nullable, strong) id contents;
@property CGFloat contentsScale;
Expand All @@ -24,6 +26,13 @@ @protocol CALayerProtocol <NSObject>
@protocol UIViewProtocol <NSObject>
@property (nonatomic, strong, readonly) id<CALayerProtocol> layer;
@property (nonatomic, assign) SDImageScaleMode contentMode;
@property (nonatomic, readonly) id<UIViewProtocol> superview;
@property (nonatomic, readonly, copy) NSArray<id<UIViewProtocol>> *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 ()
Expand All @@ -44,6 +53,14 @@ @interface SDAnimatedImageStatus : NSObject

@implementation SDAnimatedImageStatus

- (instancetype)init {
self = [super init];
if (self) {
_animationStatus = kCGImageAnimationStatus_Uninitialized;
}
return self;
}

@end

@interface SDAnimatedImageInterface () {
Expand All @@ -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

Expand Down Expand Up @@ -105,6 +124,8 @@ - (void)setImage:(UIImage *)image {
}
_image = image;

// Stop animating
[self stopBuiltInAnimation];
// Reset all value
[self resetAnimatedImage];

Expand All @@ -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<SDAnimatedImage> *)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<SDAnimatedImage> *animatedImage = self.animatedImage;
NSData *animatedImageData = animatedImage.animatedImageData;
NSUInteger maxLoopCount;
if (self.animationRepeatCount != nil) {
Expand All @@ -148,7 +183,7 @@ - (void)startBuiltInAnimationWithImage:(UIImage<SDAnimatedImage> *)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) {
Expand All @@ -171,6 +206,11 @@ - (void)startBuiltInAnimationWithImage:(UIImage<SDAnimatedImage> *)animatedImage
self.currentStatus = status;
}

- (void)stopBuiltInAnimation {
self.currentStatus.shouldAnimate = NO;
self.currentStatus.animationStatus = kCGImageAnimationStatus_Uninitialized;
}

- (void)displayLayer {
if (self.currentFrame) {
id<CALayerProtocol> layer = [self _interfaceView].layer;
Expand All @@ -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<UIViewProtocol> view = [self _interfaceView];
BOOL isVisible = view.window && view.superview && ![view isHidden] && view.alpha > 0.0;
self.currentStatus.shouldAnimate = self.isAnimating && self.animatedImage && self.isAnimatedFormat && self.totalFrameCount > 1 && 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];
}
Expand Down