iosswiftstoryboardtableviewsegue

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


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.


Solution

  • 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])
    }