Delete UITableView cell/row in UITableView with multiple sections using closure not working












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...










share|improve this question




















  • 3




    What does your delete code look like? How is the data stored or managed?
    – MwcsMac
    Nov 20 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 at 18:46










  • I added the entire ViewController class to show the issue.
    – Jamie D
    Nov 21 at 2:38










  • @JulianSilvestri I have posted the entire code you requested for completeness.
    – Jamie D
    Nov 21 at 6:31






  • 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 at 0:58


















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...










share|improve this question




















  • 3




    What does your delete code look like? How is the data stored or managed?
    – MwcsMac
    Nov 20 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 at 18:46










  • I added the entire ViewController class to show the issue.
    – Jamie D
    Nov 21 at 2:38










  • @JulianSilvestri I have posted the entire code you requested for completeness.
    – Jamie D
    Nov 21 at 6:31






  • 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 at 0:58
















1












1








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...










share|improve this question















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...







swift uitableview uibutton custom-controls delete-row






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 21 at 6:27

























asked Nov 20 at 18:23









Jamie D

63




63








  • 3




    What does your delete code look like? How is the data stored or managed?
    – MwcsMac
    Nov 20 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 at 18:46










  • I added the entire ViewController class to show the issue.
    – Jamie D
    Nov 21 at 2:38










  • @JulianSilvestri I have posted the entire code you requested for completeness.
    – Jamie D
    Nov 21 at 6:31






  • 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 at 0:58
















  • 3




    What does your delete code look like? How is the data stored or managed?
    – MwcsMac
    Nov 20 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 at 18:46










  • I added the entire ViewController class to show the issue.
    – Jamie D
    Nov 21 at 2:38










  • @JulianSilvestri I have posted the entire code you requested for completeness.
    – Jamie D
    Nov 21 at 6:31






  • 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 at 0:58










3




3




What does your delete code look like? How is the data stored or managed?
– MwcsMac
Nov 20 at 18:42




What does your delete code look like? How is the data stored or managed?
– MwcsMac
Nov 20 at 18:42




2




2




Are you able to share more code ? didSelectRowAt and multipleSection and deSelect, ETC... can you please share that code
– Julian Silvestri
Nov 20 at 18:46




Are you able to share more code ? didSelectRowAt and multipleSection and deSelect, ETC... can you please share that code
– Julian Silvestri
Nov 20 at 18:46












I added the entire ViewController class to show the issue.
– Jamie D
Nov 21 at 2:38




I added the entire ViewController class to show the issue.
– Jamie D
Nov 21 at 2:38












@JulianSilvestri I have posted the entire code you requested for completeness.
– Jamie D
Nov 21 at 6:31




@JulianSilvestri I have posted the entire code you requested for completeness.
– Jamie D
Nov 21 at 6:31




1




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 at 0:58






@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 at 0:58



















active

oldest

votes











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53399228%2fdelete-uitableview-cell-row-in-uitableview-with-multiple-sections-using-closure%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown






























active

oldest

votes













active

oldest

votes









active

oldest

votes






active

oldest

votes
















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.





Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


Please pay close attention to the following guidance:


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53399228%2fdelete-uitableview-cell-row-in-uitableview-with-multiple-sections-using-closure%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Costa Masnaga

Fotorealismo

Sidney Franklin