后台位置更新
Posted
技术标签:
【中文标题】后台位置更新【英文标题】:Background Location Updates 【发布时间】:2020-03-02 10:11:47 【问题描述】:我正在构建一个带有位置服务的应用程序。
我正在使用用户当前位置来获取用户周围的对象。目前运行良好。唯一的问题是,我想在后台使用“signfiicantLocationChanges”为用户创建本地通知,但是当应用程序从 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
类:
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")
我正在尝试通过使用Wait for executable to be launched
在 Xcode 上创建新架构并在模拟器的调试菜单上使用 Freeway Ride 来调试它。也用真机测试过。
我错过了什么?
【问题讨论】:
您是否在后台模式下启用了位置更新?启用后,您的应用程序应在后台获取更新。另外,我在任何地方都没有看到 [locationManager startMonitoringSignificantLocationChanges] 被调用。如果不调用它,您的应用将不会获得重要的位置更新。 @PuneetSharma 它在 LocationManager 类中调用。同样是的,它已启用。 您解决了这个问题吗? @CyberMew 实际上是的,但由于大流行我已经放弃了项目...... @onurgenes 真不幸……为了后代,您能否分享一下您是如何修复它的以及原因是什么? 【参考方案1】:@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()
之后,您“重写”了您的监控,这就是您没有从中获得任何更新的原因。
另外,请记住:
-
重要位置仅在存在
设备位置的显着变化,(实验
建立 500 米或以上)
重要的位置非常不准确(对我来说有时高达 900 米)。很多时候重要的位置使用只是为了唤醒应用程序并重新启动
定位服务。
您的应用从位置更改唤醒后
通知,您将获得少量时间(大约 10
秒),因此如果您需要更多时间将位置发送到服务器
你应该要求更多的时间
beginBackgroundTask(withName:expirationHandler:)
希望我的回答对您有所帮助。快乐编码!
【讨论】:
我已经尝试了所有建议,但这仍然不起作用。如果你有空,我可以邀请你加入我的 GitHub 存储库。 @nurgenes 当然,我在 github 的用户名是 rubikva 已发送邀请。您需要此项目的服务器,但您可以模拟服务器响应。 “按照 Apple 的建议创建不同的位置管理器实例。” 【参考方案2】:@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 类上使用这个 LocationManager 类来初始化它。我希望它能帮助您实现所需的输出。
【讨论】:
还是没有运气。如果你愿意,我可以邀请你到我的 GitHub 仓库。以上是关于后台位置更新的主要内容,如果未能解决你的问题,请参考以下文章