David Henry
David Henry

Reputation: 3036

Completion Handler executed before Request is complete

I have an app that uses UIImagePickerController to select an image. That image is then passed into an API function. Once that function is complete I pass the result using a delegate to a modal displayed controller with the results. However, the modal controller is presented before the completion block and my error AlerViewController alerts are never called.

The API is run in the background thread, I have set the completion on the main thread (as it updates the UI - presents the modal controller) but it gets called before the completion is fully executed.

Code below;

API Request

func searchImage(with image: UIImage, to viewController: UIViewController, success: @escaping([ViImageResult]?) -> Void) {
        var results = [ViImageResult]()
        let params = ViUploadSearchParams(image: image)
        ViSearch.sharedInstance.uploadSearch(params: params, successHandler: { (data : ViResponseData?) -> Void in
            guard let data = data else { return }
            if data.result.isEmpty {
                AlertViewController.noResultsFound(viewController: viewController)
                return
            } else {
                if data.hasError {
                    AlertViewController.dataError(viewController: viewController)
                    return
                } else {
                    for response in data.result {
                        results.append(response)
                    }
                    DispatchQueue.main.async {
                        success(results)
                    }
                }
            }
        }, failureHandler: {
            (error) -> Void in
            AlertViewController.dataError(viewController: viewController)
        })
    }

Controller

var selectedImage: UIImage? {
        didSet {
            guard let selectedImage = selectedImage else { return }
            ViSearchSDKService.shared.searchImage(with: selectedImage, to: self) { (results) in
                guard let results = results else { return }
                if self.resultsDelegate != nil {
                    self.resultsDelegate?.recievedResults(recievedResults: results)
                }
            }
            let resultsController = ResultsViewController()
            self.resultsDelegate = resultsController
            let navigationController = UINavigationController(rootViewController: resultsController)
            navigationController.modalPresentationStyle = .overFullScreen
            DispatchQueue.main.async {
                self.present(navigationController, animated: true, completion: nil)
            }
        }
    }

In the API Request, all my AlertViewController functions are called on the main thread and then returns out of the request. Success block is also called on the main thread.

What am I doing wrong here?...

Update

I am not quite sure why this works but it does everything I need. I have moved the API Request into another function outside of

var selectedImage: UIImage? {
        didSet {

in my controller.

New Working Code

var selectedImage: UIImage? {
        didSet {
            guard let selectedImage = selectedImage else { return }
            self.searchImage(with: selectedImage)
        }
    }

    func searchImage(with image: UIImage) {
        ViSearchSDKService.shared.searchImage(with: image, to: self) { (results) in
            guard let results = results else { return }
            let resultsController = ResultsViewController()
            self.resultsDelegate = resultsController
            if self.resultsDelegate != nil {
                self.resultsDelegate?.recievedResults(recievedResults: results)
            }
            let navigationController = UINavigationController(rootViewController: resultsController)
            navigationController.modalPresentationStyle = .fullScreen
            DispatchQueue.main.async {
                self.present(navigationController, animated: true, completion: nil)
            }
        }
    }

Upvotes: 0

Views: 131

Answers (1)

Popmedic
Popmedic

Reputation: 1871

I think you want this.

    var selectedImage: UIImage? {
        didSet {
            // make sure it was not set to nil
            guard let selectedImage = selectedImage else { return }

            // set up your view controller for the response
            let resultsController = ResultsViewController()
            self.resultsDelegate = resultsController
            let navigationController = UINavigationController(rootViewController: resultsController)

            // do your search
            ViSearchSDKService.shared.searchImage(with: selectedImage, to: self) { (results) in
                // leave no path without visible side-effect
                guard let results = results else { debugPrint("nil results"); return }

                // now that we have the result, present your results view controller
                navigationController.modalPresentationStyle = .overFullScreen
                DispatchQueue.main.async {
                    self.present(navigationController, animated: true) { in

                        // once done presenting, let it know about the results
                        self.resultsDelegate?.recievedResults(recievedResults: results)
                    }
                }
            }
        }
    }

Upvotes: 1

Related Questions