Search code examples
iosjsonswiftasynchronousuistoryboardsegue

Swift 3.0 iOS10 Passing Async data through a segue sender for Firebase


I have a log in page that collects a username and password. On submit, Its sends to the database to retrieve our servers access key. I do this through an asynchronous JSON POST using session.dataTask. When I retrieve the JSON Object I parse the key out of it. I want to pass it to the next page, retrieve a firebase token and then send both pieces of data back to the server for DB storage. I have created a "prepare for segue" function that collects the variable and passes it to a variable on the next page. I believe I am not setting up the sequence of events correctly or that the data isn't making it out of the Async container. Can someone have a look at these two files and see where I am getting it wrong?

Here is the first page I want to segue away from after making the REST web service call...

loginVC.swift:

import UIKit

class LoginVC: UIViewController {

    @IBOutlet weak var username: UITextField!
    @IBOutlet weak var password: UITextField!
    @IBOutlet weak var validationBox: UITextView!
    @IBAction func logInAction(_ sender: UIButton) {
        guard let user = username.text, !user.isEmpty else {
            validationBox.text = "Please enter valid credentials"
            return
        }
        guard let pass = password.text, !pass.isEmpty else {
            validationBox.text = "Please enter valid credentials"
            return
        }

        let params = ["sUser": username.text!, "sPass": password.text!]

        let url = URL(string: "restWebServiceURL")!
        let session = URLSession.shared
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        do {
            request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
        } catch let error {
            print(error.localizedDescription)
        }
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.addValue("application/json", forHTTPHeaderField: "Accept")

        let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in

            guard error == nil else { return }
            guard let data = data else { return }

            do {
                if let parsedJSON = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
                    let parsedData = parsedJSON["d"] as! [String:Any]
                    let key = parsedData["key"] as! String
                    DispatchQueue.main.async {
                        print(key)
                        self.performSegue(withIdentifier: "FirebaseVC", sender: key)
                    }

                }
            } catch let error {
                print(error.localizedDescription)
            }
        })
        task.resume()
    }
    func sayHello() {
        print("Hello!")
    }
    func sayGoodbye() {
        print("Goodbye!")
    }



    override func viewDidLoad() {
        super.viewDidLoad()
        validationBox.text = "Ready..."
        func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if let FirebaseInit = segue.destination as? FirebaseVC {
                if let sKey = sender as? String {
                    print("prepare - " + sKey)
                    FirebaseInit.sessionKey = sKey
                }
            }
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

Here is the page I want to go to to receive the data access key ...

FirebaseVC.swift:

import UIKit

class FirebaseVC: UIViewController {

    private var _sessionKey = String()

    var sessionKey : String {
        get { return _sessionKey }
        set { _sessionKey = newValue }
    }

    @IBOutlet weak var sessionKeyTestBox: UITextView!

    override func viewDidLoad() {
        super.viewDidLoad()
        print(_sessionKey)

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }



}

Feel free to suggest a better way to pass the data to the next page. Thanks...


Solution

  • It turns out I was correct in my assumption the the chain of events was off. Following the model suggested by @achrefGassoumi, I moved the datatask to a Singleton Service here:

    import Foundation
    
    struct CallWebService {
    
        static let sharedInstance = CallWebService()
    
        func logInToCaduceus(u: String, p: String, completion: @escaping (_ sKey: String) -> ()) {
            let params = ["sUser": u, "sPass": p]
    
            let url = URL(string: "https://telemed.caduceususa.com/ws/telemed.asmx/telemedLogin")!
            let session = URLSession.shared
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            do {
                request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
            } catch let error {
                print(error.localizedDescription)
            }
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            request.addValue("application/json", forHTTPHeaderField: "Accept")
    
            let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
    
                guard error == nil else { return }
                guard let data = data else { return }
    
                do {
                    if let parsedJSON = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
                        let parsedData = parsedJSON["d"] as! [String:Any]
                        let key = parsedData["key"] as! String
                        DispatchQueue.main.async {
                            completion(key)
                        }
                    }
                } catch let error {
                    print(error.localizedDescription)
                }
            })
            task.resume()
    
        }
    
    }
    

    Then my two controllers look like this:

    LoginVC

    import UIKit
    
    class LoginVC: UIViewController {
    
        override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
            if (segue.destination.isKind(of: FirebaseVC.self)) {
                let vc = segue.destination as! FirebaseVC
                if let sKey = sender as? String {
                    vc.sessionKey = sKey
                }
            }
        }
    
        @IBOutlet weak var username: UITextField!
        @IBOutlet weak var password: UITextField!
        @IBOutlet weak var validationBox: UITextView!
        @IBAction func logInAction(_ sender: UIButton) {
            guard let user = username.text, !user.isEmpty else {
                validationBox.text = "Please enter valid credentials"
                return
            }
            guard let pass = password.text, !pass.isEmpty else {
                validationBox.text = "Please enter valid credentials"
                return
            }
    
            CallWebService.sharedInstance.logInToCaduceus(u: username.text!, p: password.text!, completion: {(sessionKey: String) -> Void in
                    print(sessionKey)
                    self.performSegue(withIdentifier: "FirebaseVC", sender: sessionKey)
                }
            )
    
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            //validationBox.textAlignment = .center
            validationBox.text = "Ready..."
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
    
    }
    

    AND THE receiving FirebaseVC

    import UIKit
    
    class FirebaseVC: UIViewController {
    
        private var _sessionKey = String()
    
        var sessionKey : String {
            get { return _sessionKey }
            set { _sessionKey = newValue }
        }
    
        @IBOutlet weak var sessionKeyTestBox: UITextView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            sessionKeyTestBox.text = _sessionKey
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
    }
    

    Excuse my (non-swift) Javascript terminology but essentially I moved the data call into a service and then place a callback method in the service with the completion method to ensure the the performSegue doesn't fire until the data has been received and parsed out. So when i submit the log in form data to the server the segue doesn't fire until that async call has been completed.