I have tableView constraining to the top, left, right, and bottom of the ViewController.
I put UIButton inside the view for a header which has a function for expanding and shrinking using inserting and deleting rows with fade animation.
However, when I tap the button to expand the third section in a situation where the first and the last section are expanded and the second and the third section are shrunk, the third section's cells are overlapping with the third section like this video.
I want to know why this is happening and the solution.
Thanks in advance.
struct TwoDimentionalData {
let items: [String]
var isExpanded: Bool
}
class ViewController: UIViewController {
let headerId = "headerId"
let cellId = "cellId"
var datas = [TwoDimentionalData]()
@IBOutlet weak var tableView: UITableView! {
didSet {
tableView.delegate = self
tableView.dataSource = self
tableView.register(CollapsibleTableViewCell.self, forCellReuseIdentifier: cellId)
tableView.register(CopplasibleTableViewHeader.self, forHeaderFooterViewReuseIdentifier: headerId)
}
}
override func viewDidLoad() {
super.viewDidLoad()
datas = [TwoDimentionalData(items: ["Item1-1", "Item1-2"], isExpanded: true),
TwoDimentionalData(items: ["Item2-1", "Item2-2", "Item2-3"], isExpanded: true),
TwoDimentionalData(items: ["Item3-1", "Item3-2", "Item3-3", "Item3-4"], isExpanded: true),
TwoDimentionalData(items: ["Item4-1"], isExpanded: true)
]
}
}
extension ViewController: UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return datas.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datas[section].isExpanded ? datas[section].items.count : 0
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
cell.backgroundColor = .green
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return datas[indexPath.section].isExpanded ? 50 : 0
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: headerId) as? CopplasibleTableViewHeader
header?.expandButton.tag = section
header?.collapsibleTableViewHeaderDelegate = self
header?.setLayout()
header?.contentView.backgroundColor = .yellow
return header
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40
}
}
extension ViewController: CollapsibleTableViewHeaderDelegate {
func didTapExpandButton(tag: Int) {
let isExpended = datas[tag].isExpanded
datas[tag].isExpanded = !isExpended
var indexPaths = [IndexPath]()
let items = datas[tag].items
for row in items.indices {
let indexPath = IndexPath(row: row, section: tag)
indexPaths.append(indexPath)
}
tableView.performBatchUpdates {
if isExpended {
tableView.deleteRows(at: indexPaths, with: .fade)
} else {
tableView.insertRows(at: indexPaths, with: .fade)
}
}
}
}
protocol CollapsibleTableViewHeaderDelegate: AnyObject {
func didTapExpandButton(tag: Int)
}
class CopplasibleTableViewHeader: UITableViewHeaderFooterView {
weak var collapsibleTableViewHeaderDelegate: CollapsibleTableViewHeaderDelegate?
lazy var expandButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
button.addTarget(self, action: #selector(handleExpandButton), for: .touchUpInside)
return button
}()
@objc private func handleExpandButton(button: UIButton) {
print("handleExpandButton")
let tag = button.tag
collapsibleTableViewHeaderDelegate?.didTapExpandButton(tag: tag)
}
override func prepareForReuse() {
super.prepareForReuse()
}
func setLayout() {
addSubview(expandButton)
expandButton.translatesAutoresizingMaskIntoConstraints = false
expandButton.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
expandButton.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
I've solved this by using reloadRows method instead of insert/delete rows method.
First, change numberofRowsInSection to the below code to prevent a crash.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datas[section].items.count
}
And inside CollapsibleTableViewHeaderDelegate,
extension ViewController: CollapsibleTableViewHeaderDelegate {
func didTapExpandButton(tag: Int) {
let isExpended = datas[tag].isExpanded
datas[tag].isExpanded = !isExpended
var indexPaths = [IndexPath]()
let items = datas[tag].items
for row in items.indices {
let indexPath = IndexPath(row: row, section: tag)
indexPaths.append(indexPath)
}
tableView.performBatchUpdates {
tableView.reloadRows(at: indexPaths, with: .fade)
}
}
}
In this way, I get no more overlapping cells but still get fade animation.