I am trying to understand when to use a delegate. When I add an item in my Master View controller I deselect all items. If, however, I cancel the add I need to reselect the last row I was on. Is that a case where I should set up a protocol in the detail view and delegate the reselection to the Master View Controller? Right now the cancel button is tied to the exit button on the detail view controller which returns it to the Master with no action taken.
Accepted Reply
I admire his dedication also. I didn't realize this would be such a complicated question.
I wonder if I should have stuck with my profession, accounting, rather than trying to create an app.
I found the solution to selecting the row. The code above selects the first row after deleting an item but clicking the done button is where the select row belongs to highlight the row.
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if editing {
} else {
tableView.selectRow(at: selectedItem, animated: true, scrollPosition: .none)
}
}
Replies
I do not need any code to be corrected ! And I hope you can add the missing quote yourself.
print("Row", indexPath.row, "Rows in section", tableView.numberOfRows(inSection: indexPath.section))
But if you don't want to be collaborative, tell me, I'll just stop bothering you with questions when looking at your posts.
Good luck.
Sorry, I copied and pasted your response and tried closing the quote in evey place but that one. Still trying to get used to the print statement in Xcode.
It says section 3 which is wrong. I only have one section. I tried section 0 and 1 but still doesn't select the first record.
No, what it says is
Row 3 Rows in section 3
Which means that, after deleting the row, there are 3 rows in the section (0), is it correct ?
I have tried the code in an app in the
if editingStyle == .delete { }
and it works OK
Just try this, in case you are not in the main thread:
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
deadsticks.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
DeadStick.saveDeadSticks(deadsticks)
DispatchQueue.main.async { // ADD THIS
if indexPath.row < tableView.numberOfRows(inSection: indexPath.section) {
self.tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top)
} else {
if tableView.numberOfRows(inSection: indexPath.section) > 0 {
self.tableView.selectRow(at: IndexPath(row: 0, section: indexPath.section), animated: true, scrollPosition: .top)
}
} // AND THIS
}
}
}
Correct there are three rows in the section after deleting the fourth row.
The DispatchQueue skips the if statement after it. Does that mean I am not in the main thread?
Are you testing this out in the Playground? Can you do that without a tableview and an array available to access data?
I tested in a compiled app, not in playground.
Are you testing in playground ? I understood you were testing in an app.
What do you mean :
The DispatchQueue skips the if statement after it. Does that mean I am not in the main thread?
How do you know the if statement is skipped ?
You deleted the fourth row:
so, normally, you should execute the else condition:
} else {
if tableView.numberOfRows(inSection: indexPath.section) > 0 {
self.tableView.selectRow(at: IndexPath(row: 0, section: indexPath.section), animated: true, scrollPosition: .top)
}
- what if you delete the second row for instance ?
- did you select the row before deleting ?
Tell me also:
- where is the table (masterView) ?
- where do you implement the override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
At this stage, you should post the complete code of the class where you have the tableView.
I put a breakpoint before the editing style function and then stepped through the code as it executes. It executes to DispatchQueue statement and then does not execute the if statement.
I tried selecting the second row and selecting the row I added before deleting. Neither made a difference.
This is the complete code.
// Define class.
class MasterViewController: UITableViewController {
var detailViewController: DetailViewController? = nil
var objects = [Any]()
var selectedItem = IndexPath(row: 0, section: 0)
var deadsticks = [DeadStick]()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = editButtonItem
if let split = splitViewController {
let controllers = split.viewControllers
detailViewController = (controllers[controllers.count-1] as! UINavigationController).topViewController as? DetailViewController
}
if let savedDeadSticks = DeadStick.loadDeadSticks() {
deadsticks = savedDeadSticks
} else {
deadsticks = DeadStick.loadSampleDeadSticks()
}
self.tableView.selectRow(at: selectedItem, animated: true, scrollPosition: .top)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return deadsticks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else
{
fatalError("Could not degueue a cell")
}
let deadstick = deadsticks[indexPath.row]
cell.textLabel?.text = deadstick.aircraft
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt
indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit
editingStyle: UITableViewCellEditingStyle, forRowAt indexPath:
IndexPath) {
if editingStyle == .delete {
deadsticks.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
DeadStick.saveDeadSticks(deadsticks)
DispatchQueue.main.async {
if indexPath.row < tableView.numberOfRows(inSection: indexPath.section) {
self.tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top)
} else {
if tableView.numberOfRows(inSection: indexPath.section) > 0 {
self.tableView.selectRow(at: IndexPath(row: 0, section: indexPath.section), animated: true, scrollPosition: .top)
}
}
}
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedItem = indexPath
print(selectedItem.row)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "addDetail":
if let index = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: index, animated: true)
}
detailViewController?.previousSelectionPath = selectedItem
os_log("Adding New Aircraft.", log: OSLog.default, type: .debug)
case "showDetail":
let detailViewController = (segue.destination as! UINavigationController).topViewController as! DetailViewController
let indexPath = tableView.indexPathForSelectedRow!
let selectedDeadStick = deadsticks[indexPath.row]
detailViewController.deadstick = selectedDeadStick
detailViewController.navigationItem.leftBarButtonItem = nil
default:
fatalError("Unexpected Segue Identifier; \(String(describing: segue.identifier))")
}
}
@IBAction func unwindAircraftData(segue: UIStoryboardSegue) {
guard segue.identifier == "saveUnwind" else {
tableView.selectRow(at: selectedItem, animated: true, scrollPosition: .none)
return }
let sourceViewController = segue.source as!
DetailViewController
if let deadstick = sourceViewController.deadstick {
if let selectedIndexPath =
tableView.indexPathForSelectedRow {
deadsticks[selectedIndexPath.row] = deadstick
tableView.reloadRows(at: [selectedIndexPath],
with: .none)
tableView.selectRow(at: selectedIndexPath, animated: true, scrollPosition: .none)
DeadStick.saveDeadSticks(deadsticks)
} else {
let newIndexPath = IndexPath(row: deadsticks.count,
section: 0)
deadsticks.append(deadstick)
tableView.insertRows(at: [newIndexPath],
with: .automatic)
DeadStick.saveDeadSticks(deadsticks)
tableView.selectRow(at: newIndexPath, animated: true, scrollPosition: .none)
}
}
}
}
I do not see a reason for this not to work.
So, the best would be to continue by mail, so that you can send me the complete code, if you don't mind.
Could you post your email ?
OK, received. I've sent you an email, to send me the complete project folder.
OK, the problem is that selectRow(at:) does not change selectedRow !!!
See documentation :
Instance Method
selectRow(at:animated:scrollPosition:)
Selects a row in the table view identified by index path, optionally scrolling the row to a location in the table view.
Discussion
Calling this method does not cause the delegate to receive a
ortableView(_:willSelectRowAt:)
message, nor does it sendtableView(_:didSelectRowAt:)
notifications to observers.UITableViewSelectionDidChange
To correct this, you can implement the following changes.
When you delete the row, it will show the row below the deleted one in Detail view
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
deadsticks.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
// Array.function(data).
DeadStick.saveDeadSticks(deadsticks)
// ### ADD THIS
if indexPath.row < tableView.numberOfRows(inSection: indexPath.section) {
self.tableView.selectRow(at: indexPath, animated: true, scrollPosition: .top)
selectedItem = indexPath // ### ADDED THIS
self.performSegue(withIdentifier: "showDetail", sender: self)
} else {
if tableView.numberOfRows(inSection: indexPath.section) > 0 {
self.tableView.selectRow(at: IndexPath(row: 0, section: indexPath.section), animated: true, scrollPosition: .top)
selectedItem = IndexPath(row: 0, section: indexPath.section) // ### ADD THIS
self.performSegue(withIdentifier: "showDetail", sender: self)
}
}
}
}
Complement prepare as follows :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch(segue.identifier ?? "") {
case "addDetail":
if let index = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: index, animated: true)
}
detailViewController?.previousSelectionPath = selectedItem
os_log("Adding New Aircraft.", log: OSLog.default, type: .debug)
case "showDetail":
let detailViewController = (segue.destination as! UINavigationController).topViewController as! DetailViewControlle
// ### REPLACE let indexPath = tableView.indexPathForSelectedRow!
let indexPath = tableView.indexPathForSelectedRow ?? selectedItem
let selectedDeadStick = deadsticks[indexPath.row]
detailViewController.deadstick = selectedDeadStick
detailViewController.navigationItem.leftBarButtonItem = nil
default:
fatalError("Unexpected Segue Identifier; \(String(describing: segue.identifier))")
}
}
Here's hoping you at least get a "correct response" point if not a special seat in heaven for this good deed. I admire your dedication to....
And I learned something about what selectRow does and more important does not do !
Thanks for your kind post.
I admire his dedication also. I didn't realize this would be such a complicated question.
I wonder if I should have stuck with my profession, accounting, rather than trying to create an app.
I found the solution to selecting the row. The code above selects the first row after deleting an item but clicking the done button is where the select row belongs to highlight the row.
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if editing {
} else {
tableView.selectRow(at: selectedItem, animated: true, scrollPosition: .none)
}
}
I guess you'll have to stay with 3,220 points. h.dale gets the "correct answer. (the second quotation mark was left out - too subtle?)
Yes 😁. And effectively, this works well.
If found by himself, congratulations to Hal; otherwise it would have been useful to mention where he found the solution. There may be some additional information beyond just the solution.
It works, it works well. Better than going to detail view.
However, in terms of UI, I'm not convinced it is a good solution so select a cell after deletion.
Consider the scenario: the user deletes a row that was selected. When he/she has done, he/she sees a different selection being done in his/her back; may even wonder if deletion took place. And whyis this cell selected and not that cell ? What does that mean to him/her ? Will Apple accept it ?
It is probably intentional in UIKit that no cell is selected after a delete.
So, it would probably be better to just leave it as is, with no cell selected at all. Up to the user to make a new selection if that's what he/she wants.