Reputation: 73
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
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:
Pass an instance of the MapViewController
to the PositionController
.
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
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
Reputation: 54795
I know of two methods to access location information once CLLocationManager.requestLocation()
has a value.
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
}
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