UICollectionView scrolls using AFNetworking to load images

I read quite a few UICollectionView posts about bad scrolling, but no one seems to be directly applicable, or they still go unanswered.

I use AFNetworking to asynchronously upload images (squared 95 pixels) to each cell, and then when the images scroll to the viewport again, the image is recovered from the cache (as verified by the response code set to 0 instead of 200).

Here is what I tried:

  • Commented on weakCell.photoView.image = image; so that the images are not drawn on the screen, and the scrolling is smoother (it still stutters a bit during HTTP retrieval).
  • All AFNetworking code was removed from the cellForRowAtIndexPath method, and the scrolling was much smoother (even with custom cell shadows, etc., which are still displayed on the screen).
  • When I draw only the cell screen (with shadows) on the screen, the scroll is very smooth for 100 cells. As soon as I start drawing images on the screen, the scrolling is very bad on my device, and this is even noticeable on the simulator. Instagram has very smooth scrolling for hundreds of cells in its profile, so I try to get closer to their performance.

Is there a way to improve my code below to improve scroll performance?

Here is my cell code:

 #import "PhotoGalleryCell.h" @implementation PhotoGalleryCell - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Setup the background color, shadow, and border self.backgroundColor = [UIColor colorWithWhite:0.25f alpha:1.0f]; self.layer.borderColor = [UIColor blackColor].CGColor; self.layer.borderWidth = 0.5f; self.layer.shadowColor = [UIColor blackColor].CGColor; self.layer.shadowRadius = 3.0f; self.layer.shadowOffset = CGSizeMake(0.0f, 2.0f); self.layer.shadowOpacity = 0.5f; // Make sure we rasterize for retina self.layer.rasterizationScale = [UIScreen mainScreen].scale; self.layer.shouldRasterize = YES; // Add to the content view self.photoView = [[UIImageView alloc] initWithFrame:self.bounds]; [self.contentView addSubview:self.photoView]; } return self; } - (void)prepareForReuse { [super prepareForReuse]; self.photoView.image = nil; self.largeImageURL = nil; } 

And here is my UICollectionView code:

 #pragma mark - Collection View Delegates - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { return 1; } - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return [zePhotos count]; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { PhotoGalleryCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kPGPhotoCellIdentifier forIndexPath:indexPath]; // Get a reference to the image dictionary NSDictionary *photoDict = [[zePhotos objectAtIndex:indexPath.row] objectForKey:@"image"]; // Asynchronously set the thumbnail view __weak PhotoGalleryCell *weakCell = cell; NSString *thumbnailURL = [[photoDict objectForKey:@"thumbnail"] objectForKey:@"url"]; NSURLRequest *photoRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:thumbnailURL]]; [cell.photoView setImageWithURLRequest:photoRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) { weakCell.photoView.image = image; } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) { NSLog(@"Error retrieving thumbnail... %@", [error localizedDescription]); }]; // Cache the large image URL in case they tap on this cell later cell.largeImageURL = [[photoDict objectForKey:@"large"] objectForKey:@"url"]; return cell; } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { [self performSegueWithIdentifier:@"showPhotoDetail" sender:self]; } 
+4
source share
3 answers

You can try adding shadowPath to your init cell, this should improve the performance of the code that I used in one of my projects to add a rounded shadowPath (see UIBezierPath methods for more choices)

 self.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:self.frame.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(10, 10)].CGPath; 

In addition, if I remember correctly, AFNetworking does not change the size of the image returned from the server, so this may affect the quality of your image (despite the scaling method that you added to UIImageView), I recommend sending the returned image to change it if you want:

 CGSize targetSize = cell.photoView.bounds.size; [cell.photoView setImageWithURLRequest:photoRequest placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ CGFloat imageHeight = image.size.height; CGFloat imageWidth = image.size.width; CGSize newSize = weakCell.imageView.bounds.size; CGFloat scaleFactor = targetSize.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(),^{ weakCell.photoView.image = small; }); }); } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) { NSLog(@"Error retrieving thumbnail... %@", [error localizedDescription]); }]; 
+1
source

The code check looks good, although I'm sure this is a shadow composition that adds a good deal to the delay. The way you pinpoint what causes the delay is to use the Time Profiler tool in the tools. Here are the docs from Apple .

0
source

The problem is that with fast scrolling, you are simultaneously launching hundreds of network requests. If you have a cached image, display it immediately. If you do not, start the download only when the table display slows down.

You can use something like this:

 //Properties or Instance Variables NSDate *scrollDateBuffer; CGPoint scrollOffsetBuffer; - (void)scrollViewDidScroll:(UIScrollView *)scrollView { NSTimeInterval secondsSinceLastScroll = [[NSDate date] timeIntervalSinceDate:scrollDateBuffer]; CGFloat distanceSinceLastScroll = fabsf(scrollView.contentOffset.y - scrollOffsetBuffer.y); BOOL slow = (secondsSinceLastScroll > 0 && secondsSinceLastScroll < 0.02); BOOL small = (distanceSinceLastScroll > 0 && distanceSinceLastScroll < 1); if (slow && small) { [self loadImagesForOnscreenRows]; } scrollDateBuffer = [NSDate date]; scrollOffsetBuffer = scrollView.contentOffset; } 

You want to call loadImagesForOnscreenRows in other methods, for example, when new data appears, viewWillAppear and scrollViewDidScrollToTop .

Here's an example implementation of loadImagesForOnscreenRows :

 - (void)loadImagesForOnscreenRows { @try { for (UITableViewCell *cell in self.tableView.visibleCells) { // load your images NSURLRequest *photoRequest = …; if (photoRequest) { [cell.photoView setImageWithURLRequest:…]; } } } @catch (NSException *exception) { NSLog(@"Exception when loading table cells: %@", exception); } } 

I have this in a try / catch block because in my experience [UITableView -visibleCells] not reliable - it sometimes returns freed cells or cells without a supervisor. If you make sure that this method is called only if the table does not scroll quickly, this should not greatly affect the scroll performance.

Also note that the AFNetworking UIImageView category does not cache the object. You need to modify it a bit to check if you already have a cached image; this answer should point you in the right direction.

0
source

Source: https://habr.com/ru/post/1500162/


All Articles