1

I have a UITableView in a UIViewController that consists of dynamic multiple sections and dynamic multiple cells/rows in each section.

I have tried using actions, closures, index path. Etc

What I want to do is tap a UIButton in a custom UITableViewCell and have the row and corresponding data be deleted/removed.

Currently when I try this I can delete the first cell in a section but then when I try to delete the last one in the section I get a crash, Index out of bounds. It seems the UIButton retains the old indexPath.row

I have tried to reloadData() for the table but I haven’t been able to successfully solve this.

Using this solution from SO (UIButton action in table view cell):

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var myTableView: UITableView!

    var sections: [String] = []

    var items: [[String]] = []

    var things: [[String]] = []
    var things2: [[String]] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        things = [["A","B","C","D"],["X","Y","Z"],["J","A","A","E","K"]]

        things2 = [["Q","W","E","R"], ["G","H","J"]]

        items.append(contentsOf: things)
        items.append(contentsOf: things2)


        let secs: Int = Int.random(in: 1...items.count)

        for num in 1...secs {

            if num == 1 {
                sections.append("My Stuff")
            } else {
                sections.append("Section \(num)")
            }


        }

    }

}

extension ViewController: UITableViewDelegate, UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }

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

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return sections[section]
    }

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

        cell.theLabel?.text = items[indexPath.section][indexPath.row]

        if indexPath.section == 0 {
            cell.deleteButton.isHidden = false
            cell.editButton.isHidden = false

            cell.deleteButton.addAction {

                // This is broken, when the action is added in the closure. It persists the indexPath as it is, not changed even with a reloadData()

                self.items[indexPath.section].remove(at: indexPath.row)
                self.myTableView.deleteRows(at: [indexPath], with: .fade)


            }

            cell.editButton.addAction {
                print("I'm editing this row")
                cell.backgroundColor = UIColor.magenta
            }


        } else {
            cell.deleteButton.isHidden = true
            cell.editButton.isHidden = true
        }


        return cell
    }



}

// used from https://stackoverflow.com/questions/28894765/uibutton-action-in-table-view-cell/41374087

extension UIControl {
    func addAction(for controlEvents: UIControl.Event = .primaryActionTriggered, action: @escaping () -> ()) {
        let sleeve = ClosureSleeve(attachTo: self, closure: action)
        addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
    }

}

class ClosureSleeve {
    let closure: () -> ()

    init(attachTo: AnyObject, closure: @escaping () -> ()) {
        self.closure = closure
        objc_setAssociatedObject(attachTo, "[\(arc4random())]", self,.OBJC_ASSOCIATION_RETAIN)
    }

    @objc func invoke() {
        closure()
    }
}

The problem with this being used in multiple sections is that it retains the original indexPath and row.

Example: if section 0 has two items (0,1) I delete section 0 item 0 - it works as expected. Then when I tap the button to delete the remaining cell, which was section 0 item 1 and should have been reloaded to section 0 item 0. It fails because the closure still sees the code block as being section 0 item 1. Even through a reloadData() the closure still sees the original assignment of indexPath.section and indexPath.row

Anyone have any ideas?

I'm thinking it has something to do with the retain, but honestly, I'm drawing a blank. I've looked at this code in a debugger for a day or two and I'm frustrated and need some Yoda help...

Jamie D
  • 21
  • 3
  • 3
    What does your delete code look like? How is the data stored or managed? – MwcsMac Nov 20 '18 at 18:42
  • 2
    Are you able to share more code ? didSelectRowAt and multipleSection and deSelect, ETC... can you please share that code – Julian Silvestri Nov 20 '18 at 18:46
  • I added the entire ViewController class to show the issue. – Jamie D Nov 21 '18 at 02:38
  • @JulianSilvestri I have posted the entire code you requested for completeness. – Jamie D Nov 21 '18 at 06:31
  • @MwcsMac I have posted the entire code you requested for completeness. – Jamie D Nov 21 '18 at 06:31
  • @levinvaghese Using your closure example has some unusual side effects. – Jamie D Nov 21 '18 at 06:35
  • my best guess from reading and seeing your code. It appears that you need to reload the data when you delete an item from the array. I am not referring to reloadData() , I mean that when you delete a cell with the corresponding data, completely remove ALL the data from the corresponding array and then populate it again with the new data(with the cell that has been deleted gone). Then use that array to populate your tableView. – Julian Silvestri Nov 21 '18 at 21:59
  • 1
    @JulianSilvestri I tried that. It appears that the closure bound to the button keeps the original indexpath values. If you paste the code into a view controller wire it to a button in a custom cell, you’ll see what I mean. It also doesn’t like scrolling either. It sets the i dexpath in the closure to be nil. I’m guessing closure isn’t a way to do this... – Jamie D Nov 22 '18 at 00:58
  • any update on this ? Have you solved it yourself? – Julian Silvestri Dec 14 '18 at 20:10

0 Answers0