Search code examples
iosuitableviewexpandablecollapsable

ExpandableCell cell reuse issues


I'm trying to use the library ExpandableCell to add collapsable table view cells to my app. I'm using the latest version of the library which is 1.3.0.

Below is the full code.

import UIKit
import ExpandableCell

class ViewController: UIViewController {
    @IBOutlet weak var tableView: ExpandableTableView!

    private var passengers = [Passenger]()


    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.tableFooterView = UIView(frame: .zero)
        tableView.expandableDelegate = self

        passengers = [
            Passenger(id: 1, name: "Mark", trips: [Trip(id: 1, route: "NY to NJ")]),
            Passenger(id: 2, name: "Jesica", trips: [Trip(id: 1, route: "NY to NJ"), Trip(id: 2, route: "LA to LV")]),
            Passenger(id: 3, name: "Brian", trips: [Trip(id: 2, route: "Kansas City to Denver")])
        ]
        tableView.reloadData()
    }
}

extension ViewController: ExpandableDelegate {
    func expandableTableView(_ expandableTableView: ExpandableTableView, numberOfRowsInSection section: Int) -> Int {
        return passengers.count
    }

    func expandableTableView(_ expandableTableView: ExpandableTableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: PassengerCell.reuseIdentifier, for: indexPath) as! PassengerCell
        let passenger = passengers[indexPath.row]
        cell.textLabel?.text = passenger.name
        cell.detailTextLabel?.text = "\(passenger.trips?.count ?? 0) trips"
        return cell
    }

    func expandableTableView(_ expandableTableView: ExpandableTableView, expandedCellsForRowAt indexPath: IndexPath) -> [UITableViewCell]? {
        let cell = tableView.dequeueReusableCell(withIdentifier: TripCell.reuseIdentifier, for: indexPath) as! TripCell
        let passenger = passengers[indexPath.row]
        if let trips = passenger.trips {
            var cells = [TripCell]()
            for trip in trips {
                cell.textLabel?.text = trip.route
                cells.append(cell)
            }
            return cells
        } else {
            return nil
        }
    }

    func expandableTableView(_ expandableTableView: ExpandableTableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }

    func expandableTableView(_ expandableTableView: ExpandableTableView, heightsForExpandedRowAt indexPath: IndexPath) -> [CGFloat]? {
        let count = passengers[indexPath.row].trips?.count ?? 0
        let heightArray = [CGFloat](repeating: 50, count: count)
        return heightArray
    }

    func expandableTableView(_ expandableTableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
        return true
    }
}

The data is loaded correctly and the tableview appears as expected. But the problem is when you tap on a collapsed cell. It acts...weird.

enter image description here

Notice how some cells don't appear at all (the second group should show 2 yellow cells). And some cells appear in other groups that they don't belong in. It looks like a cell reuse issue.

I tried overriding the prepareForReuse method and reset the controls manually as well but that didn't work either.

override func prepareForReuse() {
    super.prepareForReuse()
    textLabel?.text = nil
    backgroundColor = nil
}

I saw some similar issues in the library's Github repo but there aren't any answers or fixes.

If anyone has used this library before, any idea what might be causing this issue and how to fix it?

Demo project


Solution

  • Looking at your Demo Project...

    In expandedCellsForRowAt in ViewController, you are creating one cell object, then assigning it different text values and appending it to an array.

    func expandableTableView(_ expandableTableView: ExpandableTableView, expandedCellsForRowAt indexPath: IndexPath) -> [UITableViewCell]? {
    
        // here, you create a cell object
        let cell = tableView.dequeueReusableCell(withIdentifier: TripCell.reuseIdentifier, for: indexPath) as! TripCell
    
        let passenger = passengers[indexPath.row]
        if let trips = passenger.trips {
            var cells = [TripCell]()
            for trip in trips {
                // here, you repeatedly set the text of the SAME cell object
                cell.textLabel?.text = trip.route
                cells.append(cell)
            }
            return cells
        } else {
            return nil
        }
    }
    

    Use this instead:

    func expandableTableView(_ expandableTableView: ExpandableTableView, expandedCellsForRowAt indexPath: IndexPath) -> [UITableViewCell]? {
    
        // Don't create the cell here
        //let cell = tableView.dequeueReusableCell(withIdentifier: TripCell.reuseIdentifier, for: indexPath) as! TripCell
    
        let passenger = passengers[indexPath.row]
        if let trips = passenger.trips {
            var cells = [TripCell]()
            for trip in trips {
                // create a NEW cell for each trip (don't use indexPath)
                let cell = tableView.dequeueReusableCell(withIdentifier: TripCell.reuseIdentifier) as! TripCell
                cell.textLabel?.text = trip.route
                cells.append(cell)
            }
            return cells
        } else {
            return nil
        }
    }