Protocol question?

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

tableView(_:willSelectRowAt:)
or
tableView(_:didSelectRowAt:)
message, nor does it send
UITableViewSelectionDidChange
notifications to observers.




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.