用户位置更新后触发的函数
Function that fires after user's location has updated
重要说明: 我知道我可以简单地在 didUpdateLocations
中调用我的 callback
函数并实现我想要的。不幸的是,由于在该项目中做出了一些预先存在的设计决策(我对此没有影响),因此无法采用这条路线。
我需要编写一个在用户坐标第一次更新时触发的函数,然后将这些坐标传递给完成处理程序。在我的例子中,完成处理程序是一个名为 fetchCountry(fromLocation: CLLocation)
的函数,其中 returns 对应于给定 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
的包装器,但我真的不确定我将如何编写此函数以实现我想要的。
我的更大目标是仅当用户在启动应用程序时位于特定国家/地区(例如美国)时,才向用户显示本地通知。我的应用程序很难决定 scheduling/not 在 AppDelegate.swift 内安排此通知,但在用户位置确定之前无法做出此决定检索。我打算像这样在 appDelegate 中使用 getUserLocation
:
我希望我已经清楚地表达了我希望使用带有 完成处理程序 的函数来实现此目的。这是我希望我的代码在 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)
}
})
})
}
}
根据您的 AppDelegate 代码,我可以假设您仅在 LocationManager class 中确定国家/地区。我建议从 getUserLocation() 函数中删除回调,并在 AppDelegate 中创建一个名为 postLocalNotification() 的不同函数,仅 post 本地通知。
当您开始获取用户位置时,将调用 didUpdateLocation,您应在其中调用 fetchCountry() 和最新位置。如果获取的国家是正确的并且你想要 post 本地通知获取 appelegate 对象并调用将 post 通知如下的函数
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.postLocalNotification()
希望对您有所帮助。
编辑了整个答案。您需要使用同步 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
}
}
我也同意尽可能使用第三方库。这些代码将保证 unit-tested 并处理边缘情况。然而,我上面的代码可能有一个错误(none 我知道顺便说一句)
您尝试使用 PromiseKit+CoreLocation 了吗?
提供CLLocationManager.requestLocation(authorizationType:satisfying:)
。如果您不想导入所有 PromiseKit framework (which is great & avoid such completion chain), you can copy its code。他们做的正是你想要的:将 CoreLocation
请求包装在一个函数中。
我不知道您的特殊要求是什么,所以这是一般性建议。
听起来您应该设置第二个位置管理器供您自己使用。在应用程序委托中设置它并将其委托回调放在那里,与主要位置管理器分开。
不要试图延迟 willFinishLaunchingWithOptions
完成。根据您的要求,您可能必须将任何 UI 设置代码移动到您自己的回调中,以便在确定国家/地区后设置界面。我什至会考虑在您设置位置和通知时显示不同的 UI,然后在您拥有通知权限、位置权限和国家/地区时将其换成主要 UI。
请注意,您通过 didUpdateLocations
获得的第一个位置更新可能不准确。检查它的距离精度,但也要检查它的 timestamp
并丢弃任何旧的更新。出于您的目的(国家/地区准确性),这可能意味着超过一个小时。您实际上只是在考虑用户从另一个国家/地区下飞机后首次打开您的应用程序的用例。为了准确起见,如果时间戳是最近的,则 3000m 或 5000m 以下的任何高度都可以达到该级别。
由于所需的精度非常低,因此该位置将来自手机信号塔三角测量。它应该很快(可能在 2-5 秒内)。
我要注意的一件事是,您的位置管理员将必须请求位置权限,而主要位置管理员会做同样的事情。我不知道这样两次请求权限是如何工作的。
我还将获取国家/地区和获取位置信息与通知权限分开。
一些更一般的建议:您的用例看起来像是在 Advanced NSOperations 的 WWDC session 中处理的。演讲者处理在下一部分继续之前需要设置几件事的情况。那里也有一个位置用例和一个权限用例,一个依赖另一个。
重要说明: 我知道我可以简单地在 didUpdateLocations
中调用我的 callback
函数并实现我想要的。不幸的是,由于在该项目中做出了一些预先存在的设计决策(我对此没有影响),因此无法采用这条路线。
我需要编写一个在用户坐标第一次更新时触发的函数,然后将这些坐标传递给完成处理程序。在我的例子中,完成处理程序是一个名为 fetchCountry(fromLocation: CLLocation)
的函数,其中 returns 对应于给定 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
的包装器,但我真的不确定我将如何编写此函数以实现我想要的。
我的更大目标是仅当用户在启动应用程序时位于特定国家/地区(例如美国)时,才向用户显示本地通知。我的应用程序很难决定 scheduling/not 在 AppDelegate.swift 内安排此通知,但在用户位置确定之前无法做出此决定检索。我打算像这样在 appDelegate 中使用 getUserLocation
:
我希望我已经清楚地表达了我希望使用带有 完成处理程序 的函数来实现此目的。这是我希望我的代码在 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)
}
})
})
}
}
根据您的 AppDelegate 代码,我可以假设您仅在 LocationManager class 中确定国家/地区。我建议从 getUserLocation() 函数中删除回调,并在 AppDelegate 中创建一个名为 postLocalNotification() 的不同函数,仅 post 本地通知。
当您开始获取用户位置时,将调用 didUpdateLocation,您应在其中调用 fetchCountry() 和最新位置。如果获取的国家是正确的并且你想要 post 本地通知获取 appelegate 对象并调用将 post 通知如下的函数
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.postLocalNotification()
希望对您有所帮助。
编辑了整个答案。您需要使用同步 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
}
}
我也同意尽可能使用第三方库。这些代码将保证 unit-tested 并处理边缘情况。然而,我上面的代码可能有一个错误(none 我知道顺便说一句)
您尝试使用 PromiseKit+CoreLocation 了吗?
提供CLLocationManager.requestLocation(authorizationType:satisfying:)
。如果您不想导入所有 PromiseKit framework (which is great & avoid such completion chain), you can copy its code。他们做的正是你想要的:将 CoreLocation
请求包装在一个函数中。
我不知道您的特殊要求是什么,所以这是一般性建议。
听起来您应该设置第二个位置管理器供您自己使用。在应用程序委托中设置它并将其委托回调放在那里,与主要位置管理器分开。
不要试图延迟 willFinishLaunchingWithOptions
完成。根据您的要求,您可能必须将任何 UI 设置代码移动到您自己的回调中,以便在确定国家/地区后设置界面。我什至会考虑在您设置位置和通知时显示不同的 UI,然后在您拥有通知权限、位置权限和国家/地区时将其换成主要 UI。
请注意,您通过 didUpdateLocations
获得的第一个位置更新可能不准确。检查它的距离精度,但也要检查它的 timestamp
并丢弃任何旧的更新。出于您的目的(国家/地区准确性),这可能意味着超过一个小时。您实际上只是在考虑用户从另一个国家/地区下飞机后首次打开您的应用程序的用例。为了准确起见,如果时间戳是最近的,则 3000m 或 5000m 以下的任何高度都可以达到该级别。
由于所需的精度非常低,因此该位置将来自手机信号塔三角测量。它应该很快(可能在 2-5 秒内)。
我要注意的一件事是,您的位置管理员将必须请求位置权限,而主要位置管理员会做同样的事情。我不知道这样两次请求权限是如何工作的。
我还将获取国家/地区和获取位置信息与通知权限分开。
一些更一般的建议:您的用例看起来像是在 Advanced NSOperations 的 WWDC session 中处理的。演讲者处理在下一部分继续之前需要设置几件事的情况。那里也有一个位置用例和一个权限用例,一个依赖另一个。