[英]UICollectionView Cell Image changing as it comes into view with GCD
我需要調整大型本地存儲的圖像(包含在self.optionArray
),然后在collectionView中顯示它。 如果我只是顯示它,iOS會嘗試調整圖像大小,因為我快速滾動導致與內存相關的崩潰。
在下面的代碼中,collectionView將平滑滾動,但有時如果我滾動得非常快,則會顯示一個不正確的圖像,然后在滾動減速時更改為正確的圖像。 為什么不將cell.cellImage.image
設置為nil
修復此問題?
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath];
cell.cellImage.image = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
cell.cellImage.image = nil;
UIImage *test = [self.optionArray objectAtIndex:indexPath.row];
UIImage *localImage2 = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)];
dispatch_sync(dispatch_get_main_queue(), ^{
cell.cellImage.image = localImage2
cell.cellTextLabel.text = @"";
[cell setNeedsLayout];
});
});
}
return cell;
}
- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0);
[image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
編輯:我添加了另一個async來緩存第一個和nil並初始化cell.image。 我在初始快速向下滾動時遇到了同樣的問題。 然而,在卷軸上,它現在完美無瑕。
我補充說:
-(void)createDictionary
{
for (UIImage *test in self.optionArray) {
UIImage *shownImage = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)];
[localImageDict setObject:shownImage forKey:[NSNumber numberWithInt:[self.optionArray indexOfObject:test]]];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (!localImageDict) {
localImageDict = [[NSMutableDictionary alloc]initWithCapacity:self.optionArray.count];
}
else {
[localImageDict removeAllObjects];
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[self createDictionary];
});
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath];
cell.cellImage.image = nil;
cell.cellImage.image = [[UIImage alloc]init];
if ([localImageDict objectForKey:[NSNumber numberWithInt:indexPath.row]]) {
cell.cellImage.image = [localImageDict objectForKey:[NSNumber numberWithInt:indexPath.row]];
cell.cellTextLabel.text = @"";
}
else {
cell.cellImage.image = nil;
cell.cellImage.image = [[UIImage alloc]init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
UIImage *test = [self.optionArray objectAtIndex:indexPath.row];
UIImage *shownImage = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)];
[localImageDict setObject:shownImage forKey:[NSNumber numberWithInt:indexPath.row]];
dispatch_sync(dispatch_get_main_queue(), ^{
cell.cellImage.image = shownImage;
cell.cellTextLabel.text = @"";
[cell setNeedsLayout];
});
});
}
}
return cell;
仔細看看你的代碼示例,我可以看到你的內存問題的根源。 跳出來的最重要的問題是您似乎將所有圖像保存在一個數組中。 這需要非常大的內存(我推斷你需要調整它們必須大的圖像)。
要減少應用程序的占用空間,您不應該維護一組UIImage
對象。 相反,只需維護一個URL或路徑到您的圖像,然后只在UI需要時動態創建UIImage
對象(一個稱為延遲加載的過程)。 一旦圖像離開屏幕,就可以釋放它( UICollectionView
,就像UITableView
為你做的很多清理工作一樣,只要你不保持對圖像的強引用)。
應用程序通常只應維護當前可見圖像的UIImage
對象。 您可能出於性能原因緩存這些已調整大小的圖像(例如,使用NSCache
),但是當內存不足時,將自動清除緩存。
好的是你顯然已經精通異步處理。 無論如何,實現可能如下所示:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CustomTabBarCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CustomTabBarCell" forIndexPath:indexPath];
NSString *filename = [self.filenameArray objectAtIndex:indexPath.row]; // I always use indexPath.item, but if row works, that's great
UIImage *image = [self.thumbnailCache objectForKey:filename]; // you can key this on whatever you want, but the filename works
cell.cellImage.image = image; // this will load cached image if found, or `nil` it if not found
if (image == nil) // we only need to retrieve image if not found in our cache
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
UIImage *test = [UIImage imageWithContentsOfFile:filename]; // load the image here, now that we know we need it
if (!test)
{
NSLog(@"%s: unable to load image", __FUNCTION__);
return;
}
UIImage *localImage2 = [self imageWithImage:test scaledToSize:CGSizeMake(test.size.width/5, test.size.height/5)];
if (!localImage2)
{
NSLog(@"%s: unable to convert image", __FUNCTION__);
return;
}
[self.thumbnailCache setObject:localImage2 forKey:filename]; // save the image to the cache
dispatch_async(dispatch_get_main_queue(), ^{ // async is fine; no need to keep this background operation alive, waiting for the main queue to respond
// see if the cell for this indexPath is still onscreen; probably is, but just in case
CustomTabBarCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
if (updateCell)
{
updateCell.cellImage.image = localImage2
updateCell.cellTextLabel.text = @"";
[updateCell setNeedsLayout];
}
});
});
}
return cell;
}
這假定您定義了thumbnailCache
的類屬性,它是對您將在viewDidLoad
或其中任何位置初始化的NSCache
的強引用。 緩存是一種充分利用兩個世界的方法,在內存中加載圖像以獲得最佳性能,但是當您遇到內存壓力時它將被釋放。
很明顯,我很樂意假設“哦,只需用一組圖像文件名替換你的圖像陣列”,我知道你可能需要進入代碼的一些不同部分才能使它工作,但是這個毫無疑問是你的記憶消耗的來源。 很明顯,你總是會遇到其他內存問題(保留周期等),但在你發布的代碼片段中沒有類似內容。
我有一個類似的問題,但以不同的方式。
我也遇到了“pop-in”的問題,因為加載異步的圖像在進入時閃爍,直到最后顯示正確的圖像。
發生這種情況的一個原因是,最初出列的單元格的當前索引路徑與您放入其中的圖像的索引不匹配。
基本上,如果您從0-19快速滾動並且要更新的單元格是#20,並且您希望它顯示圖像#20,但它仍然異步加載圖像3,7,14。
為了防止這種情況,我所做的是追蹤兩個指數; #1)反映單元實際位置的最新索引路徑和#2)實際加載異步的圖像對應的索引(在這種情況下實際應該是你傳入cellforitematindexpath的索引路徑,它保留為異步過程通過隊列工作,因此對於某些圖像加載實際上是“舊”數據)。
獲取最新索引路徑的一種方法可能是創建一個簡單的方法,只返回單元格當前位置的NSInteger。 將其存儲為currentIndex。
然后我在實際填充圖像之前添加了一些if語句來檢查兩者是否相等。
所以if(currentIndex == imageIndex)然后加載圖片。
如果你把NSLog(@“CURRENT ...%d ... IMAGE ...%d”,currentIndex,imageIndex)放在那些if語句之前你可以很清楚地看到當兩者不匹配時,異步調用應該出口。
希望這可以幫助。
我找到了chuey101所說的措辭,令人困惑。 我找到了一種方法,然后意識到chuey101意味着相同。
如果它可以幫助任何人,由於正在運行的不同線程,圖像會閃爍並更改。 因此,當您為圖像操作生成線程時,它將針對特定單元格生成,例如c1。 但是,最后當您實際將圖像加載到單元格中時,它將成為您正在查看的當前單元格,即您滾動到的單元格 - 例如c2。 因此,當您滾動到c2時,在滾動時會生成c2個線程,每個單元格前面有一個。 根據我的理解,所有這些線程都會嘗試將其圖像加載到當前單元格c2中。 所以,你有閃爍的圖像。
要避免這種情況,您需要實際檢查是否要將所需的圖像加載到要加載到的單元格中。 因此,在將圖像加載到其中之前獲取collectionviewcell indexpath.row(loading_image_into_cell)。 此外,在產生線程之前,即在主線程(image_num_to_load)中,獲取從線程中產生的單元格。 現在,在加載之前,檢查這兩個數字是否相等。
問題解決了 :)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.