Home > OS >  Swift how to trigger "prepare for segue" before target view load?
Swift how to trigger "prepare for segue" before target view load?

Time:09-17

I am trying to load data from firestore(google database) and want to show on tableview.

so in the first VC, by prepare function, get data from database, and transfer to second VC(tableview). but There is one problem. I learned that prepare function goes before viewdidload, in my application, prepare function goes after second VC load.

here's my code.

first VC

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let docRef = db.collection("Posting").getDocuments(){(querySnapshot, err) in
        if let err = err{
            print("errror getting documents")
        }else{
            for document in querySnapshot!.documents{
                print("\(document.documentID) => \(document.data())")
                self.savePostings.append(document.data())
            }
            print("\n\n\(self.savePostings)")
        }
        let vc = segue.destination as! PostingListTableViewController
        vc.updatedPostings = self.savePostings
        vc.testPrint = "잉 기모찌"
        print("배열 전달 확인\n\(vc.updatedPostings)\n\n\(self.savePostings)")
    }
    
}

Second VC (Tableview)

class PostingListTableViewController: UITableViewController {

//private var postings: Array<[String:Any]> = []
private var documents: [DocumentSnapshot] = []
var updatedPostings: Array<[String:Any]?> = []
var testPrint:String = ""

override func viewDidLoad() {
    super.viewDidLoad()
    print("view did load")
    // Uncomment the following line to preserve selection between presentations
    // self.clearsSelectionOnViewWillAppear = false

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem
}

// MARK: - Table view data source



override func numberOfSections(in tableView: UITableView) -> Int {
    // #warning Incomplete implementation, return the number of sections
    return 1
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // #warning Incomplete implementation, return the number of rows
    return updatedPostings.count
}


override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "myTableCell", for: indexPath)
    cell.textLabel?.text = updatedPostings[indexPath.row]!["text"] as! String

    // Configure the cell...

    return cell
}

}

CodePudding user response:

as @vadian correctly said, your problem is that you're making an async call.

prepare(for segue is called before viewDidLoad, but you're updating your properties some time after that, when your request finishes, and that's after viewDidLoad.

Instead of that I suggest you the following:

  1. Remove your segue, add identifier to the destination view controller

  2. Inside tableView:didSelectRowAtIndexPath: run your getDocuments(or inside IBAction if this is a button segue)

    2.1. you can show some progress indicator so user wold know the reason of delay

  3. In completion create your view controller from storyboard using instantiateViewControllerWithIdentifier and present it manually. You don't need to wait for prepare(for segue to set your properties in this case.

If your segue is calling from the cell, you can add your view controller as a delegate, like this:

then you need to conform your view controller to UITableViewDelegate, and didSelectRowAt method will be called when user press a cell. You can get cell number from indexPath.row

extension PostingListTableViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let docRef = db.collection("Posting").getDocuments(){(querySnapshot, err) in
            if let err = err{
                print("errror getting documents")
            }else{
                for document in querySnapshot!.documents{
                    print("\(document.documentID) => \(document.data())")
                    self.savePostings.append(document.data())
                }
                print("\n\n\(self.savePostings)")
            }
            // not sure if completion of getDocuments is called on main thread, if it does - you don't need this line
            DispatchQueue.main.async {
                let vc = storyboard!.instantiateViewController(identifier: "storyboard_identifier") as! PostingListTableViewController
                vc.updatedPostings = self.savePostings
                vc.testPrint = "잉 기모찌"
                present(vc, animated: true)
                print("배열 전달 확인\n\(vc.updatedPostings)\n\n\(self.savePostings)")
            }
        }
    }
}

If you're performing this segue from a plain button, not from a cell, you can do the same with @IBAction:

@IBAction @objc func push() {
    let docRef = db.collection("Posting").getDocuments(){(querySnapshot, err) in
                if let err = err{
                    print("errror getting documents")
                }else{
                    for document in querySnapshot!.documents{
                        print("\(document.documentID) => \(document.data())")
                        self.savePostings.append(document.data())
                    }
                    print("\n\n\(self.savePostings)")
                }
                // not sure if completion of getDocuments is called on main thread, if it does - you don't need this line
                DispatchQueue.main.async {
                    let vc = storyboard!.instantiateViewController(identifier: "storyboard_identifier") as! PostingListTableViewController
                    vc.updatedPostings = self.savePostings
                    vc.testPrint = "잉 기모찌"
                    present(vc, animated: true)
                    print("배열 전달 확인\n\(vc.updatedPostings)\n\n\(self.savePostings)")
                }
            }
}

CodePudding user response:

What I would do first is to DELETE ❌ the segue you've probably created by drag and drop from the button on VC1 to VC2. Replace it by a regular segue which is not attached to any UI component so that you can trigger it manually in code (from its identifier, don't forget to provide one). It will allow you to first perform some asynchronous code, and only when getting the callback trigger the navigation (if data fetch is successful).

To create such a segue:

  1. click on your VC1 in the storyboard => A small rectangle with icons should be now displayed right above the VC1
  2. Ctrl drag from the yellow icon (probably at the most left of that rectangle) to the VC2 (on main view, does not rally matter where you drop it as long as it is on VC2)
  3. Provide identifier after having clicked on the newly created segue so that you can trigger it from code

That was for the segue, but now, when to trigger it?

  1. Create an an @IBAction on the button which is suppose to trigger the fetch navigation (or equivalent, like didSelectRowAtIndexPath)
  2. This IBAction should call another method like the following:
private func fetchPostingAndNavigateIfSuccessful() {
   // Should probably first set the activity indicator to `.startAnimating()`
   let docRef = db.collection("Posting").getDocuments() { [weak self] querySnapshot, error in
       // Should probably set the activity indicator to `.stopAnimating()`
       guard error != nil,
             let documents = querySnapshot?.documents else {
             print("error getting documents") //probably a good place to display an alert
             return
       }
       let postingsData = documents.map { $0.data() }
       self?.performSegue(withIdentifier: "NavigateToPostingsSegue", sender: postingsData)
   }
}

Your prepare for segue would then look like that:


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    super.prepare(for: segue, sender: sender)
    if let postingListTableViewController = segue.destination as? PostingListTableViewController,
       let postingsData = sender as? [[String:Any]] {
        postingListTableViewController.updatedPostings = postingsData
    }
}

  • Related