title: UICollection View Scroll lag with SDWebImage
body: <p><strong>Background</strong></p>
  
  <p>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. </p>
  
  <p>By using <code>SDWebImage</code>, the images should be loading in separate thread. However, it is lag only in the landscape mode in the iPad simulator.</p>
  
  <p><strong>Problem description</strong></p>
  
  <p>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.</p>
  
  <p>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.</p>
  
  <p>Hope there would be someone know the tricks part.</p>
  
  <p>Here are the relate code</p>
  
  <p><strong>In The View Controller</strong></p>
  
  <pre><code>- (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 &gt;= _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;
  
  }
  </code></pre>
  
  <p><strong>PrepareForReuse in the collectionViewCell</strong></p>
  
  <pre><code>- (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;
  
  }
  </code></pre>
  
  <p><strong>Override the setProduct method</strong></p>
  
  <pre><code>- (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 &gt; 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;
      }
  
  }
  </code></pre>