UICollection View使用SDWebImage查看卷动滞后

背景

我search了SO和苹果论坛。 很多人谈到收集视图单元格与图像的performance。 他们中的大多数人表示,自从在主线程中加载图像以来,它在滚动上是滞后的。

通过使用SDWebImage ,图像应该加载在单独的线程中。 但是,在iPad模拟器中,仅在横向模式下才是滞后的。

问题描述

在纵向模式下,集合视图为每行加载3个单元格。 并没有滞后或微不足道的延迟。 在横向模式下,集合视图为每行加载4个单元格。 帧速率有明显的滞后和下降。

我用核心animation检查了仪器工具。 新单元出现时,帧速率降至8fps左右。 我不知道哪个行为给我带来如此低的performance收集视图。

希望有人知道这个技巧的一部分。

这里是相关的代码

在视图控制器中

 - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { ProductCollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:@"ProductViewCell" forIndexPath:indexPath]; Product *tmpProduct = (Product*)_ploader.loadedProduct[indexPath.row]; cell.product = tmpProduct; if (cellShouldAnimate) { cell.alpha = 0.0; [UIView animateWithDuration:0.2 delay:0 options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction) animations:^{ cell.alpha = 1.0; } completion:nil]; } if(indexPath.row >= _ploader.loadedProduct.count - ceil((LIMIT_COUNT * 0.3))) { [_ploader loadProductsWithCompleteBlock:^(NSError *error){ if (nil == error) { cellShouldAnimate = NO; [_collectionView reloadData]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ cellShouldAnimate = YES; }); } else if (error.code != 1){ #ifdef DEBUG_MODE ULog(@"Error.des : %@", error.description); #else CustomAlertView *alertView = [[CustomAlertView alloc] initWithTitle:@"Connection Error" message:@"Please retry." buttonTitles:@[@"OK"]]; [alertView show]; #endif } }]; } return cell; } 

PrepareForReuse在collectionViewCell中

 - (void)prepareForReuse { [super prepareForReuse]; CGRect bounds = self.bounds; [_thumbnailImgView sd_cancelCurrentImageLoad]; CGFloat labelsTotalHeight = bounds.size.height - _thumbnailImgView.frame.size.height; CGFloat brandToImageOffset = 2.0; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { brandToImageOffset = 53.0; } CGFloat labelStartY = _thumbnailImgView.frame.size.height + _thumbnailImgView.frame.origin.y + brandToImageOffset; CGFloat nameLblHeight = labelsTotalHeight * 0.46; CGFloat priceLblHeight = labelsTotalHeight * 0.18; _brandLbl.frame = (CGRect){{15, labelStartY}, {bounds.size.width - 30, nameLblHeight}}; CGFloat priceToNameOffset = 8.0; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { priceToNameOffset = 18.0; } _priceLbl.frame = (CGRect){{5, labelStartY + nameLblHeight - priceToNameOffset}, {bounds.size.width-10, priceLblHeight}}; [_spinner stopAnimating]; [_spinner removeFromSuperview]; _spinner = nil; } 

覆盖setProduct方法

 - (void)setProduct:(Product *)product { _product = product; _spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; _spinner.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); [self addSubview:_spinner]; [_spinner startAnimating]; _spinner.hidesWhenStopped = YES; // Add a spinner __block UIActivityIndicatorView *tmpSpinner = _spinner; __block UIImageView *tmpImgView = _thumbnailImgView; ProductImage *thumbnailImage = _product.images[0]; [_thumbnailImgView sd_setImageWithURL:[NSURL URLWithString:thumbnailImage.mediumURL] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) { // dismiss the spinner [tmpSpinner stopAnimating]; [tmpSpinner removeFromSuperview]; tmpSpinner = nil; if (nil == error) { // Resize the incoming images dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ CGFloat imageHeight = image.size.height; CGFloat imageWidth = image.size.width; CGSize newSize = tmpImgView.bounds.size; CGFloat scaleFactor = newSize.width / imageWidth; newSize.height = imageHeight * scaleFactor; UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0); [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; UIImage *small = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); dispatch_async(dispatch_get_main_queue(),^{ tmpImgView.image = small; }); }); if (cacheType == SDImageCacheTypeNone) { tmpImgView.alpha = 0.0; [UIView animateWithDuration:0.2 delay:0 options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction) animations:^{ tmpImgView.alpha = 1.0; } completion:nil]; } } else { // loading error [tmpImgView setImage:[UIImage imageNamed:@"broken_image_small"]]; } }]; _brandLbl.text = [_product.brand.name uppercaseString]; _nameLbl.text = _product.name; [_nameLbl sizeToFit]; // Format the price NSNumberFormatter * floatFormatter = [[NSNumberFormatter alloc] init]; [floatFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; [floatFormatter setDecimalSeparator:@"."]; [floatFormatter setMaximumFractionDigits:2]; [floatFormatter setMinimumFractionDigits:0]; [floatFormatter setGroupingSeparator:@","]; _priceLbl.text = [NSString stringWithFormat:@"$%@ USD", [floatFormatter stringFromNumber:_product.price]]; if (_product.salePrice.intValue > 0) { NSString *rawStr = [NSString stringWithFormat:@"$%@ $%@ USD", [floatFormatter stringFromNumber:_product.price], [floatFormatter stringFromNumber:_product.salePrice]]; NSMutableAttributedString * string = [[NSMutableAttributedString alloc] initWithString:rawStr]; // Change all the text to red first [string addAttribute:NSForegroundColorAttributeName value:[UIColor colorWithRed:157/255.0 green:38/255.0 blue:29/255.0 alpha:1.0] range:NSMakeRange(0,rawStr.length)]; // find the first space NSRange firstSpace = [rawStr rangeOfString:@" "]; // Change from zero to space to gray color [string addAttribute:NSForegroundColorAttributeName value:_priceLbl.textColor range:NSMakeRange(0, firstSpace.location)]; [string addAttribute:NSStrikethroughStyleAttributeName value:@2 range:NSMakeRange(0, firstSpace.location)]; _priceLbl.attributedText = string; } } 

SDWebImage是非常令人敬佩的,但DLImageLoader是绝对不可思议的,并且是许多大型生产应用程序的关键

https://stackoverflow.com/a/19115912/294884

它非常容易使用。

为了避免撇去问题,基本上只是在开始下载图像之前引入延迟。 所以,基本上就是这样…这很简单

 dispatch_after_secs_on_main(0.4, ^ { if ( ! [urlWasThen isEqualToString:self.currentImage] ) { // so in other words, in fact, after a short period of time, // the user has indeed scrolled away from that item. // (ie, the user is skimming) // this item is now some "new" item so of course we don't // bother loading "that old" item // ie, we now know the user was simply skimming over that item. // (just TBC in the preliminary clause above, // since the image is already in cache, // we'd just instantly load the image - even if the user is skimming) // NSLog(@" --- --- --- --- --- --- too quick!"); return; } // a short time has passed, and indeed this cell is still "that" item // the user is NOT skimming, SO we start loading the image. //NSLog(@" --- not too quick "); [DLImageLoader loadImageFromURL:urlWasThen completed:^(NSError *error, NSData *imgData) { if (self == nil) return; // some time has passed while the image was loading from the internet... if ( ! [urlWasThen isEqualToString:self.currentImage] ) { // note that this is the "normal" situation where the user has // moved on from the image, so no need toload. // // in other words: in this case, not due to skimming, // but because SO much time has passed, // the user has moved on to some other part of the table. // we pointlessly loaded the image from the internet! doh! //NSLog(@" === === 'too late!' image load!"); return; } UIImage *image = [UIImage imageWithData:imgData]; self.someImage.image = image; }]; }); 

这是“非常容易”的解决scheme。

国际海事组织经过大量的实验,实际上它的工作原理要比滚动浏览时更复杂的跟踪解决scheme好得多

再次, DLImageLoader使这一切非常简单https://stackoverflow.com/a/19115912/294884


请注意,上面的代码段只是您在单元格中加载图像的“常用”方式。

这是典型的代码,可以做到这一点:

 -(void)imageIsNow:(NSString *)imUrl { // call this routine o "set the image" on this cell. // note that "image is now" is a better name than "set the image" // Don't forget that cells very rapidly change contents, due to // the cell reuse paradigm on iOS. // this cell is being told that, the image to be displayed is now this image // being aware of scrolling/skimming issues, cache issues, etc, // utilise this information to apprporiately load/whatever the image. self.someImage.image = nil; // that's UIImageView self.currentImage = imUrl; // you need that string property [self loadImageInASecIfItsTheSameAs:imUrl]; } -(void)loadImageInASecIfItsTheSameAs:(NSString *)urlWasThen { // (note - at this point here the image may already be available // in cache. if so, just display it. I have omitted that // code for simplicity here.) // so, right here, "possibly load with delay" the image // exactly as shown in the code above ..... dispatch_after_secs_on_main(0.4, ^ ...etc.... ...etc.... } 

由于DLImageLoader非常棒,所以这一切都很容易实现。 这是一个非常坚实的图书馆。