skip to Main Content

I have a user object that I am getting its values from firebase and I want to pass this object basically in all of my other view controllers. I am using storyboard and I looked up how to do this and I found out that I can override the prepare method, I wasn’t successful with that as I did not know how to call the method or if it was ever called, it just didn’t work. Then I found that you can just assign a vc to another view controller and pass data like that but I hit an issue:

In HomeViewController, I have this method that gets data from firebase and assign it to user:

extension HomeViewController {
public func AssignValueToUserObject() {
    guard let uid = Auth.auth().currentUser?.uid else {
        print("Could not get user id")
        return
    }
    
    Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { [self] snapshot in
        if let dictionary = snapshot.value as? [String: AnyObject] {
            
            user.first_name = dictionary["first_name"] as? String
            user.last_name = dictionary["last_name"] as? String
            user.email = dictionary["email"] as? String
            user.profile_picture = dictionary["profile_picture"] as? String
        }
    }, withCancel: nil)
} // End AssignValueToUserObject Method
} // End extension

And this is what I have in HomeViewController to copy that user object to my ProfileViewController:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    title = "Home"
    checkIfUserIsLoggedIn()
    copyData()
}

func checkIfUserIsLoggedIn() {
    // Check if user is logged in
    if Auth.auth().currentUser == nil {
        // User is not logged in, send user to login screen
        let loginVC = storyboard?.instantiateViewController(identifier: "login")
        loginVC?.modalPresentationStyle = .fullScreen
        present(loginVC!, animated: false)
    }
    
    // User is logged in, fetch their info
    AssignValueToUserObject()

    } // End checkIfUserIsLoggedIn method

// Copy user from Home to Profile
func copyData() {
    let vc = storyboard?.instantiateViewController(identifier: "profile") as? ProfileViewController
    vc?.user = user
}

After debugging, I found the BIG problem, which is that the copy method gets called BEFORE the values gets assigned to user in the AssignValueToUserObject method, which to me makes absolutely no sense.

I call the assign method before the copy method so how does that work? After some research, I figured out it has something to do with completion handling but I just don’t get it.

2

Answers


  1. As mentioned in the comments, with asynchronous functions, you can’t expect a return value right away. One common way to handle this is by using a callback function or completion handler.

    I’m including a very basic example of this. Note that I’m not doing any error handling right now — you’d want to build it out to be more robust, but this at least gets the concept:

    extension HomeViewController {
        public func assignValueToUserObject(completion: @escaping () -> Void) { //completion handler gets passed as an parameter
            guard let uid = Auth.auth().currentUser?.uid else {
                print("Could not get user id")
                return
            }
            
            Database.database().reference().child("users").child(uid).observeSingleEvent(of: .value, with: { [self] snapshot in
                if let dictionary = snapshot.value as? [String: AnyObject] {
                    
                    user.first_name = dictionary["first_name"] as? String
                    user.last_name = dictionary["last_name"] as? String
                    user.email = dictionary["email"] as? String
                    user.profile_picture = dictionary["profile_picture"] as? String
                    completion() //call once the action is done
                }
            }, withCancel: nil)
        } // End AssignValueToUserObject Method
    } // End extension
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        title = "Home"
        checkIfUserIsLoggedIn()
        //don't call copy here anymore
    }
    
    func checkIfUserIsLoggedIn() {
        // Check if user is logged in
        if Auth.auth().currentUser == nil {
            // User is not logged in, send user to login screen
            let loginVC = storyboard?.instantiateViewController(identifier: "login")
            loginVC?.modalPresentationStyle = .fullScreen
            present(loginVC!, animated: false)
        }
        
        // User is logged in, fetch their info
        assignValueToUserObject(completion: {
          self.copyData() //only copy once the completion handler is run
        })
        
    } // End checkIfUserIsLoggedIn method
    

    Update, showing a way to use a singleton to monitor a user value in different view controllers:

    import Combine
    
    struct User {
        var id : UUID //whatever properties your user model has
    }
    
    class UserManager {
        @Published var user : User?
        static public var shared = UserManager()
        private init() {
            
        }
        
        func login() {
            //do your firebase call here and set `user` when done in the completion handler
            self.user = User(id: UUID())
        }
    }
    
    class HomeViewController : UIViewController {
        private var userManager = UserManager.shared
        private var cancellable : AnyCancellable?
        
        init() {
            super.init(nibName: nil, bundle: nil)
            setupUserLink() //make sure this gets called in whatever initializer is used
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            setupUserLink() //make sure this gets called in whatever initializer is used
        }
        
        func setupUserLink() {
            cancellable = userManager.$user.compactMap { $0 }.sink { user in
                print(user.id.uuidString) //do something with the user value -- assign it to a variable, a control, etc
            }
        }
    }
    
    class ProfileViewController : UIViewController {
        //do the same pattern as in HomeViewController, setting up the user link to be monitored
    }
    
    Login or Signup to reply.
  2. Try this

    func copyData() {
        let storyboard = UIStoryboard(name: "Your Storyboard Name", bundle: nil)
        let vc = storyboard.instantiateViewController(identifier: "profile") as? ProfileViewController
        vc?.user = user
        self.navigationController?.pushViewController(vc, animated: true)
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search