Функция, которая срабатывает после обновления местоположения пользователя - PullRequest
4 голосов
/ 19 мая 2019

Важное примечание: Я знаю, что я мог бы просто вызвать мою callback функцию внутри didUpdateLocations и добиться того, чего я хочу. К сожалению, этот маршрут не может быть выбран из-за некоторых уже существующих проектных решений, которые были приняты в этом проекте (на которые я не имею никакого влияния).

Мне нужно написать функцию, которая запускает первый раз, когда пользователь обновляет координаты, а затем передает эти координаты обработчику завершения. В моем случае этот обработчик завершения является функцией с именем fetchCountry(fromLocation: CLLocation), которая возвращает страну, соответствующую заданному CLLocation.

Другими словами, я хочу написать функцию, аналогичную didUpdateLocations, с возможностью вызова обработчика завершения после получения этих обновлений:

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)
}

Короче говоря, getUserLocation - это просто оболочка для didUpdateLocations, но я действительно не уверен, как бы я написал эту функцию, чтобы она достигла того, чего я хочу.

Моя большая цель здесь - показать пользователю локальное уведомление только в том случае, если они находятся в определенной стране (например, США) после запуска приложения. Моему приложению трудно принять решение о планировании / не планировании этого уведомления внутри AppDelegate.swift , но это решение не может быть принято до тех пор, пока не будет найдено местоположение пользователя. Я планирую использовать getUserLocation внутри приложения Delegate следующим образом:

Надеюсь, что я ясно дал понять, что хочу добиться этого, используя функцию с обработчиком завершения . Вот что я хотел бы, чтобы мой код (т.е. мой сценарий использования), внутри 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)
                    }
                })
            })
        }
    }

Ответы [ 4 ]

2 голосов
/ 21 мая 2019

Отредактировал весь ответ.Вам нужно будет использовать синхронизирующий API (OperationQueue, DispatchQueue и т. Д.), Потому что ваш CLLocationManager уже получает еще до вызова getUserLocation.Только обратные вызовы не могут справиться с этим, поэтому я уже удалил эту опцию.Для этого случая я использовал DispatchQueue, потому что я предпочитаю использовать его, каждому свое.

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
    }

}


Я также согласен на использование сторонней библиотеки, если это возможно.Этот код гарантированно будет проверен модулем и будет обрабатывать крайние случаи.Принимая во внимание, что мой код выше может иметь ошибку (ни один из которых я знаю, кстати)

1 голос
/ 21 мая 2019

Я не знаю, каковы ваши особые требования, так что это общий совет.

Похоже, вам следует настроить второй диспетчер местоположений для собственного использования. Установите его в делегате приложения и поместите туда обратные вызовы делегата, отдельно от главного менеджера расположений.

Не пытайтесь отложить willFinishLaunchingWithOptions до конца. В зависимости от ваших требований вам может понадобиться переместить любой код настройки пользовательского интерфейса в ваш собственный обратный вызов, чтобы настроить интерфейс после определения страны. Я бы даже подумал о том, чтобы показать другой пользовательский интерфейс при настройке местоположения и уведомлений, а затем заменить его на основной пользовательский интерфейс, если у вас есть разрешение на уведомление, разрешение на местоположение и страну.

Поймите, что первое обновление местоположения, которое вы получите через didUpdateLocations, может быть неточным. Проверьте точность расстояния, а также проверьте значение timestamp и откажитесь от всех старых обновлений. Для ваших целей (точность страны) это, вероятно, означает, что старше часа. Вы действительно рассматриваете только тот случай использования, когда ваше приложение было открыто впервые после того, как пользователь покинул самолет, летевший из другой страны. Для точности, если временная метка недавняя, под этот уровень подойдет все, что меньше 3000 м или 5000 м.

Поскольку требуемая точность слишком низкая, местоположение будет зависеть от триангуляции вышек сотовой связи. Это должно быть быстро (возможно, в течение 2-5 секунд).

Единственное, в чем я должен быть осторожен, так это в том, что ваш менеджер местоположений должен будет запрашивать разрешения местоположения, тогда как главный менеджер местоположений делает то же самое. Я не знаю, как работает двойной запрос разрешений.

Я бы также отделил выбор страны и получение местоположения от разрешений уведомлений.

Еще несколько общих советов: ваш вариант использования выглядит так, как будто он обрабатывается в сеансе WWDC для Advanced NSOperations . Громкоговоритель обрабатывает случаи, когда вам нужно настроить несколько вещей, прежде чем перейти к следующей части. Там также есть сценарий использования местоположения и сценарий использования разрешений, один из которых зависит от другого.

1 голос
/ 21 мая 2019

Вы пытались использовать PromiseKit + CoreLocation ?

Это обеспечивает CLLocationManager.requestLocation(authorizationType:satisfying:).Если вы не хотите импортировать всю инфраструктуру PromiseKit (что замечательно и избежать такой цепочки завершения), вы можете скопировать ее код .Они сделали именно то, что вы хотите: обернуть запрос CoreLocation в функцию.

1 голос
/ 19 мая 2019

Из вашего кода AppDelegate я могу предположить, что вы определяете страну только в классе LocationManager.Я бы предложил удалить обратный вызов из функции getUserLocation () и создать другую функцию с именем postLocalNotification () в AppDelegate, чтобы просто публиковать локальное уведомление.

Когда вы начнете извлекать местоположение пользователя, didUpdateLocation будетвызывается, в котором вы должны вызвать fetchCountry () с последним местоположением.Если выбранная страна правильная и вы хотите опубликовать локальное уведомление, получите объект appelegate и вызовите функцию, которая отправит уведомление, как показано ниже

let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.postLocalNotification()

Надеюсь, это поможет.

...