UICollection 视图滚动滞后与 SDWebImage [英] UICollection View Scroll lag with SDWebImage

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

问题描述

背景

我搜索过 SO 和苹果论坛.很多人都在谈论带有图像的集合视图单元格的性能.他们中的大多数人说自从在主线程中加载图像以来滚动滞后.

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.

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

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

问题描述

在纵向模式下,集合视图每行加载 3 个单元格.它没有滞后或微不足道的延迟.在横向模式下,集合视图为每行加载 4 个单元格.并且有明显的卡顿和掉帧现象.

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.

我已经检查了带有核心动画的仪器工具.当新单元出现时,帧速率下降到大约 8fps.我不确定哪个行为让我的收藏视图性能如此之低.

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.

这里是相关代码

在视图控制器中

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

}

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 = [[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 绝对令人难以置信,是许多大型生产应用程序的关键部分

SDWebImage is very admirable, but DLImageLoader is absolutely incredible, and a key piece of many big production apps

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

它非常容易使用.

为了避免浏览问题,基本上只是在开始下载图像之前引入延迟.所以,本质上是这样的……就这么简单

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,经过大量实验,它实际上比滚动浏览时跟踪的更复杂的解决方案效果要好得多.

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

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

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.

下面是典型的代码:

-(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,这一切都很容易实现,这真是太棒了.这是一个非常可靠的库.

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

这篇关于UICollection 视图滚动滞后与 SDWebImage的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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