Regicide
Regicide

Reputation: 73

How to wait for Function to finish before continuing

In my current project I want to be able to display the users current position on a map. I have implemented this functionality via the requestLocation method provided by Apple, but the problem is that my code continues without waiting for the requestLocation Function to finish.

The flow is this: First I call the positionController.setPositionViaLocationData() function, which handles the setup and execution of the requestLocation method. In the end this receives the coordinates and stores them in the userPosition member of the PositionController. In the next line of code I want to access the userPosition via the PositionController but here comes my problem:

The line ...

var userPosition = positionController.getPosition() 

... is executed before positionController.setPositionViaLocationData() is finished, thus it takes the default values stored in userPosition.

How can I make my code wait for positionController.setPositionViaLocationData() before executing var userPosition = positionController.getPosition()? I already read stuff about Closures and CompletionManagers but I don't get that stuff to work for me.

Heres the code of both classes, first the ViewController who calls the functions:

import UIKit
import GoogleMaps
import CoreLocation

class MapViewController: UIViewController, CLLocationManagerDelegate {

    let positionController = PositionController()

    override func viewDidLoad() {
        super.viewDidLoad()

        positionController.setPositionViaLocationData()
        var userPosition = positionController.getPosition()
        setUserPositionMarkerOnMapAndShowMap(userPositionLatitude: userPosition.userLatitude, userPositionLongitude: userPosition.userLongitude)

    }

    func setUserPositionMarkerOnMapAndShowMap(userPositionLatitude: Double, userPositionLongitude: Double){
        let camera = GMSCameraPosition.camera(withLatitude: userPositionLatitude,
                                              longitude: userPositionLongitude, zoom:3)
        let mapView = GMSMapView.map(withFrame: CGRect.zero, camera:camera)
        let marker = GMSMarker()

        self.view = mapView
        marker.snippet = "Munich"
        marker.appearAnimation = GMSMarkerAnimation.pop
        marker.map = mapView

        self.view = mapView
    }

}

And now my PositionController class:

import Foundation
import CoreLocation

class PositionController: CLLocationManager, CLLocationManagerDelegate {

    let locationManager: CLLocationManager
    var userPosition = Position(longitude: 0,latitude: 0)

    override init() {
        self.locationManager = CLLocationManager()
        print("---------------Init---------------------------")
    }

    func setPositionViaLocationData(){

        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
        locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
        locationManager.requestLocation()
    }

    func setPosition(longitude: Double, latitude: Double){
        userPosition.longitude = longitude
        userPosition.latitude = latitude
    }

    func getPosition() -> (userLongitude: Double, userLatitude: Double){
        return (userPosition.longitude, userPosition.latitude)
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        let locValue:CLLocationCoordinate2D = manager.location!.coordinate
        print("locations = \(locValue.latitude) \(locValue.longitude)")
        userPosition.latitude = locValue.latitude
        userPosition.longitude = locValue.longitude
    }
}

Upvotes: 3

Views: 1104

Answers (3)

Jack G.
Jack G.

Reputation: 3941

As Rob commented, one cannot wait for the locationRequest method to end.

Looking at the code the MapViewController.setUserPositionMarkerOnMapAndShowMap method should be called from the PositionController .locationManager(didUpdateLocations) method.

The problem doing so is that the PositionController doesn't know about MapViewController.

Some options:

  1. Pass an instance of the MapViewController to the PositionController.

  2. Leave out the PositionController controller altogether and implement the functionality ('didUpdateLocations') in the MapViewController.

PS: Not related to your question directly but maybe indicating some confusion, both MapViewController and PositionController declare the CLLocationManagerDelegate protocol but only the PositionController is implementing it.

Upvotes: 0

Juan Pablo Mazza
Juan Pablo Mazza

Reputation: 26

You can use a delegate to notify MapViewController that the user position has been updated.

You can add the next protocol above the PositionController class definition

protocol PositionControllerDelegate: class {
  func didUpdatePosition(_ newPosition: Position)
}

Then add a new optional property to PositionController

weak var delegate: PositionControllerDelegate?

And then call the delegate method:

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    let locValue: CLLocationCoordinate2D = manager.location!.coordinate
    print("locations = \(locValue.latitude) \(locValue.longitude)")
    userPosition.latitude = locValue.latitude
    userPosition.longitude = locValue.longitude
    delegate?.didUpdatePosition(userPosition)
}

On the MapViewController side, you only must implement the protocol and assign self to the delegate of PositionController.

override func viewDidLoad() {
    super.viewDidLoad()

    positionController.setPositionViaLocationData()
    positionController.delegate = self
    var userPosition = positionController.getPosition()
    setUserPositionMarkerOnMapAndShowMap(userPositionLatitude: userPosition.userLatitude, userPositionLongitude: userPosition.userLongitude)
}

extension MapViewController: PositionControllerDelegate {
  func didUpdatePosition(_ newPosition: Position) {
    //Do whatever you want with the position
  }
}

Upvotes: 1

David Pasztor
David Pasztor

Reputation: 54795

I know of two methods to access location information once CLLocationManager.requestLocation() has a value.

  1. Call your functions in the delegate methods of your location manager.

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        //call function when you get user location
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        //handle error
    }
    
  2. Use PromiseKit, which provides a Promise that only returns once the location manager updates. You have to have CLLocationManager.promise() to get a Promise that returns when the location manager updates its location.

If you plan to use a lot of asynchronous calls, I would go for PromiseKit, since it makes handling and running asynchronous functions sequentially really straightforward. However, getting to know how PromiseKit works takes some time so if you would only use it for this single use case, I would rather go for method 1.

Upvotes: 0

Related Questions