Delete UITableView cell/row in UITableView with multiple sections using closure not working
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
|
show 4 more comments
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
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
|
show 4 more comments
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
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
swift uitableview uibutton custom-controls delete-row
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
|
show 4 more comments
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
|
show 4 more comments
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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