UICollection使用SDWebImage查看滚动延迟 [英] UICollection View Scroll lag with SDWebImage

查看:111
本文介绍了UICollection使用SDWebImage查看滚动延迟的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景



我搜索了SO和苹果论坛。相当多的人谈到了带图像的集合视图单元的性能。他们中的大多数人表示,由于在主线程中加载图像,因此滚动滞后。



通过使用 SDWebImage ,图像应该在单独的线程中加载。但是,它仅在iPad模拟器中以横向模式延迟。



问题描述



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



我已经使用具有核心动画的乐器工具进行了检查。当出现新单元格时,帧速率降至约8fps。我不确定哪种行为会给集合视图带来如此低的性能。



希望有人知道这些技巧。



以下是关联代码



在视图控制器中

   - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 
{
ProductCollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:@ ProductViewCellforIndexPath: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;
}完成: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
}
}];
}
返回单元格;

}

collectionViewCell中的PrepareForReuse

   - (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 = [[UIActivityIndi​​catorView alloc] initWithActivityIndi​​catorStyle:UIActivityIndi​​catorViewStyleGray];
_spinner.center = CGPointMake(CGRectGetMidX(self.bounds),CGRectGetMidY(self.bounds));
[self addSubview:_spinner];
[_spinner startAnimating];
_spinner.hidesWhenStopped = YES;

//添加微调器

__block UIActivityIndi​​catorView * 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){
//关闭微调器
[tmpSpinner stopAnimating];
[tmpSpinner removeFromSuperview];
tmpSpinner = nil;
if(nil == error){

//调整传入图像的大小
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;
}完成:nil];
}

} else {
//加载错误
[tmpImgView setImage:[UIImage imageNamed:@broken_image_small]];
}
}];

_brandLbl.text = [_product.brand.name uppercaseString];

_nameLbl.text = _product.name;
[_nameLbl sizeToFit];


//格式化价格
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];
//首先将所有文本更改为红色
[string addAttribute:NSForegroundColorAttributeName
value:[UIColor colorWithRed:157 / 255.0 green:38 / 255.0 blue:29 / 255.0 alpha:1.0]
范围:NSMakeRange(0,rawStr.length)];

//找到第一个空格
NSRange firstSpace = [rawStr rangeOfString:@];

//从零变为空格变为灰色
[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])
{
//所以换句话说,事实上,在很短的一段时间后,
//用户确实滚动了那个项目。
//(即,用户正在浏览)
//这个项目现在是一些新项目所以当然我们没有
//打扰加载那个旧的item
//即,我们现在知道用户只是略过了那个项目。
//(上面的初步条款中的TBC,
//因为图像已经在缓存中,
//我们只是即时加载图像 - 即使用户正在浏览)
// NSLog(@--- --- --- --- --- - - 太快了!);
返回;
}

//很短的时间过去了,确实这个单元格仍然是那个项目
//用户不是略读,所以我们开始加载图像。
// NSLog(@---不太快);

[DLImageLoader loadImageFromURL:urlWasThen
complete:^(NSError * error,NSData * imgData)
{
if(self == nil)return;

//从互联网上加载图像已经过了一段时间...

if(![urlWasThen isEqualToString:self.currentImage])
{
//请注意,这是正常情况,用户从图像移动了
//因此不需要toload。
//
//换句话说:在这种情况下,不是由于略读,
//但因为已经过了很多时间,
//用户已经移动了到表的其他部分。
//我们毫无意义地从互联网上加载了图像!卫生署!

// NSLog(@=== ==='太迟了!'图片加载!);
返回;
}

UIImage * image = [UIImage imageWithData:imgData];
self.someImage.image = image;
}];

});

这是非常简单的解决方案。



< IMO,经过大量的实验,实际上比滚动浏览时更复杂的跟踪更好。



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






请注意,上面的代码部分只是在单元格中加载图像的常用方式。 / p>

这是典型的代码:

   - (void) imageIsNow:(NSString *)imUrl 
{
//在此单元格上调用此例程设置图像。
//请注意图像现在比设置图像更好的名称
//不要忘记单元格会非常快速地更改内容,因为
//单元格在iOS上重用范例。

//这个单元被告知,要显示的图像现在是这个图像
//意识到滚动/略读问题,缓存问题等,
/ /利用此信息来适当加载/无论图像。

self.someImage.image = nil; //那是UIImageView
self.currentImage = imUrl; //你需要那个字符串属性

[self loadImageInASecIfItsTheSameAs:imUrl];
}


- (void)loadImageInASecIfItsTheSameAs :( NSString *)urlWasThen
{

//(注意 - 此处此处图像可能已经在缓存中可用
//。如果是这样,只显示它。为了简单起见,我省略了
//代码。)

//所以,就在这里,可能加载延迟图像
//完全如上面的代码所示.....

dispatch_after_secs_on_main(0.4,^
......等等....
......等....
}

由于DLImageLoader令人惊叹,这一切都很容易实现。它是一个非常可靠的库。


Background

I have searched around SO and apple forum. Quite a lot of people talked about performance of collection view cell with image. Most of them said it is lag on scroll since loading the image in the main thread.

By using SDWebImage, the images should be loading in separate thread. However, it is lag only in the landscape mode in the iPad simulator.

Problem description

In the portrait mode, the collection view load 3 cells for each row. And it has no lag or insignificant delay. In the landscape mode, the collection view load 4 cells for each row. And it has obvious lag and drop in frame rate.

I have checked with instrument tools with the core animation. The frame rate drop to about 8fps when new cell appear. I am not sure which act bring me such a low performance for the collection view.

Hope there would be someone know the tricks part.

Here are the relate code

In The View Controller

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

}

Override the setProduct method

- (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 is very admirable, but DLImageLoader is absolutely incredible, and a key piece of many big production apps

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

it's amazingly easy to use.

To avoid the skimming problem, basically just introduce a delay before bothering to start downloading the image. So, essentially like this...it's this simple

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

    });

That's the "incredibly easy" solution.

IMO, after vast experimentation, it actually works considerably better than the more complex solution of tracking when the scroll is skimming.

once again, DLImageLoader makes all this extremely easy https://stackoverflow.com/a/19115912/294884


Note that the section of code above is just the "usual" way you load an image inside a cell.

Here's typical code that would do that:

-(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....
    }

Again this is all easily possible due to DLImageLoader which is amazing. It is an amazingly solid library.

这篇关于UICollection使用SDWebImage查看滚动延迟的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆