Фоновые обновления местоположения - PullRequest
0 голосов
/ 02 марта 2020

Я создаю приложение со службами определения местоположения.

Я использую текущее местоположение пользователей для перемещения объектов вокруг пользователя. Который в настоящее время работает отлично. Единственная проблема в том, что я хочу создать локальные уведомления для пользователя с "signficantLocationChanges" на фоне, но когда приложение запускается из AppDelegate с функцией applicationDidFinishLaunching(_:), launchOptions объект равен nil.

Я хочу чтобы получить фоновые обновления и сделать запрос HTTP API и, в зависимости от ответа, я создам локальное уведомление.

Вот мой класс AppDelegate:

import UIKit
import UserNotifications
import CoreLocation

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    var locationManager: LocationManager?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // Checking this because if the app is started for location updates,
        // no need to setup app for UI
        if let _ = launchOptions?[.location] {
            locationManager = LocationManager()
            locationManager?.delegate = self
            locationManager?.getCurrentLocation()
            return true
        }

        attemptToRegisterForNotifications(application: application)

        if #available(iOS 13, *) { } else {
            app.start()
        }

        return true
    }

    // MARK: UISceneSession Lifecycle
    @available(iOS 13.0, *)
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    @available(iOS 13.0, *)
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        UNUserNotificationCenter.current().removeAllDeliveredNotifications()
    }
}

extension AppDelegate: LocatableOutputProtocol {
    func didGetCurrentLocation(latitude: Double, longitude: Double) {
        UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { (settings) in
            if settings.authorizationStatus == .authorized {
                let content = UNMutableNotificationContent()
                content.title = "\(Date().timeIntervalSince1970)"

                let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)

                let request = UNNotificationRequest(identifier: "\(Date().timeIntervalSince1970)", content: content, trigger: trigger)

                UNUserNotificationCenter.current().add(request) { _ in

                }
            }
        })
    }

    func failedGetCurrentLocation(error: Error) {
        print(error)
    }
}

extension AppDelegate: UNUserNotificationCenterDelegate {

    private func attemptToRegisterForNotifications(application: UIApplication) {
        UNUserNotificationCenter.current().delegate = self

        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
        UNUserNotificationCenter.current().requestAuthorization(options: authOptions, completionHandler: { granted, error in
            if let error = error {
                print("failed to get auth", error)
                return
            }
            if granted {
                DispatchQueue.main.async {
                    application.registerForRemoteNotifications()
                }
            } else {
                print("NO AVAIL FOR NOTIFS")
            }
        })
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler(.alert)
    }
}

Также у меня есть пользовательский LocationManager class:

import CoreLocation

final class LocationManager: NSObject, Locatable {
    weak var delegate: LocatableOutputProtocol?

    var locationManager: CLLocationManager

    override init() {
        locationManager = CLLocationManager()
        super.init()

        let authStatus = CLLocationManager.authorizationStatus()
        if CLLocationManager.locationServicesEnabled() {
            if (authStatus == .authorizedAlways || authStatus == .authorizedWhenInUse) {
                locationManager.delegate = self
                locationManager.startUpdatingLocation()
                locationManager.startMonitoringSignificantLocationChanges()
                locationManager.allowsBackgroundLocationUpdates = true
                locationManager.desiredAccuracy = kCLLocationAccuracyBest
            } else {
                locationManager.requestAlwaysAuthorization()
                print("we dont have permission")
            }
        } else {

        }
    }

    func getCurrentLocation() {
        locationManager.startUpdatingLocation()
    }
}

extension LocationManager: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let coordinates = locations.first?.coordinate {
            locationManager.stopUpdatingLocation()
            self.delegate?.didGetCurrentLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        self.delegate?.failedGetCurrentLocation(error: error)
    }

    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        print("status changed")
        if (status == .authorizedAlways || status == .authorizedWhenInUse) {
            print("we got permission")
        } else {
            print("nope")
        }
    }
}

Я пытаюсь отладить это, создав новую схему на Xcode с Wait for executable to be launched и используя Freeway Ride в меню отладки симулятора. Также протестировано на реальном устройстве.

Чего мне не хватает?

Ответы [ 2 ]

2 голосов
/ 18 марта 2020

@ onurgenes, если вы добавите реальный код из своего проекта, в первую очередь, как вы можете запускать любые обновления местоположения здесь?

    if let _ = launchOptions?[.location] {
        locationManager = LocationManager()
        locationManager?.delegate = self
        locationManager?.getCurrentLocation()
        return true
    }

Когда приложение запускается в первый раз launchOptions будет nil, а ваш LocationManager() даже не запущен, поэтому любые ваши наблюдения за местоположением и обновления не будут работать (возможно, у вас есть какой-то код на app.start(), но теперь это выглядит как ошибка).

Второе - в вашем примере вы используете систему мониторинга местоположения:

    locationManager.startUpdatingLocation()
    locationManager.startMonitoringSignificantLocationChanges()

, так что здесь ваш менеджер местоположений обрабатывает только significantLocationChanges(). Если вы хотите использовать оба из них - вы должны переключить его (на didBecomeActiveNotification и didEnterBackgroundNotification) или создать разные экземпляры менеджера местоположений, как рекомендует Apple.

Третье - ваша проблема. Давайте посмотрим на эту часть более подробно:

locationManager = LocationManager()
locationManager?.delegate = self
locationManager?.getCurrentLocation()

Как я уже упоминал - в LocationManager() вы начинаете мониторинг местоположения:

locationManager.startUpdatingLocation()
locationManager.startMonitoringSignificantLocationChanges()

, и это то, что вам нужно - значительные изменения местоположения , Но после того, как вы наберете getCurrentLocation() с locationManager.startUpdatingLocation(), чтобы вы «переписали» свой мониторинг, вот почему вы не получили от него никаких обновлений.

Также имейте в виду:

  1. Значительные местоположения доставляют обновления только тогда, когда произошло существенное изменение местоположения устройства (экспериментально установлено 500 метров или более)
  2. Значительные местоположения очень неточные (для меня иногда это до 900 метров). Очень часто значимое местоположение используют только для пробуждения приложения и перезапуска службы определения местоположения.
  3. После того, как ваше приложение проснется от уведомления об изменении местоположения, вам предоставляется небольшое количество времени (около 10 секунд), поэтому, если вам нужно больше времени для отправки местоположения на сервер, вам следует запросить больше времени с помощью beginBackgroundTask(withName:expirationHandler:)

Надеюсь, мой ответ был полезен. Удачного кодирования!

2 голосов
/ 17 марта 2020

@ onurgenes Вам необходимо использовать NotificationCenter в разделе инициализации диспетчера местоположений, как показано ниже,

import CoreLocation

    final class LocationManager: NSObject, Locatable {
        weak var delegate: LocatableOutputProtocol?

        var locationManager: CLLocationManager

        override init() {

            NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackgroundActive(_:)), name: UIApplication.didEnterBackgroundNotification, object: nil)

            NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForegroundActive(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)

            locationManager = CLLocationManager()
            super.init()

            let authStatus = CLLocationManager.authorizationStatus()
            if CLLocationManager.locationServicesEnabled() {
                if (authStatus == .authorizedAlways || authStatus == .authorizedWhenInUse) {
                    locationManager.delegate = self
                    locationManager.startUpdatingLocation()
                    locationManager.startMonitoringSignificantLocationChanges()
                    locationManager.allowsBackgroundLocationUpdates = true
                    locationManager.desiredAccuracy = kCLLocationAccuracyBest
                } else {
                    locationManager.requestAlwaysAuthorization()
                    print("we dont have permission")
                }
            } else {

            }
        }

        func getCurrentLocation() {
            locationManager.startUpdatingLocation()
        }
    }

    extension LocationManager: CLLocationManagerDelegate {
        func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
            if let coordinates = locations.first?.coordinate {
                locationManager.stopUpdatingLocation()
                self.delegate?.didGetCurrentLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
            }
        }

        func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
            self.delegate?.failedGetCurrentLocation(error: error)
            self.locationManager.stopMonitoringSignificantLocationChanges()
        }

        func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
            print("status changed")
            if (status == .authorizedAlways || status == .authorizedWhenInUse) {
                print("we got permission")
            self.locationManager.startMonitoringSignificantLocationChanges()
            } else {
                print("nope")
            }
        }
    }


        @objc private func applicationDidEnterBackgroundActive (_ notification: Notification) {
                self.locationManager.startMonitoringSignificantLocationChanges()
        }

        @objc private func applicationWillEnterForegroundActive (_ notification: Notification) {
            self.locationManager.startUpdatingLocation()
        }

Вам необходимо использовать этот класс LocationManager в своем классе AppDelegate для его инициализации. Я надеюсь, что это поможет достичь желаемого результата.

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