Continuation of the last time ↓ I tried to summarize the points to consider when acquiring location information with the iOS app ②
It is a summary of points to consider when acquiring location information with the iOS app.
Last time, when you returned from background state
to presence / absence of location information service of application
I wrote that I have to check.
To do some processing when the app returns from the background state
, it was done withapplicationWillEnterForeground (_ application: UIApplication)
of AppDelegate
.
And I used NotificationCenter
to handle it in the view controller because I want to display the alert.
Now, let's actually write the code.
Again, this method is used to display alerts ↓ Implement UIAlertController with separate files
ViewController
import UIKit
import CoreLocation
final class ViewController: UIViewController {
private var locationManager: CLLocationManager = {
var locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.distanceFilter = 5
return locationManager
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
}
//Method called when returning from background state(Make it yourself)
@objc private func willEnterForeground() {
//When the location information service of the terminal is on
if CLLocationManager.locationServicesEnabled() {
//Get location-based service authentication status
let status = CLLocationManager.authorizationStatus()
switch status {
//If not allowed
case .denied:
//Alert display
Alert.okAlert(vc: self, title: "The location information service of the app\n Please turn it on", message: "Tap OK to go to the settings app") { (_) in
//Screen transition to the settings app
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
default:
break
}
}
//When the location information service of the terminal is off
}else {
//Alert display
Alert.okAlert(vc: self, title: "Location-based services\n Please turn it on", message: "You can turn it on from the "Settings" app ⇒ "Privacy" ⇒ "Location services"")
}
}
Fork whether the location information service of the terminal is on or off
If it is on
, it is checking for presence / absence of location information service of the application
.
When I try to build it, it is checked properly ↓
※bug? Perhaps because of this, sometimes the location information service authentication status is next confirmation
.
Originally it will be none
.
By the way, this part is OK only in the case of .denied
.
The reason is that if the authentication status changes, the delegate method
will be called, and the screen transition to the settings app
is only for .denied
, and I want to check if it returns without doing anything at that time. Therefore, it is not necessary to describe other authentication status.
Now, let's take a look at the entire source code.
ViewController
import UIKit
import CoreLocation
final class ViewController: UIViewController {
private var locationManager: CLLocationManager = {
var locationManager = CLLocationManager()
locationManager.distanceFilter = 5
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
return locationManager
}()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
}
@objc private func willEnterForeground() {
if CLLocationManager.locationServicesEnabled() {
let status = CLLocationManager.authorizationStatus()
switch status {
case .denied:
Alert.okAlert(vc: self, title: "The location information service of the app\n Please turn it on", message: "Tap OK to go to the settings app") { (_) in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
default:
break
}
}else {
Alert.okAlert(vc: self, title: "Location-based services\n Please turn it on", message: "You can turn it on from the "Settings" app ⇒ "Privacy" ⇒ "Location services"")
}
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if CLLocationManager.locationServicesEnabled() {
let status = manager.authorizationStatus
switch status {
case .authorizedAlways, .authorizedWhenInUse:
manager.startUpdatingLocation()
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .denied:
Alert.okAlert(vc: self, title: "The location information service of the app\n Please turn it on", message: "Tap OK to go to the settings app") { (_) in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
case .restricted:
break
default:
break
}
}else {
Alert.okAlert(vc: self, title: "Location-based services\n Please turn it on", message: "You can turn it on from the "Settings" app ⇒ "Privacy" ⇒ "Location services"")
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let gps = manager.location?.coordinate else {
return
}
manager.stopUpdatingLocation()
let lat = gps.latitude
let lng = gps.longitude
print("longitude:\(String(describing: lat)),latitude:\(String(describing: lng))")
}
}
Alert
import UIKit
final class Alert {
static func okAlert(vc: UIViewController,title: String, message: String, handler: ((UIAlertAction) -> Void)? = nil) {
let okAlertVC = UIAlertController(title: title, message: message, preferredStyle: .alert)
okAlertVC.addAction(UIAlertAction(title: "OK", style: .default, handler: handler))
vc.present(okAlertVC, animated: true, completion: nil)
}
}
Up to this point, such as whether or not there is a location information service for terminals / apps
, processing of changed authentication status
, etc.
I have considered various places.
After that, I think some of you may have noticed.
If the authentication status is .restricted
, you haven't done anything.
This is because there are some restrictions such as parental control
when looking at Apple's documentation
The authentication status is called when the user cannot change the authentication status ↓
CLAuthorization Status .restricted
I used to use break
for a long time, but it's better to inform the user by alert etc. so let's fix it.
ViewController
case .restricted:
Alert.okAlert(vc: self, title: "Use location-based services\n Not allowed", message: "There are some restrictions")
Currently, we have been acquiring location information assuming iOS 14
or later.
I think that it will come out if you want to acquire location information even in versions other than iOS14.
In that case, the method used is different, and code that Apple has deprecated has also appeared. It gets a little complicated.
Now, let's actually lower the version of the application and implement it.
I read this article and lowered the iOS version, so please refer to it ↓ I will explain the world's most detailed new project created with Xcode 11 in Japanese to the point where it can be executed on iOS 12 or earlier
Now, the app is compatible with iOS11 ~ iOS14.
Before implementing it, I will introduce the code recommended / deprecated
by Apple.
//Instance initialization of CLLocationManager class and delegate method called when authentication status changes
//Not recommended iOS 4.2–14.0
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)
//Recommended iOS14-
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager)
//Get location information authentication status
//Not recommended iOS 4.2–14.0
func authorizationStatus() -> CLAuthorizationStatus
//Recommended iOS14-
var authorizationStatus: CLAuthorizationStatus
It has been changed like this.
After that, a new item called accurate position accuracy
has been added from iOS14.
Simply put, you can now choose whether to make your location accurate or ambiguous.
You can branch depending on the choice as shown in the code below ↓
switch locationManager.accuracyAuthorization {
//When you get accurate location information
case .fullAccuracy:
//When ambiguous location information is acquired
case .reducedAccuracy:
default:
break
}
This depends on the app you develop, so it's a good part to scrutinize. This article is written in detail, so please refer to it ↓ Core Location changes in iOS 14 Privacy around location information enhanced in iOS 14
The part to pay attention to is the recommended / deprecated code explained at the beginning.
You must also use deprecated code
to support iOS 14 and earlier versions.
Let's implement it immediately.
ViewController
import UIKit
import CoreLocation
final class ViewController: UIViewController {
private var locationManager: CLLocationManager = {
var locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.distanceFilter = 5
return locationManager
}()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
}
}
//Recommended delegate method
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
//For iOS 14 or later
if #available(iOS 14.0, *) {
if CLLocationManager.locationServicesEnabled() {
let status = manager.authorizationStatus
switch status {
case .authorizedAlways, .authorizedWhenInUse:
manager.startUpdatingLocation()
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .denied:
Alert.okAlert(vc: self, title: "The location information service of the app\n Please turn it on", message: "Tap OK to go to the settings app") { (_) in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
case .restricted:
Alert.okAlert(vc: self, title: "Use location-based services\n Not allowed", message: "There are some restrictions")
default:
break
}
}else {
Alert.okAlert(vc: self, title: "Location-based services\n Please turn it on", message: "You can turn it on from the "Settings" app ⇒ "Privacy" ⇒ "Location services"")
}
}
}
//Deprecated delegate method
//For iOS 14 or earlier
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if CLLocationManager.locationServicesEnabled() {
switch status {
case .authorizedAlways, .authorizedWhenInUse:
manager.startUpdatingLocation()
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .denied:
Alert.okAlert(vc: self, title: "The location information service of the app\n Please turn it on", message: "Tap OK to go to the settings app") { (_) in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
case .restricted:
Alert.okAlert(vc: self, title: "Use location-based services\n Not allowed", message: "")
default:
break
}
}else {
Alert.okAlert(vc: self, title: "Location-based services\n Please turn it on", message: "You can turn it on from the "Settings" app ⇒ "Privacy" ⇒ "Location services"")
}
}
It will be implemented like this, and both will be described. The inside of the method is almost the same except for the version specification.
You can now support iOS 14 and earlier versions
.
In most cases, error handling exists, but of course CLLocationManager also exists. func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
Then, how to describe it in the view controller is like this ↓
ViewController
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
//Originally, the error content is displayed on the screen with an alert
print("\((error as NSError).domain)")
}
This delegate method is called when CLLocationManager could not get the location information
.
If you don't implement this method, you'll get an error through, so be careful.
There are various errors, so please take a look ↓ CLError
And from here, I will introduce the places where I struggled.
As for the acquisition of authentication status, as I introduced earlier, there were recommended / deprecated codes
.
Is this quite annoying? And now I'm worried about the progressive tense ...
//Get location information authentication status
//Not recommended iOS 4.2–14.0
func authorizationStatus() -> CLAuthorizationStatus
//Recommended iOS14-
var authorizationStatus: CLAuthorizationStatus
First, if you compare these two codes
Those who do not recommend it are methods
, and those who recommend it are properties
.
First of all, this is a big difference.
And the deprecated func authorizationStatus ()
method
The app will return the authentication status regardless of the background or foreground state
This time, the authorizationStatus
property newly introduced in iOS 14
It only returns the authentication status when it was in the foreground state
.
In other words, if the authorizationStatus
property changes the authentication status in the background state
It returns the authentication status at the time of foreground state
.
Then, where did you struggle in this part?
This is the part that checks the presence or absence of the location information service of the application when returning from the background state
.
I will explain it in code so that it is actually easy to understand.
When getting authentication status
, it was the following implementation so far ↓
ViewController
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
}
//Method called when returning from background state
@objc private func willEnterForeground() {
if CLLocationManager.locationServicesEnabled() {
//Get authentication status(not recommended)
let status = CLLocationManager.authorizationStatus()
switch status {
//If not allowed
case .denied:
Alert.okAlert(vc: self, title: "The location information service of the app\n Please turn it on", message: "Tap OK to go to the settings app") { (_) in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
default:
break
}
}else {
Alert.okAlert(vc: self, title: "Location-based services\n Please turn it on", message: "You can turn it on from the "Settings" app ⇒ "Privacy" ⇒ "Location services"")
}
}
}
Next, implement with the recommended
code ↓
ViewController
private var locationManager: CLLocationManager = {
var locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.distanceFilter = 5
return locationManager
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
}
//Method called when returning from background state
@objc private func willEnterForeground() {
if CLLocationManager.locationServicesEnabled() {
//Get authentication status(Recommendation)
let status = locationManager.authorizationStatus
switch status {
//If not allowed
case .denied:
Alert.okAlert(vc: self, title: "The location information service of the app\n Please turn it on", message: "Tap OK to go to the settings app") { (_) in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
default:
break
}
}else {
Alert.okAlert(vc: self, title: "Location-based services\n Please turn it on", message: "You can turn it on from the "Settings" app ⇒ "Privacy" ⇒ "Location services"")
}
}
}
It doesn't change much, but you can see the difference by building it.
First, both will display an alert properly if the authentication status is .denid
at startup
.
However, if you change it to .denid> .restricted in background state
and return to foreground state
, it will be like this ↓
Originally, the alert displayed with Do not allow (.denid)
It was also displayed as authorizedWhenInUse
while using the app.
This bug will occur, so there is no choice but to deprecate the func authorizationStatus ()
method.
It is the current situation that is calling.
I came to this conclusion while investigating through various trials and errors.
In the first place, the authorizationStatus
property that appeared in iOS 14
func locationManagerDidChangeAuthorization (_ manager: CLLocationManager)
Use it as usual and use it in this method.
So you don't have to use the deprecated func authorizationStatus ()
method in the first place, right?
That method is a deligate method that is called when the authentication status is changed regardless of the initialization of the
CLLocationManager class and the background / foreground state, and willEnterForeground ()
is not necessary? ↓
//Method called when returning in the background
@objc private func willEnterForeground()
I think there are people who think that.
But what about this situation?
For example, requesting permission for location information from a user
Suppose you get an alert and select Do not allow
.
If you go from the flow so far, such an alert will be displayed and the screen will transition to the setting app
.
With this, it's okay to switch to the settings app, but when you return without doing anything
What if you don't call func willEnterForeground ()
?
If not called, nothing is checked.
Of course, since the authentication status has not changed
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager)
Is not called.
For the above reasons, I am using func willEnterForeground ()
You are also using the deprecated func authorizationStatus ()
method.
I researched various ways to improve this part, but even in the latest article, I used the deprecated func authorizationStatus ()
method and my head was full of ???.
If you know how to solve this, I would appreciate it if you could let me know in the comments.
That's why it became a long article consisting of three parts, but this time it is complete. I'm surprised that you have to take this into consideration just by acquiring location information.
Maybe this is totally different! There may be a part. In that case, please do not hesitate to comment.
magic number
, but I'll post this as an article at a later date.The source code up to this point is posted below, so please refer to it if you like ↓
Thank you for reading to the end.
If you create a extention file
and extend it, you can also suppress the volume of the view controller ↓
CLLocationManager+
import UIKit
import CoreLocation
extension CLLocationManager {
func alertStatusIfNeeded(vc: UIViewController) {
if #available(iOS 14.0, *) {
if CLLocationManager.locationServicesEnabled() {
let status = self.authorizationStatus
switch status {
case .notDetermined:
self.requestWhenInUseAuthorization()
case .restricted:
//Display alerts
case .denied:
//Display alerts
case .authorizedAlways:
self.startUpdatingLocation()
case .authorizedWhenInUse:
self.startUpdatingLocation()
default:
break
}
}else {
//Display alerts
}
}
}
func alertStatusIfNeededUnderiOSVer(vc: UIViewController) {
if CLLocationManager.locationServicesEnabled() {
let status = CLLocationManager.authorizationStatus()
switch status {
case .notDetermined:
self.requestWhenInUseAuthorization()
case .restricted:
//Display alerts
case .denied:
//Display alerts
case .authorizedAlways:
self.startUpdatingLocation()
case .authorizedWhenInUse:
self.startUpdatingLocation()
default:
break
}
}else {
//Display alerts
}
func alertStatusIfNeededBackground(vc: UIViewController) {
if !CLLocationManager.locationServicesEnabled() {
//Display alerts
return
}
let status = CLLocationManager.authorizationStatus()
if status == .denied {
//Display alerts
}
}
}
It looks like this in ViewController ↓ ※Excerpt
ViewController
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
manager.alertStatusIfNeeded(vc: self)
}
I also give it to here.
ViewController
import UIKit
import CoreLocation
final class ViewController: UIViewController {
private var locationManager: CLLocationManager = {
var locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.distanceFilter = 5
return locationManager
}()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.requestWhenInUseAuthorization()
locationManager.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
}
@objc private func willEnterForeground() {
if CLLocationManager.locationServicesEnabled() {
let status = CLLocationManager.authorizationStatus()
switch status {
case .denied:
Alert.okAlert(vc: self, title: "The location information service of the app\n Please turn it on", message: "Tap OK to go to the settings app") { (_) in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
default:
break
}
}else {
Alert.okAlert(vc: self, title: "Location-based services\n Please turn it on", message: "You can turn it on from the "Settings" app ⇒ "Privacy" ⇒ "Location services"")
}
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
if #available(iOS 14.0, *) {
if CLLocationManager.locationServicesEnabled() {
let status = manager.authorizationStatus
switch status {
case .authorizedAlways, .authorizedWhenInUse:
manager.startUpdatingLocation()
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .denied:
Alert.okAlert(vc: self, title: "The location information service of the app\n Please turn it on", message: "Tap OK to go to the settings app") { (_) in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
case .restricted:
Alert.okAlert(vc: self, title: "Use location-based services\n Not allowed", message: "")
default:
break
}
}else {
Alert.okAlert(vc: self, title: "Location-based services\n Please turn it on", message: "You can turn it on from the "Settings" app ⇒ "Privacy" ⇒ "Location services"")
}
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if CLLocationManager.locationServicesEnabled() {
switch status {
case .authorizedAlways, .authorizedWhenInUse:
manager.startUpdatingLocation()
case .notDetermined:
manager.requestWhenInUseAuthorization()
case .denied:
Alert.okAlert(vc: self, title: "The location information service of the app\n Please turn it on", message: "Tap OK to go to the settings app") { (_) in
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
}
case .restricted:
Alert.okAlert(vc: self, title: "Use location-based services\n Not allowed", message: "")
default:
break
}
}else {
Alert.okAlert(vc: self, title: "Location-based services\n Please turn it on", message: "You can turn it on from the "Settings" app ⇒ "Privacy" ⇒ "Location services"")
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let gps = manager.location?.coordinate else {
return
}
manager.stopUpdatingLocation()
let lat = gps.latitude
let lng = gps.longitude
print("longitude:\(String(describing: lat)),latitude:\(String(describing: lng))")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("\((error as NSError).domain)")
}
}
Alert
import UIKit
final class Alert {
static func okAlert(vc: UIViewController,title: String, message: String, handler: ((UIAlertAction) -> Void)? = nil) {
let okAlertVC = UIAlertController(title: title, message: message, preferredStyle: .alert)
okAlertVC.addAction(UIAlertAction(title: "OK", style: .default, handler: handler))
vc.present(okAlertVC, animated: true, completion: nil)
}
}
Recommended Posts