Skip to content

Commit

Permalink
Fix the issue that on watchOS, AnimatedImage when disappear, the CGIm…
Browse files Browse the repository at this point in the history
…age decoding and animation does not stop issue. Increase performance
  • Loading branch information
dreampiggy committed Oct 26, 2019
1 parent d9c1beb commit 281e542
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 25 deletions.
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.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];
}
Expand Down

0 comments on commit 281e542

Please sign in to comment.