Storyboard, Segues. What should I do if I want to use two segues from one element?

73 Views Asked by At

This is my first question here. I have TableViewController and I want to use two segues from one raw. One segue should work when you tapped a row, and the second is action from this row. Every of them need to show different ViewControllers and I don't understand how can I do that because I can't to create two segues from one row. The problem is that both of cases need to call prepare function and it called only with segue, and it does not called when I use performSegue.

    override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let editAction = UIContextualAction(style: .normal, title: "Add"){(_,_, completionHandler) in
            self.performSegue(withIdentifier: "editQuiz", sender: self)
            tableView.reloadData()
        }
        let deleteAction = UIContextualAction(style: .destructive, title: "Delete"){(_,_, completionHandler) in
            CoreDataManager.shared.deleteSomeQuizData(data: CoreDataManager.shared.quizzes[indexPath.row], indexNumber: indexPath.row)
            self.navigationItem.title = "\(CoreDataManager.shared.quizzes.count) quizzes"
            tableView.reloadData()
        }
        return UISwipeActionsConfiguration(actions: [deleteAction, editAction])
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let indexPath = tableView.indexPathForSelectedRow else {return}
        print(segue.source)
        if segue.identifier == "editQuiz" {
            guard let destination = segue.destination as? AddWordsViewController else{return}
        }
        if segue.identifier == "showQuiz" {
            guard let destination = segue.destination as? QuizViewController else{return}
            destination.from = Int(CoreDataManager.shared.quizzes[indexPath.row].from)
            destination.to = Int(CoreDataManager.shared.quizzes[indexPath.row].to)
        }
    }

prepare(for segue:) works only when it called from row segue to another ViewController and it didn't called with performSegue. Also if I create both of segue from TableViewController to ViewControllers and don't call performSegue, transition doesn't work.

All of the segues identifiers set correctly.

Even if I try

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.performSegue(withIdentifier: "showQuiz", sender: self)
    }

prepare(for segue:) is calling, but in

let editAction = UIContextualAction(style: .normal, title: "Add"){(_,_, completionHandler) in
            self.performSegue(withIdentifier: "editQuiz", sender: self)
            tableView.reloadData()
        }

it does not calling.

1

There are 1 best solutions below

0
DonMag On BEST ANSWER

It looks like you have almost everything setup correctly, except...

In your prepare for segue code, the first thing you do is check for the selected row:

guard let indexPath = tableView.indexPathForSelectedRow else {return}

If you call performSegue from your "Edit" action, the table view will NOT HAVE a selected row.

A bit tough for me to test because I don't have all of your data management and destination controllers, but this should fix the issue (if you have your segues setup correctly in Storyboard):

override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    let editAction = UIContextualAction(style: .normal, title: "Add"){(_,_, completionHandler) in
        
        // instead of passing self as sender
        //self.performSegue(withIdentifier: "editQuiz", sender: self)
        
        // pass the indexPath
        self.performSegue(withIdentifier: "editQuiz", sender: indexPath)
        
        // no need to reload data here
        //tableView.reloadData()
    }
    let deleteAction = UIContextualAction(style: .destructive, title: "Delete"){(_,_, completionHandler) in
        CoreDataManager.shared.deleteSomeQuizData(data: CoreDataManager.shared.quizzes[indexPath.row], indexNumber: indexPath.row)
        self.navigationItem.title = "Title \(indexPath.row)" // "\(CoreDataManager.shared.quizzes.count) quizzes"
        tableView.reloadData()
    }
    return UISwipeActionsConfiguration(actions: [deleteAction, editAction])
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    if segue.identifier == "editQuiz" {
        // do something before segue to AddWordsViewController
        if let indexPath = sender as? IndexPath {
            print("editQuiz with IndexPath: \(indexPath)")
        }
    }
    if segue.identifier == "showQuiz" {
        guard let indexPath = tableView.indexPathForSelectedRow,
              let destination = segue.destination as? QuizViewController
        else {
            // note: this will NOT Stop the segue
            return
        }
        destination.from = Int(CoreDataManager.shared.quizzes[indexPath.row].from)
        destination.to = Int(CoreDataManager.shared.quizzes[indexPath.row].to)
    }
    
}

As I mentioned in my comment: "To give yourself the most control, don't use segues in that situation..."

To do that, delete the segues from your Storyboard.

Instead, use code like this to instantiate the "destination" view controllers as needed:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    
    // instantiate QuizViewController
    if let vc = self.storyboard?.instantiateViewController(withIdentifier: "QuizViewController") as? QuizViewController {

        vc.from = Int(CoreDataManager.shared.quizzes[indexPath.row].from)
        vc.to = Int(CoreDataManager.shared.quizzes[indexPath.row].to)
        
        self.navigationController?.pushViewController(vc, animated: true)

    }
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
    let editAction = UIContextualAction(style: .normal, title: "Add"){(_,_, completionHandler) in
        
        // instantiate AddWordsViewController
        if let vc = self.storyboard?.instantiateViewController(withIdentifier: "AddWordsViewController") as? AddWordsViewController {

            // do something before showing AddWordsViewController
            self.navigationController?.pushViewController(vc, animated: true)

        }
        
    }
    let deleteAction = UIContextualAction(style: .destructive, title: "Delete"){(_,_, completionHandler) in
        CoreDataManager.shared.deleteSomeQuizData(data: CoreDataManager.shared.quizzes[indexPath.row], indexNumber: indexPath.row)
        self.navigationItem.title = "Title \(indexPath.row)" // "\(CoreDataManager.shared.quizzes.count) quizzes"
        tableView.reloadData()
    }
    return UISwipeActionsConfiguration(actions: [deleteAction, editAction])
}