6

Sfondolag UICollection vista di scorrimento con SDWebImage

Ho cercato in giro così e forum di Apple. Molte persone hanno parlato delle prestazioni della cella di visualizzazione della raccolta con l'immagine. La maggior parte di loro ha detto che è in ritardo sullo scroll da quando ha caricato l'immagine nel thread principale.

Utilizzando SDWebImage, le immagini devono essere caricate in una filettatura separata. Tuttavia, è in ritardo solo nella modalità orizzontale nel simulatore iPad.

Descrizione del problema

In modalità ritratto, la collezione vista carico 3 cellule per ogni riga. E non ha ritardo o ritardo insignificante. In modalità orizzontale, la vista raccolta carica 4 celle per ogni riga. E ha ovvio ritardo e calo del frame rate.

Ho verificato con strumenti di strumento con l'animazione core. Il frame rate scende a circa 8fps quando appare una nuova cella. Non sono sicuro di quale azione mi porti prestazioni così basse per la visualizzazione della raccolta.

Spero che qualcuno sappia la parte dei trucchi.

Ecco il codice

relazionarsi Nel 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 nel 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; 

} 

l'override del metodo 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; 
    } 

} 
+0

Problema simile ... http://stackoverflow.com/q/25347854/294884 (E lo stesso in Android! :) http://stackoverflow.com/a/25950627/294884) – Fattie

risposta

6

SDWebImage è molto ammirevole, ma DLImageLoader è assolutamente incredibile, e un pezzo chiave di molte grandi applicazioni di produzione

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

è incredibilmente facile da usare.

Per evitare il problema di scrematura, in pratica basta introdurre un ritardo prima di iniziare a scaricare l'immagine. Quindi, essenzialmente così ... è così semplice

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

    }); 

Questa è la soluzione "incredibilmente facile".

IMO, dopo una vasta sperimentazione, in realtà funziona in modo considerevolmente migliore rispetto alla soluzione più complessa di tracciamento quando si scorre lo scorrimento.

ancora una volta, DLImageLoader rende tutto questo estremamente facile https://stackoverflow.com/a/19115912/294884


Si noti che la sezione del codice di cui sopra è solo il modo "solito" si carica un'immagine all'interno di una cella.

Ecco il codice tipico che farebbe che:

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

Anche in questo caso è tutto facilmente possibile grazie alla DLImageLoader che è incredibile. È una libreria incredibilmente solida.

+0

Sono un po 'confuso di come aggiorni "UrlWasThen" ?? –

+0

Ehi, @KenHui, ho dato una spiegazione completa, evviva! – Fattie

+0

Grazie mille. Sebbene il codice assomigli a imUrl, urlWasThen dovrebbe aggiornarsi mentre cellForItemAtIndexPath è in esecuzione. Tuttavia, l'urlWasThen nel blocco sembra non sarà influenzato da tale aggiornamento in qualche modo ..... Credo che questa sia la parte confusa che ho avuto. Grazie molto. –