Kenny Ho
Kenny Ho

Reputation: 459

Insert & delete rows for Expansion & Collapse effect for Tableview

I have a problem concerning the datasource. reason: 'attempt to insert row 0 into section 0, but there are only 0 rows in section 0 after the update'.

I've been trying to expand and collapse section 1 of my tableview. When I'm first presented the view controller, I can expand, then collapse, but when I try to expand it the 2nd time, it crashes. I try adding + 1 to when its expanded in numberOfRows, but that crashes too. idk what i'm doing wrong and what I need to add to make this work.

Edit* When I initially click to expand the section, within numberofRowsInSection the if isExpanded == false statement is run giving me a section.count - 1. But why is that ran and give me back a row? It seems my problem is related to that somehow but IDK the fix.

var sectionArray = [ ExpandableCell(isExpanded: false, section: [""])
]


@objc func handleExpandClose(button: UIButton) {
    let indexPath = IndexPath(row: 0, section: 0)

    let isExpanded = sectionArray[0].isExpanded
    if isExpanded {
        sectionArray[0].section.removeAll()
        tableView.beginUpdates()
        tableView.deleteRows(at: [indexPath], with: .fade)
        tableView.endUpdates()
    } else {
        sectionArray[0].section.append("")
        tableView.beginUpdates()
        tableView.insertRows(at: [indexPath], with: .fade)
        tableView.endUpdates()

    }
    sectionArray[0].isExpanded.toggle()
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

    if section == 0 && sectionArray[0].isExpanded {
        return sectionArray[0].section.count
    } else if section == 0 && sectionArray[0].isExpanded == false {
        return sectionArray[0].section.count - 1
    }

    else if section == 1 {
        return 1
    }
    return 0
}

Upvotes: 1

Views: 652

Answers (2)

Dambar B. Bista
Dambar B. Bista

Reputation: 74

For the future solution seekers, Here's the way to insert and delete rows to make collapseable.

 class ViewController: UIViewController {

let navigationBar: UINavigationBar = {
    let width = UIScreen.main.bounds.width
    let navBar = UINavigationBar(frame: CGRect(x: 0, y: 0, width: width, height: 25))
    let navItem = UINavigationItem(title: "Lists")
    let addIcon = UIBarButtonItem(systemItem: .add)
    
    navItem.rightBarButtonItem = addIcon
    navBar.items = [navItem]
    
    return navBar
}()


    let tableView: UITableView = {
    let table = UITableView(frame: .zero, style: .insetGrouped)
    table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
    return table
}()

var isOpen: Bool = false


var list = ["Fruites", "Vegetable"]

var fruits = ["Apple", "Orange", "Banana", "Mango", "Pineapple"]
override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    tableView.delegate = self
    tableView.dataSource = self
    
    view.addSubview(navigationBar)
    view.addSubview(tableView)
    
    setupConstraints()
 
}


private func setupConstraints() {
    navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
    navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    navigationBar.translatesAutoresizingMaskIntoConstraints = false
    
    tableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true
    tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    tableView.translatesAutoresizingMaskIntoConstraints = false
}

}

extension ViewController: UITableViewDelegate, UITableViewDataSource {

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return list.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell",for: indexPath)
    
    cell.textLabel?.text = list[indexPath.row]
    return cell
}


func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

    
    if !isOpen {
        for i in 0..<fruits.count {
            /// Here i am inserting at 1, you can insert where ever you want
            list.insert(fruits[i], at: 1+i)
            tableView.insertRows(at: [.init(row: 1+i, section: 0)], with: .fade)
        }
        
    } else {
        for _ in 0..<fruits.count {
            list.removeLast()
            print(list.last!)
            print( list.count)
            tableView.deleteRows(at: [.init(row: list.count - 1, section: 0)], with: .fade)
          
        }
    }
    
    isOpen.toggle()
}

}

Upvotes: 2

Shehata Gamal
Shehata Gamal

Reputation: 100503

when the app runs this

if section == 0 && sectionArray[0].isExpanded == false

runs so number of rows is 0 according to ectionArray[0].section.count - 1 , then when you click the action handleExpandClose , the else runs

} else {
sectionArray[0].section.append("")
tableView.beginUpdates()
tableView.insertRows(at: [indexPath], with: .fade)

in it you append data to the inner array inside the the only object , so when you insert , the dataSource main array sectionArray not changed , hence the crash


class TableViewController: UITableViewController {

    var sectionArray = [ExpandableCell(),ExpandableCell(),ExpandableCell()]


    override func viewDidLoad() {
        super.viewDidLoad()

        // Uncomment the following line to preserve selection between presentations
        // self.clearsSelectionOnViewWillAppear = false

        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem

        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        // simulate collapse action
        DispatchQueue.main.asyncAfter(deadline: .now() + 4) {

            self.sectionArray[0].isExpanded = false

            self.tableView.reloadData()
        }
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return sectionArray.count
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return sectionArray[section].isExpanded ? sectionArray[section].content.count : 0
    }


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

        // Configure the cell...

        cell.textLabel?.text = sectionArray[indexPath.section].content[indexPath.row]

        return cell
    }


}



struct ExpandableCell {

    var isExpanded = true

    var content = ["1","2","3"]
}

Upvotes: 2

Related Questions