后台位置更新
Background Location Updates
我正在构建一个带有位置服务的应用程序。
我正在使用用户当前位置来获取用户周围的对象。目前运行良好。唯一的问题是,我想在后台为具有 "signficantLocationChanges" 的用户创建本地通知,但是当应用程序从具有 applicationDidFinishLaunching(_:)
功能的 AppDelegate
启动时,launchOptions
对象是 nil
.
我想获取后台更新并发出 HTTP API 请求,根据响应,我将创建本地通知。
这是我的 AppDelegate
class:
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
创建新模式并在模拟器的调试菜单上使用高速公路行驶来调试它。也用真机测试过。
我错过了什么?
@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()
}
您需要在 AppDelegate class 上使用此 LocationManager class 以进行初始化。我希望它能帮助您实现所需的输出。
@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()
这就是您所需要的 - 显着的位置变化。但是在你用 locationManager.startUpdatingLocation()
调用 getCurrentLocation()
之后,你 'rewrite' 你的监控,这就是你没有从它那里得到任何更新的原因。
此外,请记住:
- 重要位置仅在出现
设备位置的重大变化,(实验性的
建立500米以上)
- 重要位置非常不准确(对我来说有时高达 900 米)。通常重要的位置仅用于唤醒应用程序并重新启动
定位服务。
- 在您的应用从位置更改中唤醒后
通知,你有少量的时间(大约 10
秒),所以如果您需要更多时间将位置发送到服务器
你应该要求更多的时间
beginBackgroundTask(withName:expirationHandler:)
希望我的回答对您有所帮助。编码愉快!
我正在构建一个带有位置服务的应用程序。
我正在使用用户当前位置来获取用户周围的对象。目前运行良好。唯一的问题是,我想在后台为具有 "signficantLocationChanges" 的用户创建本地通知,但是当应用程序从具有 applicationDidFinishLaunching(_:)
功能的 AppDelegate
启动时,launchOptions
对象是 nil
.
我想获取后台更新并发出 HTTP API 请求,根据响应,我将创建本地通知。
这是我的 AppDelegate
class:
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
创建新模式并在模拟器的调试菜单上使用高速公路行驶来调试它。也用真机测试过。
我错过了什么?
@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()
}
您需要在 AppDelegate class 上使用此 LocationManager class 以进行初始化。我希望它能帮助您实现所需的输出。
@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()
这就是您所需要的 - 显着的位置变化。但是在你用 locationManager.startUpdatingLocation()
调用 getCurrentLocation()
之后,你 'rewrite' 你的监控,这就是你没有从它那里得到任何更新的原因。
此外,请记住:
- 重要位置仅在出现 设备位置的重大变化,(实验性的 建立500米以上)
- 重要位置非常不准确(对我来说有时高达 900 米)。通常重要的位置仅用于唤醒应用程序并重新启动 定位服务。
- 在您的应用从位置更改中唤醒后
通知,你有少量的时间(大约 10
秒),所以如果您需要更多时间将位置发送到服务器
你应该要求更多的时间
beginBackgroundTask(withName:expirationHandler:)
希望我的回答对您有所帮助。编码愉快!