我的应用程序中有一个UICollectionView,每个单元格都是UIImageView和一些文本标签。问题是当我让UIImageViews显示他们的图像时,滚动性能很糟糕。它远不如UITableView的滚动体验,甚至没有UIImageView的相同UICollectionView。
我几个月前发现了this question,似乎找到了答案,但它是用RubyMotion编写的,我不明白。我试着看看如何将它转换为Xcode,但由于我从未使用过NSCache,所以有点难。那里的海报还指出here除了他们的解决方案之外还要实现一些东西,但我不确定在哪里放置代码。可能是因为我不理解first question中的代码。
有人能帮助将其翻译成Xcode吗?
def viewDidLoad
...
@images_cache = NSCache.alloc.init
@image_loading_queue = NSOperationQueue.alloc.init
@image_loading_queue.maxConcurrentOperationCount = 3
...
end
def collectionView(collection_view, cellForItemAtIndexPath: index_path)
cell = collection_view.dequeueReusableCellWithReuseIdentifier(CELL_IDENTIFIER, forIndexPath: index_path)
image_path = @image_paths[index_path.row]
if cached_image = @images_cache.objectForKey(image_path)
cell.image = cached_image
else
@operation = NSBlockOperation.blockOperationWithBlock lambda {
@image = UIImage.imageWithContentsOfFile(image_path)
Dispatch::Queue.main.async do
return unless collectionView.indexPathsForVisibleItems.containsObject(index_path)
@images_cache.setObject(@image, forKey: image_path)
cell = collectionView.cellForItemAtIndexPath(index_path)
cell.image = @image
end
}
@image_loading_queue.addOperation(@operation)
end
end
以下是second question的提问者解决问题的first question代码:
UIImage *productImage = [[UIImage alloc] initWithContentsOfFile:path];
CGSize imageSize = productImage.size;
UIGraphicsBeginImageContext(imageSize);
[productImage drawInRect:CGRectMake(0, 0, imageSize.width, imageSize.height)];
productImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
同样,我不确定如何/在何处实施。
非常感谢。
答案 0 :(得分:19)
这是我遵循的模式。始终加载异步并缓存结果。当异步加载完成时,不要假设视图的状态。我有一个简化负载的类,如下所示:
//
// ImageRequest.h
// This class keeps track of in-flight instances, creating only one NSURLConnection for
// multiple matching requests (requests with matching URLs). It also uses NSCache to cache
// retrieved images. Set the cache count limit with the macro in this file.
#define kIMAGE_REQUEST_CACHE_LIMIT 100
typedef void (^CompletionBlock) (UIImage *, NSError *);
@interface ImageRequest : NSMutableURLRequest
- (UIImage *)cachedResult;
- (void)startWithCompletion:(CompletionBlock)completion;
@end
//
// ImageRequest.m
#import "ImageRequest.h"
NSMutableDictionary *_inflight;
NSCache *_imageCache;
@implementation ImageRequest
- (NSMutableDictionary *)inflight {
if (!_inflight) {
_inflight = [NSMutableDictionary dictionary];
}
return _inflight;
}
- (NSCache *)imageCache {
if (!_imageCache) {
_imageCache = [[NSCache alloc] init];
_imageCache.countLimit = kIMAGE_REQUEST_CACHE_LIMIT;
}
return _imageCache;
}
- (UIImage *)cachedResult {
return [self.imageCache objectForKey:self];
}
- (void)startWithCompletion:(CompletionBlock)completion {
UIImage *image = [self cachedResult];
if (image) return completion(image, nil);
NSMutableArray *inflightCompletionBlocks = [self.inflight objectForKey:self];
if (inflightCompletionBlocks) {
// a matching request is in flight, keep the completion block to run when we're finished
[inflightCompletionBlocks addObject:completion];
} else {
[self.inflight setObject:[NSMutableArray arrayWithObject:completion] forKey:self];
[NSURLConnection sendAsynchronousRequest:self queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// build an image, cache the result and run completion blocks for this request
UIImage *image = [UIImage imageWithData:data];
[self.imageCache setObject:image forKey:self];
id value = [self.inflight objectForKey:self];
[self.inflight removeObjectForKey:self];
for (CompletionBlock block in (NSMutableArray *)value) {
block(image, nil);
}
} else {
[self.inflight removeObjectForKey:self];
completion(nil, error);
}
}];
}
}
@end
现在,单元格(集合或表格)更新非常简单:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
NSURL *url = [NSURL URLWithString:@"http:// some url from your model"];
// note that this can be a web url or file url
ImageRequest *request = [[ImageRequest alloc] initWithURL:url];
UIImage *image = [request cachedResult];
if (image) {
UIImageView *imageView = (UIImageView *)[cell viewWithTag:127];
imageView.image = image;
} else {
[request startWithCompletion:^(UIImage *image, NSError *error) {
if (image && [[collectionView indexPathsForVisibleItems] containsObject:indexPath]) {
[collectionView reloadItemsAtIndexPaths:@[indexPath]];
}
}];
}
return cell;
}
答案 1 :(得分:5)
一般来说,UICollectionViews或UITableViews的滚动行为会发生,因为单元格在iOS主线程中出列并构造。预先缓存单元格或在后台线程中构造它们几乎没有自由,而是在滚动阻止UI时将它们出列并构造。 (就个人而言,我发现苹果公司的这个糟糕的设计尽管它确实简化了问题,因为你不必了解潜在的线程问题。我认为他们应该给一个钩子但是为UICollectionViewCell / UITableViewCell池提供一个自定义实现。可以处理单元格的出列/重用。)
性能下降的最重要原因确实与图像数据有关,并且(按降序排列)是我的经验:
因为我对平滑滚动非常挑剔(即使它只是第一次使用单元格)我通过子类化UINib构建了一个整个框架来预先缓存单元格(这基本上是你进入出列过程的唯一钩子iOS版)。但这可能超出了您的需求。
答案 2 :(得分:0)
我遇到了有关UICollectionView
滚动的问题。
对我来说(几乎)有点像魅力:我用png缩略图90x90填充了单元格。我说几乎是因为第一个完整的卷轴不是那么平滑,但是再也不会崩溃了。
就我而言,单元格大小为90x90。
之前我有很多原始的png尺寸,当png原始尺寸大于~1000x1000(第一次滚动时很多崩溃)时它非常不稳定。
所以,我在UICollectionView
上选择90x90(或类似物)并显示原始的png(无论大小)。希望它可以帮助别人。