Creating Slow Scrolling to Indexpath in Uicollectionview

Creating slow scrolling to indexPath in UICollectionView

You can try this approach:

@property (nonatomic, assign) CGPoint scrollingPoint, endPoint;
@property (nonatomic, strong) NSTimer *scrollingTimer;
@synthesize scrollingPoint, endPoint;
@synthesize scrollingTimer;

- (void)scrollSlowly {
// Set the point where the scrolling stops.
self.endPoint = CGPointMake(0, 300);
// Assuming that you are starting at {0, 0} and scrolling along the x-axis.
self.scrollingPoint = CGPointMake(0, 0);
// Change the timer interval for speed regulation.
self.scrollingTimer = [NSTimer scheduledTimerWithTimeInterval:0.015 target:self selector:@selector(scrollSlowlyToPoint) userInfo:nil repeats:YES];
}

- (void)scrollSlowlyToPoint {
self.collectionView.contentOffset = self.scrollingPoint;
// Here you have to respond to user interactions or else the scrolling will not stop until it reaches the endPoint.
if (CGPointEqualToPoint(self.scrollingPoint, self.endPoint)) {
[self.scrollingTimer invalidate];
}
// Going one pixel to the right.
self.scrollingPoint = CGPointMake(self.scrollingPoint.x, self.scrollingPoint.y+1);
}

UICollectionView scrolling is slow

So After some messing around, I've figured out that the problem was based on a few factors.

One- The images for the thumbnails were too big, so what I did was made a seperate array of images with smaller image sizes that would fit the cell.
Two- With the help from @ggrana, opening a seperate thread sped up the process to and made it less laggy.
Three- I also found that having an array of images rather than image locations was faster-- only problem is it takes up more memory.

SWIFT 2 - UICollectionView - slow scrolling

At first - you are working with UI objects from global queue and seems like without any purpose. That is forbidden - or behavior will be undefined.

Secondary, the mostly heavy operation is creation of thumbnail which you perform on main queue.
Consider using of the AVAssetImageGenerator's method

public func generateCGImagesAsynchronouslyForTimes(requestedTimes: [NSValue], completionHandler handler: AVAssetImageGeneratorCompletionHandler)

instead of your own asyncs.

At third, viewWithTag is pretty heavy operation causing enumeration on subviews. Consider to declare properties in the cell for views which you need.

UPD: to declare properties in a cell, create subclass of UICollectionViewCell with appropriate properties as IBOutlets. Then, in your view controller viewDidLoad implementation, call

collecionView.registerClass(<YourCellSubclass>.dynamicType,  forCellWithReuseIdentifier:"VideoCell") 

Or, if your collection view cell is configured in the Storyboard, specify the class of the cell and connect its subviews to class' outlets directly in the cell's settings window in Interface Builder.

At fourth, your cells are being reused by a collection view. Each time your cell is going out of visible area, it is removed from collection view and is put to reuse queue. When you scroll back to the cell, your view controller is asked again to provide a cell. And you're fetching the thumbnail for the video again for each newly appeared cell. Consider caching of already fetched thumbnails by storing them in some array by collectionView's indexPath.item index.

Lagging scrolling problem on UICollectionView

There are a few issues in your code that are causing hitches:

  1. Every time the collection view needs a cell, your code loads 40 large images from disk, picks one of them, then discards the rest.
  2. Even if you'd load just one image per cell, it still happens on the main queue. While the image is being loaded, the UI freezes.
  3. The images themselves are very large, so UIImageView has to resize them each time it needs to display an image.

To mitigate that, here are my recommendations:

  1. Do not create a ThemeManager class in collectionView(:cellForItemAt:). Instead, make it a property of the view controller.
  2. Instead of having ThemeManager return all images at once, make it fetch just one image with a particular index.
  3. Make image loading asynchronous, moving the work off the main queue.
  4. After loading an image from disk, resize it appropriately so that UIImageView wouldn't have to deal with huge resolutions.
  5. Implement an image cache of resized images inside ThemeManager so that the previously images would load instantly. NSCache is a great class that can achieve this.

Here's a example where I marked the implementations of each of the recommendations above:

import UIKit

final class ViewController: UIViewController {

@IBOutlet private var collectionView: UICollectionView!

// Recommendation #1: make ThemeManager a property
// instead of recreating it each time
private let themeManager = ThemeManager()

override func viewDidLoad() {
super.viewDidLoad()
collectionView.setCollectionViewLayout(UICollectionViewFlowLayout(), animated: false)
collectionView.dataSource = self
collectionView.delegate = self
}
}

extension ViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
1
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
themeManager.imageKeys.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ThemesCollectionViewCell", for: indexPath) as? ThemesCollectionViewCell else {
fatalError("Unexpected cell class dequeued")
}

cell.currentIndexPath = indexPath

let cellSize = self.collectionView(collectionView, layout: collectionView.collectionViewLayout, sizeForItemAt: indexPath)
themeManager.fetchImage(atIndex: indexPath.item, resizedTo: cellSize) { [weak cell] image, itemIndex in
guard let cell = cell, let image = image else { return }
DispatchQueue.main.async {
guard let cellIndexPath = cell.currentIndexPath, cellIndexPath.item == itemIndex else {
print("⚠️ Discarding fetched image for item \(itemIndex) because the cell is no longer being used for that index path")
return
}
print("Fetched image for \(indexPath) of size \(image.size) and scale \(image.scale)")
cell.imageView.image = image
cell.textLabel.text = "\(indexPath)"
}
}

return cell
}
}

// MARK: - UICollectionViewDelegate and UICollectionViewDelegateFlowLayout

extension ViewController: UICollectionViewDelegate {}

extension ViewController: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let sectionInsets = self.collectionView(collectionView, layout: collectionViewLayout, insetForSectionAt: indexPath.section)
return CGSize(width: collectionView.frame.width - sectionInsets.left - sectionInsets.right, height: 128)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
10
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
0
}
}

// MARK: - ThemesCollectionViewCell

final class ThemesCollectionViewCell: UICollectionViewCell {
@IBOutlet var textLabel: UILabel!
@IBOutlet var imageView: UIImageView!
var currentIndexPath: IndexPath?

override func awakeFromNib() {
super.awakeFromNib()
layer.cornerRadius = 20
}

override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
currentIndexPath = nil
}
}

// MARK: - ThemeManager

final class ThemeManager {
// Recommendation #5: Implement an image cache of resized images inside `ThemeManager`
private let imageCache = NSCache<NSString, UIImage>()

private(set) var imageKeys: [NSString]

init() {
imageKeys = []
for i in 1...40 {
imageKeys.append(NSString(string: "image\(i)"))
}
}

// Recommendation #2: make ThemeManager fetch just one image with a particular index.
func fetchImage(atIndex index: Int, resizedTo size: CGSize, completion: @escaping (UIImage?, Int) -> Void) {
guard 0 <= index, index < imageKeys.count else {
assertionFailure("Image with invalid index requested")
completion(nil, index)
return
}

let imageKey = imageKeys[index]

if let cachedImage = imageCache.object(forKey: imageKey) {
completion(cachedImage, index)
return
}

// Recommendation #3: Make image loading asynchronous, moving the work off the main queue.
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else {
completion(nil, index)
return
}
guard let image = UIImage(named: String(imageKey)) else {
assertionFailure("Image is missing from the asset catalog")
completion(nil, index)
return
}

// Recommendation #4: After loading an image from disk, resize it appropriately
let resizedImage = image.resized(to: size)

self.imageCache.setObject(resizedImage, forKey: imageKey)
completion(resizedImage, index)
}
}
}

// MARK: - Image Resize Extension

extension UIImage {
func resized(to targetSize: CGSize) -> UIImage {
let widthRatio = (targetSize.width / size.width)
let heightRatio = (targetSize.height / size.height)
let effectiveRatio = max(widthRatio, heightRatio)
let newSize = CGSize(width: size.width * effectiveRatio, height: size.height * effectiveRatio)
let rect = CGRect(origin: .zero, size: newSize)

UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
self.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

return newImage!
}
}

I've put the whole sample project on Github, feel free to clone it and see how it works.

UICollectionview Scrolling choppy when loading cells

So anybody having scrolling issues should do this

add these 2 lines after your dequeue

cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;


Related Topics



Leave a reply



Submit