iOS solution to load images with animation only if

2019-07-10 07:23发布

问题:

I've been struggling with this problem for a couple of days now. Previously I used AFNetworking category to load and cache images, but it doesn't provide a cache type in it's callbacks. So I used to keep track in each controller which images have been already loaded. I looked through SDWebImage and it provides exactly what I was looking for - SDImageCacheType.

Now i'm trying to come up with an easy to use (hopefully a one-liner) solution to load images in table and collection views with animation if they are downloaded or come from the disk cache. Basically these tasks take some time to get actual image, when memory cache provide images instantaneously, hence no animation needed when image comes from memory.

I've came up with this UIImageView category, and I would like someone more experienced to provide some feedback. It does seem to work as intended. I'm not sure that I'm reinventing the wheel here, or this approach might somehow backfire under certain circumstances.

In outline is does two separate things. In completion block, when image is ready to use, it sets animation duration depending on what cache level has been used. The second idea is for reusable cell mechanism, imageView keeps track of the loading operation with help of associated objects and if there's an ongoing task it cancels it. This was not needed with AFNetworking solution, since it cancels ongoing image loads by default when you set a new one. I don't like the fact, that operation is retained, it's not supposed to be, but associated objects don't provide a __weak type of reference afaik. OBJC_ASSOCIATION_ASSIGN key seems to make object __unsafe_unretained and leads to a bad access crash.

Any advice would be appreciated.

static char kOperationKey;

- (id <SDWebImageOperation>)scrb_setImageAnimatedWithURL:(NSURL *)url {

    id <SDWebImageOperation> lastOperation = objc_getAssociatedObject(self, &kOperationKey);

    if (lastOperation) {
        [lastOperation cancel];
    }

    id <SDWebImageOperation> operation = [[SDWebImageManager sharedManager] downloadImageWithURL:url options:SDWebImageRetryFailed progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {

        if (finished && image) {

            BOOL animated = NO;

            if (cacheType == SDImageCacheTypeDisk || cacheType == SDImageCacheTypeNone) {
                animated = YES;
            }

            if (animated) {

                [UIView transitionWithView:self duration:ANIMATION_DURATION options:UIViewAnimationOptionTransitionCrossDissolve animations:^{

                    self.image = image;

                } completion:nil];

            } else {

                self.image = image;

            }

        }
    }];

    objc_setAssociatedObject(self, &kOperationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    return operation;

    }

---UPDATE---

With help from @n00neimp0rtant I've changed my category to this:

- (void)scrb_setImageAnimatedWithURL:(NSURL *)url {

    [self sd_cancelCurrentImageLoad];

    self.alpha = 0.0;

    [self sd_setImageWithURL:url placeholderImage:nil options:SDWebImageRetryFailed completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {

        if (image) {

            BOOL animated = NO;

            if (cacheType == SDImageCacheTypeDisk || cacheType == SDImageCacheTypeNone) {
                animated = YES;
            }

            self.image = image;

            if (animated) {

                [UIView animateWithDuration:ANIMATION_DURATION animations:^{

                    self.alpha = 1.0;

                }];

            } else {

                self.alpha = 1.0;

            }
        }

    }];

}

回答1:

I just stumbled across this and have been doing this on various projects. The key here is the option that prevents auto setting of the image. We then check the cache type and do a awesome crossfade. You can also set a placeholder image while your content is loading, or set the background colour of the image view if you want a solid background while something is loading.

[self.mediaImage sd_setImageWithURL:[NSURL URLWithString:content.previewUrl]
                       placeholderImage:nil
                                options:SDWebImageAvoidAutoSetImage
                              completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
                                  if ([imageURL isEqual:[NSURL URLWithString:content.previewUrl]]) {
                                      if (cacheType == SDImageCacheTypeMemory) {
                                          self.mediaImage.image = image;
                                      } else {
                                          [UIView transitionWithView:self.mediaImage
                                                            duration:.3
                                                             options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
                                                                 self.mediaImage.image = image;
                                                             }
                                                          completion:nil];
                                      }
                                  }
                              }];


回答2:

Any particular reason you are using SDWebImageManager to download your images manually? SDWebImage already provides a UIImageView category with methods that you can call directly on a UIImageView.

This is how I would attack the problem:

#import <UIImageView+WebCache.h>

// let's say we wanted to do a fade-in animation
imageView.alpha = 0.f;

[imageView sd_setImageWithURL:url completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
    if (cacheType == SDImageCacheTypeNone) {
        [UIView animateWithDuration:0.3 animations:^{
            imageView.alpha = 1.f;
        }];
    } else {
        imageView.alpha = 1.f;
    }
}];

Also, as a side note, performing an animation with a duration of 0.0 might seem instantaneous logically, but iOS still introduces the overhead of the whole animation operation, which is not only poor for performance, but also adds a tiny delay that you otherwise would not see without the animation.



回答3:

Using the answers listed here I was able to put together a version for swift.

imageView.sd_setImage(with: imageURL, placeholderImage: imageView.image, options: .avoidAutoSetImage, completed: { (image, error, cache, url) in
    UIView.transition(with: self, duration: 1.35, options: .transitionCrossDissolve, animations: {
        self.imageView.image = image
    }, completion: nil)
})