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)
})
几天来我一直在为这个问题苦苦挣扎。以前我使用 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)
})