Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function that fires after user's location has updated

Important Note: I am aware that I could simply call my callback function inside didUpdateLocations and achieve what I want. Unfortunately, this route cannot be taken because of some pre-existing design decisions that were made in this project (which I have no influence over).

I need to write a function that fires the first time the user's coordinates update, and then passes those coordinates to a completion handler. In my case, that completion handler is a function called fetchCountry(fromLocation: CLLocation) which returns the country corresponding to the CLLocation given.

In other words, I want to write a function similar to didUpdateLocations, with the capability of calling a completion handler after those updates have been received:

func getUserLocation(callback: @escaping (CLLocation?) -> Void) {

    // wait until user's location has been retrieved by location manager, somehow

    // prepare output for callback function and pass it as its argument
    let latitude = manager.location!.coordinate.latitude
    let longitude = manager.location!.coordinate.longitude
    let location = CLLocation(latitude: latitude, longitude: longitude)

    callback(location)
}

In short, getUserLocation is just a wrapper for didUpdateLocations but I am really not sure how I would go about writing this function so that it achieves what I want.

My greater goal here is to show the user a local notification only if they are in a certain country (e.g. United States) upon launching the app. It is a hard requirement for my application to make the decision of scheduling/not scheduling this notification inside AppDelegate.swift, but this decision cannot be made until the user's location has been retrieved. I plan to use getUserLocation inside the appDelegate like this:

I hope that I have conveyed clearly that I am looking to achieve this using a function with a completion handler. Here is what I would like my code to do (i.e. my use case), inside AppDelegate.swift:

// inside didFinishLaunchingWithOptions
UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .alert, .sound]) { (granted, error) in
    if granted {

            // this is the use case of the function I am trying to write                         
            LocationManager.shared.getLocation(completion: { location in

                // fetches the country from given location using reverse geo-coding
                LocationManager.shared.fetchCountry(from: location, completion: { country in

                    if country == "United States" {                  
                        let notification = LocalNotification()
                        notificationManager.schedule(notification: notification)
                    }
                })
            })
        }
    }
like image 974
Swifty Avatar asked Feb 17 '26 17:02

Swifty


1 Answers

Edited the whole answer. You would need to use a synchronizing api (OperationQueue, DispatchQueue, etc) because your CLLocationManager is already fetching even before getUserLocation is called. Callbacks alone can't handle this that's why I removed that option already. For this case, I used DispatchQueue because I prefer using it, to each their own.

class LocationManager: NSObject, CLLocationManagerDelegate{

    static let shared: LocationManager()
    private let privateQueue = DispatchQueue.init("somePrivateQueue")
    private var latestLocation: CLLocation!{
        didSet{
            privateQueue.resume()
        }
    }

    func getUserLocation(queue: DispatchQueue, callback: @escaping (CLLocation?) -> Void) {
        if latestLocation == nil{
            privateQueue.suspend() //pause queue. wait until got a location
        }

        privateQueue.async{ //enqueue work. should run when latestLocation != nil

            queue.async{ //use a defined queue. most likely mainQueue

              callback(self.latestLocation)
              //optionally clear self.latestLocation to ensure next call to this method will wait for new user location. But if you are okay with a cached userLocation, then no need to clear.
            }
        }

    }

    func fetchCountry(from currentLocation: CLLocation, completion: ) //you already have this right?

    @objc func locationManager(_ manager: CLLocationManager, 
           didUpdateLocations locations: [CLLocation]){
         latestLocation = locations.last! //API states that it is guaranteed to always have a value
    }

}


I also agree on using a 3rd party library if possible. Those code would be guaranteed to be unit-tested and handle edge cases. Whereas, my above code could have a bug (none that I know of btw)

like image 139
Joshua Francis Roman Avatar answered Feb 19 '26 10:02

Joshua Francis Roman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!