Search code examples
swiftuicollectionviewrealmuicollectionviewcelllayoutsubviews

How to properly manage elaborate Collection View cellForItemAt method?


I'm a fairly new developer and I have some long cellForItemAt methods. I have a feeling I'm missing something important.

In this ViewController I have a segmented control that filters data with a boolean taskView property.

Here's what my cellForItemAt call looks like:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TaskCell.reuseIdentifier, for: indexPath) as! TaskCell
        
        //SwipeCell Delegate
        cell.delegate = self
        
        var transactionResults: Results<Transaction>
        
        if taskView {
            transactionResults = unclearedTransactionsToDate
        } else {
            transactionResults = allTransactions
        }
        
        cell.configureCollectionViewCells(indexPath, transactionResults)
        
        let balanceAtDate: Double = realm.objects(Transaction.self).filter("transactionDate <= %@", transactionResults[indexPath.item].transactionDate).sum(ofProperty: "transactionAmount")
        
        cell.balanceLabel.attributedText = balanceAtDate.toAttributedString(size: 9, offset: 6)
        
        if transactionResults[indexPath.item].isCleared == false && !taskView {
            cell.amountLabel.textColor = .lightGray
            cell.subcategoryLabel.textColor = .lightGray
            cell.dateLabel.textColor = .lightGray
            cell.balanceLabel.textColor = .lightGray
            cell.circleView.backgroundColor = .lightGray
        } else {
            cell.subcategoryLabel.textColor = .black
            cell.dateLabel.textColor = .black
            cell.balanceLabel.textColor = .black
            cell.circleView.backgroundColor = UIColor(rgb: transactionResults[indexPath.item].transactionCategory!.categoryColor)
        }
        
        return cell
    }
}

And in my configureCollectionViewCells method I have:

func configureCollectionViewCells(_ indexPath: IndexPath, _ transaction: Results<Transaction>) {
        
        imageView.image = UIImage(named: transaction[indexPath.item].transactionCategory!.categoryName)
        imageView.tintColor = .white
        circleView.backgroundColor = UIColor(rgb: transaction[indexPath.item].transactionCategory!.categoryColor)
        subcategoryLabel.textColor = .black
        dateLabel.textColor = .black
        balanceLabel.textColor = .black
        subcategoryLabel.text = transaction[indexPath.item].transactionSubCategory?.subCategoryName
        amountLabel.attributedText = transaction[indexPath.item].transactionAmount.toAttributedString(size: 9, offset: 6)
        
        
        
        let formatter = DateFormatter()
        
        formatter.dateFormat = "MMMM dd, yyyy"
        
        let dateString = formatter.string(from: transaction[indexPath.item].transactionDate)
        
        dateLabel.text = dateString
        
        if transaction[indexPath.item].transactionAmount > 0 {
            
            amountLabel.textColor = UIColor(rgb: Constants.green)
            
        } else {
            
            amountLabel.textColor = UIColor(rgb: Constants.red)
            
        }
    }

The code works, but I have a feeling that it's not being implemented properly. Can someone give me some advice (keeping in mind that I'm new to programming) on how to manage such lengthy functions? I feel like I'm missing a couple of concepts.

Some people have just said "Throw everything in cellForItemAt" and some only have 3 lines in cellForItemAt.

I think maybe I should be overriding the layoutSubviews method in the collectionView cell and implementing some of the code there.

Any general or specific advice is greatly appreciated. I would also be interested in researching if anyone has any resources on the topic.

Thank you in advance.


Solution

  • What you're doing is not "wrong" in and of itself. However, there is a school of thought which says that, ideally, cellForRowAt should know nothing of the internal interface of the cell. This (cellForRowAt) is the data source. It should hand the cell just the data. You have a cell subclass (TaskCell), so it just needs some methods or properties that allow it to be told what the data is, and the cell should then format itself and populate its own interface in accordance with those settings.

    If all of that formatting and configuration code is moved into the cell subclass, the cellForRowAt implementation will be much shorter, cleaner, and clearer, and the division of labor will be more appropriate.

    In support of this philosophy, I would just add that Apple has adopted it in iOS 14, where a cell can now have a UIContentConfiguration object whose job is to communicate the data from cellForRowAt into the contentView of the cell. So for example instead of saying (for a table view cell) cell.textLabel.text = "howdy" you say configuration.text = "howdy" and let the configuration object worry about the fact that a UILabel might be involved in the interface.