I'm not able to pass a value to the next ViewController

I’m working on this app that runs 2 different queries to Firebase. The first Firebase query displays the data inside a pickerView, the second is a query to a “users” collection and pulls a custom field. Both queries are working as expected, pickerView displays the info and I’m able to print the results of the user’s collection to the console. The issue I’m having is that I’m trying to pass the value inside the custom field userSpecialtyCode to the next VC and is not working. I'm not sure what I'm missing. Any help is greatly appreciated. Here is my code:



class PatfilterVC: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate {

  @IBOutlet weak var hospNameTxt: UITextField!
  @IBOutlet weak var getDataBtn: UIButton!
  @IBOutlet weak var infoPickerViewer: UIPickerView!

  private var textFieldSelected = UITextField()
  private var pickerView: UIPickerView?
  private var handle: AuthStateDidChangeListenerHandle?
  private var userListener: ListenerRegistration!

  private var hospitalClass = [HospitalList]()
  private var hospCollectionRef: CollectionReference!
  private var hospCode: HospitalList! = nil

  private var loggedInUserClass = [UserSpecialty]()
  private var usersCollectionRef: CollectionReference!
  private var currentUserSpecialty: UserSpecialty! = nil

  override func viewDidLoad() {
  super.viewDidLoad()
  overrideUserInterfaceStyle = .light

  let pickerView = UIPickerView()
  infoPickerViewer.delegate = self
  infoPickerViewer.dataSource = self

  hospNameTxt.inputView = pickerView
  hospNameTxt.delegate = self

  self.infoPickerViewer = pickerView
  self.infoPickerViewer?.delegate = self
  self.infoPickerViewer?.dataSource = self

  self.infoPickerViewer?.reloadAllComponents()

  hospCollectionRef = Firestore.firestore().collection(HOSPITAL_REF)
  usersCollectionRef = Firestore.firestore().collection(USERS_REF)

  handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
  if user == nil {
  self.getDataBtn.isUserInteractionEnabled = false
  self.infoPickerViewer.isUserInteractionEnabled = false
  } else {
  self.getDataBtn.isUserInteractionEnabled = true
  self.infoPickerViewer.isUserInteractionEnabled = true
  self.setListener()
  }
  })

  let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIInputViewController.dismissKeyboard))
  view.addGestureRecognizer(tap)

  getHospitalList()
  getUserSpecialty()

  }


  @objc func dismissKeyboard() {
  view.endEditing(true)
  }

  override func viewWillAppear(_ animated: Bool) {
  handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
  if user == nil {

  let storyboard = UIStoryboard(name: "Main", bundle: nil)
  let LoginVC = storyboard.instantiateViewController(withIdentifier: "LoginVC")
  self.present(LoginVC, animated: true, completion: nil)
  } else {
  self.setListener()
  }
  })
  }

  override func viewWillDisappear(_ animated: Bool) {
  if userListener != nil {
  userListener.remove()
  }
  }

  func setListener() {
  }

  func numberOfComponents(in pickerView: UIPickerView) -> Int {
  return 1
  }

  func textFieldDidBeginEditing(_ textField: UITextField) {
  textFieldSelected = textField
  }

  func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
  self.pickerView?.reloadAllComponents()
  return true
  }
  func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
  return hospitalClass.count
  }

  func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
  return hospitalClass[row].hospitalName
  }

  func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
  hospCode = hospitalClass[row]
  let hosp = hospitalClass[row].hospitalName
  hospNameTxt.text = hosp
  }

  @IBAction func getDataTapped(_ sender: Any) {
  guard hospCode != nil else {return}  //I'm able to pass this to ResultsdataVC
  guard currentUserSpecialty != nil else {return} //I'm not able to pass, value returns nil 

  performSegue(withIdentifier: "goToResults", sender: self)
  }

  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if segue.identifier == "goToResults" {
  let vc = segue.destination as! ResultsdataVC
  if let hosp = hospCode {
  vc.hospCodeFromVC = hosp.hospitalCode
  }
  if let user = currentUserSpecialty {
  vc.userSpecFromVC = user.userSpecialtyCode
  }
  }
  }

  //this query displays in the pickerView

  func getHospitalList() {
  let hospListQuery = hospCollectionRef?
  .order(by: HOSPITAL_NAME, descending: false)

  hospListQuery?.getDocuments { (snapshot, error) in
  if let err = error {
  debugPrint("error fetching docs: \(err)")
  } else {
  self.infoPickerViewer.reloadAllComponents()
  let snap = snapshot
  for document in snap!.documents {
  let data = document.data()
  let hospitalCode = data[HOSPITAL_CODE] as? String ?? ""
  let hospitalName = data[HOSPITAL_NAME] as? String ?? ""

  let hospList = HospitalList(hospitalCode: hospitalCode, hospitalName: hospitalName)

  self.hospitalClass.append(hospList)
  }

  }
  }
  }

  //this query gets the values from Firebase but i cannot pass userSpecialtyCode
  // to the next viewController

  func getUserSpecialty() {
  if let loggedInUser = Auth.auth().currentUser?.uid {
  let docRef = usersCollectionRef.document(loggedInUser)
  docRef.getDocument { ( document, error) in
  if let document = document, document.exists {
  let data = document.data()
  let userSpecialtyCode = data?[SPECIALTY_CODE] as? String ?? ""
  let loggedInUserId = data?[LOGGED_IN_USER_ID] as? String ?? ""

  let loggedUser = UserSpecialty(userSpecialtyCode: userSpecialtyCode, loggedInUserId: loggedInUserId)

  self.loggedInUserClass.append(loggedUser)
  print("specCode", userSpecialtyCode as Any) //IT PRINTS THE CORRECT VALUE
  print("loggedInUser", loggedInUserId as Any) //IT PRINTS THE USER ID
  } else {
  print("no document")
  }
  }
  }
  }


  @IBAction func logoutTapped(_ sender: Any) {
  let firebaseAuth = Auth.auth()
  do {
  try firebaseAuth.signOut()

  } catch let signoutError as NSError {
  debugPrint("Error signing out: \(signoutError)")
  }
  }
}

Replies

You should first check if you pass correctly in prepare


  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
       print(#function, "goToResults")          // Test if prepare is called
       if segue.identifier == "goToResults" {
            print(#function, "identifier recognized", segue.identifier)          // Test if identifier is recognized
            let vc = segue.destination as! ResultsdataVC
            if let hosp = hospCode {
                 vc.hospCodeFromVC = hosp.hospitalCode
                 print("hospCode found", hosp, hosp.hospitalCode)
            }
            if let user = currentUserSpecialty {
                 vc.userSpecFromVC = user.userSpecialtyCode
                 print("user found", user, user.userSpecialtyCode)
           }
  }


And tell what you get.


Could you show the code of ResultsdataVC ?

Hi Claude, i did what you suggested and nothing happened. Prepare or segue does not recognize the value inside userSpecialtyCode. Now, If I disable the lines in prepare for segue and perform segue to pass userSpecialtyCode, i get the folllowing:


@IBActionfunc getDataTapped(_ sender: Any) {
        guard hospCode != nil else {return}
        //guard currentUserSpecialty != nil else {return}
      
        performSegue(withIdentifier: "goToResults", sender: self)
    }
   
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        print(#function, "goToResults")
        if segue.identifier == "goToResults" {
            print(#function, "identifier recognized", segue.identifier)
            let vc = segue.destination as! ResultsdataVC
            if let hosp = hospCode {
                vc.hospCodeFromVC = hosp.hospitalCode
                print("hospCode found", hosp, hosp.hospitalCode as Any)
            }
//            if let user = currentUserSpecialty {
//                vc.userSpecFromVC = user.userSpecialtyCode
//                print("user found", user, user.userSpecialtyCode as Any)
//            }
        }
    }



printout from console:


prepare(for:sender:) goToResults

prepare(for:sender:) identifier recognized Optional("goToResults")

hospCode found EZ_MD_App.HospitalList Optional("BH")



and in ResultsdataVC I have 2 variables for hospCodeFromVC and userSpecFromVC:


class ResultsdataVC: UIViewController, UITableViewDataSource, UITableViewDelegate, PatListCellDelegate {
   
    @IBOutlet private weak var resultsTableView: UITableView!
    @IBOutlet private weak var patientFilter: UISegmentedControl!
    @IBOutlet private weak var searchBar: UISearchBar!
   
    var hospCodeFromVC : String?
    var userSpecFromVC : String?


inside ViewDidLoad I have this to make sure the values are coming across:


        print("USER SPECIALTY", userSpecFromVC asAny)
        print("HOSP FROM VC", hospCodeFromVC asAny)

USER SPECIALTY nil

HOSP FROM VC Optional("BH")

It is a bit difficult to follow.


In the previous version of code:

i did what you suggested and nothing happened. Prepare or segue does not recognize the value inside userSpecialtyCode.


What did you get exactly ?

Was prepare called ?

Was IBAction called ?


Probably, you have conflicting IBAction and Segue (you would not be the first!):

How did you connect the segue ?

It should be a segue from the viewController to the viewController, not a direct segue from the button to the destination. Otherwise, prepare is not called (in fact, segue triggers directly, without even executing IBAction).

If i leave prepare for segue as is, including lines 2, 4 , 8 and 12 and I press the button to run getDataTapped nothing happens. Prepare for segue does not run because it's expecting a value inside userSpecialtyCode .


  override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 
       print(#function, "goToResults")          // Test if prepare is called 
       if segue.identifier == "goToResults" { 
            print(#function, "identifier recognized", segue.identifier)          // Test if identifier is recognized 
            let vc = segue.destination as! ResultsdataVC 
            if let hosp = hospCode { 
                 vc.hospCodeFromVC = hosp.hospitalCode 
                 print("hospCode found", hosp, hosp.hospitalCode) 
            } 
            if let user = currentUserSpecialty { 
                 vc.userSpecFromVC = user.userSpecialtyCode 
                 print("user found", user, user.userSpecialtyCode) 
           } 
  }

There are no conflicts with IBAction nor Segue. The segue is from VC to VC.


If i run the code like this:


@IBActionfunc getDataTapped(_ sender: Any) { 
        guard hospCode != nil else {return} 
        performSegue(withIdentifier: "goToResults", sender: self) 
    } 
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 
        print(#function, "goToResults") 
        if segue.identifier == "goToResults" { 
            let vc = segue.destination as! ResultsdataVC 
            if let hosp = hospCode { 
                vc.hospCodeFromVC = hosp.hospitalCode 
            }  
        } 
    }

it works with no issues, prepare for segue grabs what's inside hospitalCode and segues to the next VC, in this case ResultsdataVC.

my question was:

do you get

print(#function, "goToResults") // Test if prepare is called

printed.


You say you did not in first case ?


Well, what is the remaining problem, not clear for me.

Is it

//guard currentUserSpecialty != nil else {return}

which does not allow to pass the value ?

This:


print(#function, "goToResults")          // Test if prepare is called printed


and


print(#function, "identifier recognized", segue.identifier)          // Test if identifier is recognized


Generates this:


prepare(for:sender:) goToResults

prepare(for:sender:) identifier recognized Optional("goToResults")



Correct, the value in currentUserSpecialty is nil, this is the issue:

guard currentUserSpecialty != nil else {return}


when I run the query getUserSpecialty() i get the value inside currentUserSpecialty , i just cannot pass it to the other VC.

currentUserSpecialty is nowhere set in the code you posted. Is it set elsewhere ?

original post, line #18.

It is set to nil, that's the problem.


I meant, set to a non nil value.

no, that is the only place where I have currentUserSpecialty . It was setup as a nil becuase there is no value until the query to Firebase runs. Once the query runs, there is a value that should go inside currentUserSpecialty.


If this is the issue, what is your recommendation? to my knowledge (and I'm still learning) i don't know how to add a property to hold the value for currentUserSpecialty.

I don't know what your data model is.

What should this var contain ?

Where should it get it precisely ?

You wrote: until the query to Firebase runs

Where is this query in your code ?

Maybe you should call perform after completion of query (need to see code for this).


But,

- if you set only the value to nil

- it does not make sens to test for non nil later !

- as you saw, that just skips the rest of func.


So my advice:

- find how to retrieve this data, through some query

- if not possible eliminate the test (but that would mean eliminate also its display in the destination, which may not be an option)

data model:

class UserSpecialty {

    private(set) var userSpecialtyCode: String?
    private(set) var loggedInUserId: String?
   
    init(userSpecialtyCode: String, loggedInUserId: String) {

        self.userSpecialtyCode = userSpecialtyCode
        self.loggedInUserId = loggedInUserId

    }
}


Both queries are in the original post:

- getHospitalList() - Line 133

- getUserSpecialty() - Line 160


this query getUserSpecialty() generates this:


MEDONC (this is the value that needs to go inside currentUserSpecialty)

tAn4uCy0aLP5YUNS5HID1HiXXXXX


There is comething curious here (even after correctly indenting code!):


func getUserSpecialty() {
    if let loggedInUser = Auth.auth().currentUser?.uid {
        let docRef = usersCollectionRef.document(loggedInUser)
        docRef.getDocument { ( document, error) in
            if let document = document, document.exists {
                let data = document.data()
                let userSpecialtyCode = data?[SPECIALTY_CODE] as? String ?? ""
                let loggedInUserId = data?[LOGGED_IN_USER_ID] as? String ?? ""
               
                let loggedUser = UserSpecialty(userSpecialtyCode: userSpecialtyCode, loggedInUserId: loggedInUserId)
               
                self.loggedInUserClass.append(loggedUser)
                print("specCode", userSpecialtyCode as Any) //IT PRINTS THE CORRECT VALUE
                print("loggedInUser", loggedInUserId as Any) //IT PRINTS THE USER ID
            } else {
                print("no document")
            }
        }
    }
}

Line 7, you get a userSpecialtyCode, and line 10 a loggedUser (UserSpecialty), but they are a local values: all lost when you exit the func.


Why don't you set currentUserSpecialty here ?

With someting like (hope I understood correctly all the code):

currentUserSpecialty = loggedUser

Any comment to this ?

Line 7, you get a userSpecialtyCode, and line 10 a loggedUser (UserSpecialty), but they are a local values: all lost when you exit the func.


Why don't you set currentUserSpecialty here ?