Search code examples
iosswiftuicollectionviewuicollectionviewflowlayout

swift - Correct way to pass data to UICollectionViewCell


I'm trying to create my own Book app, and using UICollectionView for listing all the books. Data for each cell is from .plist file, and I'm using custom flowLayout(to make some changes later).

So now I'm stucked with delays and lags when scrolling. I suppose I've made mistakes with passing data to cell or with cell initializing.

Cell created by .xib and custom class, just some layout and UI:

class BookCoverCell: UICollectionViewCell {
    @IBOutlet weak var view1: UIView!
    @IBOutlet weak var view2: UIView!
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var darkRedView: UIView!
    @IBOutlet weak var lightRedView: UIView!
    @IBOutlet weak var readButton: UIButton!
    @IBOutlet weak var pageLabel: UILabel!    


    override func awakeFromNib() {
        super.awakeFromNib()
        clipsToBounds = true
        self.backgroundColor = UIColor(white: 1, alpha: 0.0)
        view1.layer.cornerRadius = 10
        view2.layer.cornerRadius = 10
        darkRedView.layer.cornerRadius = 10
        lightRedView.layer.cornerRadius = 10
    }    
} 

At ViewController's class:

class MainVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UIScrollViewDelegate {


    @IBOutlet weak var collectionContainerView: UIView!
    @IBOutlet weak var collectionView: UICollectionView!

    var books: Array<Book>? {
        didSet {
            collectionView.reloadData()
        }
    }
    var flowLayout = UICollectionViewFlowLayout()
    override func viewDidLayoutSubviews() {        
        flowLayout = ZoomAndSnapFlowLayout()
        collectionView.collectionViewLayout = flowLayout
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        collectionContainerView.backgroundColor = UIColor(white: 1, alpha: 0.0)
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(UINib(nibName: "BookCoverCell", bundle: nil), forCellWithReuseIdentifier: "BookCoverCell");
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
        books = BookStore.sharedInstance.loadBooks(plist: "Books")        
    }

//MARK: UICollectionViewDelegate

    func numberOfSections(in collectionView: UICollectionView) -> Int {
      return 1
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        if let books = books {
            return books.count
        }
        return 0
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BookCoverCell", for: indexPath) as! BookCoverCell
        let book = books![indexPath.row]
        let cover = book.coverImage()!
        let color = book.getDominantColor()
        cell.view1.backgroundColor = color
        cell.imageView.image = cover
        cell.pageLabel.text = "Pages: 29"
        cell.readButton.setTitle("Read", for: .normal)
        return cell
    }

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let book = books?[indexPath.row]
        print ("open")
    }

}

And my layout now looks like:

class ZoomAndSnapFlowLayout: UICollectionViewFlowLayout {


    var cellWidth = CGFloat()
    var cellHeight = CGFloat()
    var minLineSpacing = CGFloat()

    override init() {
        super.init()
        self.scrollDirection = .horizontal
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func prepare() {
        guard let collectionView = collectionView else { fatalError() }

        cellHeight = collectionView.frame.height * 0.8
        minLineSpacing = 200
        cellWidth = cellHeight

        itemSize = CGSize(width: cellWidth, height: cellHeight)

        let verticalInsets = (collectionView.frame.height - collectionView.adjustedContentInset.top - collectionView.adjustedContentInset.bottom - itemSize.height) / 2
        let horizontalInsets = (collectionView.frame.width - collectionView.adjustedContentInset.right - collectionView.adjustedContentInset.left - itemSize.width) / 2

        sectionInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets)

        super.prepare()
    }
}

So I'm pretty sure that have some mistakes in my code, but can't find them(( Any suggestion will be helpful for me!

UPDATE: So first of all thanks! I've changed my code and replace a really big image with smallest one, and function to get dominant color simply to white color for now, and things already gets better!

BUT there is small lag or delay when first scroll begins, and only with 1st and 2nd cells, while they are scrolling to the left edge of the screen. And after that all cells scrolling without lags, even first two, in both directions (left / right).

Now my code looks like:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BookCoverCell", for: indexPath) as! BookCoverCell
        let book = books![indexPath.row]
        let cover = UIImage(named: "flag.png")
        let color = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
        cell.view1.backgroundColor = color
        cell.imageView.image = cover
        cell.pageLabel.text = "Pages: 29"
        cell.readButton.setTitle("Read", for: .normal)
        return cell
    }

and UPDATE #2 removing

flowLayout = ZoomAndSnapFlowLayout()
collectionView.collectionViewLayout = flowLayout

to viewWillAppear() fix these small lags too!! Hooray!)


Solution

  • Read this article about Time Profiling and collection view: https://voxels.github.io/eliminating-collection-view-tearing-with-xcode-time-profiler-instrument

    Since your implementation is very simple, there aren't many things that could be wrong, but you probably have the same issue that the author of the article had -- the images themselves are very large, and need to be read and resized.

    They solved the issue by making appropriately sized versions of the images.

    In your case getDominantColor will also be slower on large images (I am assuming that it reads the pixels to get the dominant color. You should also consider caching this color and not recalculating it every time (if you are not already doing that).