Neta yam
Neta yam

Reputation: 69

Swift launch view only when data received

I'm getting info from an API using the following function where I pass in a string of a word. Sometimes the word doesn't available in the API if it doesn't available I generate a new word and try that one. The problem is because this is an asynchronous function when I launch the page where the value from the API appears it is sometimes empty because the function is still running in the background trying to generate a word that exists in the API. How can I make sure the page launches only when the data been received from the api ?

static func wordDefin (word : String, completion: @escaping (_ def: String )->(String)) {
        let wordEncoded = word.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
        let uri = URL(string:"https://dictapi.lexicala.com/search?source=global&language=he&morph=false&text=" + wordEncoded! )
        if let unwrappedURL = uri {
            var request = URLRequest(url: unwrappedURL);request.addValue("Basic bmV0YXlhbWluOk5ldGF5YW1pbjg5Kg==", forHTTPHeaderField: "Authorization")
            let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
                do {
                if let data = data {
                    let decoder = JSONDecoder()
                    let empty = try decoder.decode(Empty.self, from: data)
                    if (empty.results?.isEmpty)!{
                        print("oops looks like the word :" + word)
                        game.wordsList.removeAll(where: { ($0) == game.word })
                        game.floffWords.removeAll(where: { ($0) == game.word })
                        helper.newGame()
                    } else {
                        let definition =  empty.results?[0].senses?[0].definition
                        _ = completion(definition ?? "test")
                        return
                    }
                    }
                }
                catch {
                    print("connection")
                    print(error)
                }
        }
            dataTask.resume()
        }
    }

Upvotes: 1

Views: 874

Answers (1)

ctrl freak
ctrl freak

Reputation: 12405

You can't stop a view controller from "launching" itself (except not to push/present/show it at all). Once you push/present/show it, its lifecycle cannot—and should not—be stopped. Therefore, it's your responsibility to load the appropriate UI for the "loading state", which may be a blank view controller with a loading spinner. You can do this however you want, including loading the full UI with .isHidden = true set for all view objects. The idea is to do as much pre-loading of the UI as possible while the database is working in the background so that when the data is ready, you can display the full UI with as little work as possible.

What I'd suggest is after you've loaded the UI in its "loading" configuration, download the data as the final step in your flow and use a completion handler to finish the task:

override func viewDidLoad() {
    super.viewDidLoad()

    loadData { (result) in
        // load full UI
    }

}

Your data method may look something like this:

private func loadData(completion: @escaping (_ result: Result) -> Void) {
    ...
}

EDIT

Consider creating a data manager that operates along the following lines. Because the data manager is a class (a reference type), when you pass it forward to other view controllers, they all point to the same instance of the manager. Therefore, changes that any of the view controllers make to it are seen by the other view controllers. That means when you push a new view controller and it's time to update a label, access it from the data property. And if it's not ready, wait for the data manager to notify the view controller when it is ready.

class GameDataManager {

    // stores game properties
    // updates game properties
    // does all thing game data

    var score = 0
    var word: String?

}

class MainViewController: UIViewController {

    let data = GameDataManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        // when you push to another view controller, point it to the data manager
        let someVC = SomeOtherViewController()
        someVC.data = data

    }

}

class SomeOtherViewController: UIViewController {

    var data: GameDataManager?

    override func viewDidLoad() {
        super.viewDidLoad()

        if let word = data?.word {
            print(word)
        }

    }

}

class AnyViewController: UIViewController {

    var data: GameDataManager?

}

Upvotes: 1

Related Questions