iOS 仅当图像尚未使用 SDWebImage 缓存在内存中时才加载带有动画的图像的解决方案

iOS solution to load images with animation only if they are not already cached in memory with SDWebImage

几天来我一直在为这个问题苦苦挣扎。以前我使用 AFNetworking 类别来加载和缓存图像,但它没有在回调中提供缓存类型。所以我过去常常在每个控制器中跟踪哪些图像已经加载。我查看了 SDWebImage,它提供了我正在寻找的内容 - SDImageCacheType.

现在我正在尝试提出一种易于使用(希望是单行)的解决方案来加载 table 中的图像和带有动画的集合视图(如果它们是下载的或来自磁盘缓存) .基本上这些任务需要一些时间来获取实际图像,当内存缓存即时提供图像时,因此当图像来自内存时不需要动画。

我想出了这个 UIImageView 类别,我希望更有经验的人提供一些反馈。它似乎确实按预期工作。我不确定我是在重新发明轮子,还是这种方法在某些情况下可能会适得其反。

大纲是做两件不同的事情。在完成块中,当图像准备好使用时,它会根据使用的缓存级别设置动画持续时间。第二个想法是可重用单元格机制,imageView 在关联对象的帮助下跟踪加载操作,如果有正在进行的任务,它会取消它。 AFNetworking 解决方案不需要这样做,因为它会在您设置新图像时默认取消正在进行的图像加载。 我不喜欢这个操作被保留的事实,它不应该被保留,但是关联的对象不提供 __weak 类型的引用 afaik。 OBJC_ASSOCIATION_ASSIGN 密钥似乎生成对象 __unsafe_unretained 并导致错误的访问崩溃。

如有任何建议,我们将不胜感激。

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;

    }

---更新---

@n00neimp0rtant 的帮助下,我将我的类别更改为:

- (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;

            }
        }

    }];

}

您使用 SDWebImageManager 手动下载图像有什么特别的原因吗? SDWebImage 已经提供了一个 UIImageView 类别,其中包含您可以直接在 UIImageView 上调用的方法。

这就是我解决问题的方式:

#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;
    }
}];

此外,作为旁注,执行一个持续时间为 0.0 的动画在逻辑上可能看起来是瞬时的,但 iOS 仍然引入了整个动画操作的开销,这不仅不利于性能,而且还添加了一个微小的延迟,如果没有动画,您将看不到。

我只是偶然发现了这个,并且一直在各种项目中这样做。这里的关键是防止图像自动设置的选项。然后我们检查缓存类型并做一个很棒的交叉淡入淡出。您还可以在加载内容时设置占位符图像,或者如果在加载内容时想要纯色背景,则可以设置图像视图的背景颜色。

[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];
                                      }
                                  }
                              }];

使用此处列出的答案,我能够为 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)
})