Location permission determiner - ios

I'm trying to build a permission determiner class, which basically determine the permission.
So far I've done below code, however I keep getting error in the case statements case LocationUsage.WhenInUse: and case .Always:.
It says that
enum case is not a member fo type PrivateResoure.LocationUsage?
What am I doing wrong in this small struct?
public struct PrivateResource {
public enum LocationUsage {
case WhenInUse
case Always
}
var usage: LocationUsage?
public var isNotDeterminedAuthorization: Bool {
return CLLocationManager.authorizationStatus() == .NotDetermined
}
public var isAuthorized: Bool {
switch usage {
case LocationUsage.WhenInUse:
return CLLocationManager.authorizationStatus() == .AuthorizedWhenInUse
case .Always:
return CLLocationManager.authorizationStatus() == .AuthorizedAlways
}
}
}

I see two problems:
usage is an Optional, so you have to unwrap it.
.AuthorizedWhenInUse and .AuthorizedAlways are not part of the CLAuthorizationStatus choices.
The choices are:
Authorized, Denied, NotDetermined, Restricted
A quick fix:
public var isAuthorized: Bool {
switch usage! {
case LocationUsage.WhenInUse:
return CLLocationManager.authorizationStatus() == .Authorized
case .Always:
return CLLocationManager.authorizationStatus() == .Authorized
}
}
Two notes: it solves the issue but my example solution changes your logic - you may have to adapt. Also, I've force unwrapped the Optional for this example: I'll let you choose the safest way instead that is adapted for your code.

Related

ATTrackingManager.AuthorizationStatus always returns notDetermined

I don't know can I use this functionality in my UI tests on iOS, but I try it, an have problem with this.
In my UI tests I can choose Allow tracking for my app or I can decline tracking, but after all these actions, I want checkout status IDFA via ATTrackingManager.AuthorizationStatus, but this method always returns notDetermined. If I go to Settings > Privacy > Tracking, here I see that settings applied correctly (switch Allow App To Request To Track is on and switch for my app in right state (on or off)).
I don't have any idea why I recieve wrong AuthorizationStatus.
Here is my code in my XCTestCase:
import AppTrackingTransparency
enum TrackingStatus {
case authorized
case denied
case notDetermined
}
func map(_ status: ATTrackingManager.AuthorizationStatus) -> TrackingStatus {
switch ATTrackingManager.trackingAuthorizationStatus {
case .notDetermined:
return .notDetermined
case .authorized:
return .authorized
default:
return .denied
}
}
func advertisingTrackingStatusCheckout(status: TrackingStatus) {
print("IDFA status: \(ATTrackingManager.trackingAuthorizationStatus)")
var currentTrackingStatus: TrackingStatus {
return map(ATTrackingManager.trackingAuthorizationStatus)
}
guard currentTrackingStatus == status else {
XCTFail("IDFA status: \(currentTrackingStatus), expected: \(status)")
return
}
}
After settings IDFA status in my UI test, i call this method, ex. advertisingTrackingStatusCheckout(status: TrackingStatus.denied)
But it always returns notDetermined.
It behaviors have only one exception: If I manually set switch Allow App To Request To Track to off-state, calling the ATTrackingManager.trackingAuthorizationStatus will returns denied.
Delete the App, And call your function in sceneDidBecomeActive with delay. So once your app become active then it will shown. Am facing the same issue now its resolved. Like this
func sceneDidBecomeActive(_ scene: UIScene) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.requestPermission()
}
}
func requestPermission() {
if #available(iOS 14, *) {
ATTrackingManager.requestTrackingAuthorization { status in
switch status {
case .authorized:
// Tracking authorization dialog was shown
// and we are authorized
print("Authorized Tracking Permission")
// Now that we are authorized we can get the IDFA
case .denied:
// Tracking authorization dialog was
// shown and permission is denied
print("Denied Tracking Permission")
case .notDetermined:
// Tracking authorization dialog has not been shown
print("Not Determined Tracking Permission")
case .restricted:
print("Restricted Tracking Permission")
#unknown default:
print("Unknown Tracking Permission")
}
}
} else {
// Fallback on earlier versions
}
}

How to reference external iOS system state updates in SwiftUI?

There are many possible variants of this question, but take as an example the CNAuthorizationStatus returned by CNContactStore.authorizationStatus(for: .contacts), which can be notDetermined, restricted, denied, or authorized. My goal is to always show the current authorization status in my app's UI.
To expose this to SwiftUI, I might make an ObservableObject called ModelData with a contacts property:
final class ModelData: ObservableObject {
#Published var contacts = Contacts.shared
}
Where contacts contains my contact-specific model code, including Authorization:
class Contacts {
fileprivate let store = CNContactStore()
static let shared = Contacts()
enum Authorization {
case notDetermined
case restricted
case denied
case authorized
}
var authorization: Authorization {
switch CNContactStore.authorizationStatus(for: .contacts) {
case .notDetermined:
return .notDetermined
case .restricted:
return .restricted
case .denied:
return .denied
case .authorized:
return .authorized
#unknown default:
return .notDetermined
}
}
}
And I might add a method that a button could call to request access:
func requestAccess(handler: #escaping (Bool, Error?) -> Void) {
store.requestAccess(for: .contacts) { (granted, error) in
// TODO: tell SwiftUI views to re-check authorization
DispatchQueue.main.async {
handler(granted, error)
}
}
}
And for the sake of simplicity, say my view is just:
Text(String(describing: modelData.contacts.authorization))
So my questions are:
Given that ModelData().contacts.authorization calls a getter function, not a property, how can I inform the SwiftUI view when I know it's changed (e.g. where the TODO is in the requestAccess() function)?
Given that the user can toggle the permission in the Settings app (i.e., the value might change out from under me), how can I ensure the view state is always updated? (Do I need to subscribe to an NSNotification and similarly force a refresh? Or is there a better way?)
As #jnpdx pointed out - using #Published with a class (especially a singleton that never changes) is probably not going to yield any useful results
#Published behaves like CurrentValueSubject and it will trigger an update only in case there are changes in the value it is storing/observing under the hood. Since it is storing a reference to the Contacts.shared instance, it won't provide/trigger any updates for the authorization state changes.
Now to your question -
Given that ModelData().contacts.authorization calls a getter function, not a property, how can I inform the SwiftUI view when I know it's changed
As long as you are directly accessing a value out of the getter ModelData().contacts.authorization, it's just a value of Contacts.Authorization type that does NOT provide any observability.
So even if the value changes over time (from .notDetermined => .authorized), there is no storage (reference point) against which we can compare whether it has changed since last time or not.
We HAVE TO define a storage that can compare the old/new values and trigger updates as needed. This can achieved be by marking authorization as #Published like following -
import SwiftUI
import Contacts
final class Contacts: ObservableObject {
fileprivate let store = CNContactStore()
static let shared = Contacts()
enum Authorization {
case notDetermined
case restricted
case denied
case authorized
}
/// Since we have a storage (and hence a way to compare old/new status values)
/// Anytime a new ( != old ) value is assigned to this
/// It triggers `.send()` which triggers an update
#Published var authorization: Authorization = .notDetermined
init() {
self.refreshAuthorizationStatus()
}
private func refreshAuthorizationStatus() {
authorization = self.currentAuthorization()
}
private func currentAuthorization() -> Authorization {
switch CNContactStore.authorizationStatus(for: .contacts) {
case .notDetermined:
return .notDetermined
case .restricted:
return .restricted
case .denied:
return .denied
case .authorized:
return .authorized
#unknown default:
return .notDetermined
}
}
func requestAccess() {
store.requestAccess(for: .contacts) { [weak self] (granted, error) in
DispatchQueue.main.async {
self?.refreshAuthorizationStatus()
}
}
}
}
struct ContentView: View {
#ObservedObject var contacts = Contacts.shared
var body: some View {
VStack(spacing: 16) {
Text(String(describing: contacts.authorization))
if contacts.authorization == .notDetermined {
Button("Request Access", action: {
contacts.requestAccess()
})
}
}
}
}
I think you have it all working.
This line gets called when user changes the access level from the Settings app.
Text(String(describing: modelData.contacts.authorization))
So your view is always displaying the current state.

Swift enum CLAuthorizationStatus is CLAuthorizationStatus instead of one of the cases

This is probably just a dumb mistake, but I have a problem with my enum that I'm completely stuck at.
I have the following code:
func locationManager(_ manager: CLLocationManager,
didChangeAuthorization status: CLAuthorizationStatus) {
if (status == .authorizedWhenInUse) {
print("-- authorized when in use")
locationManager.startUpdatingLocation()
} else {
print("--- \(String(reflecting: status))")
}
print("--- didChangeAuthorizationStatus = \(status)")
}
But the debug print statements I have in it print the following:
--- __C.CLAuthorizationStatus
--- didChangeAuthorizationStatus = CLAuthorizationStatus
Why does the parameter status, which in my mind should be a case that belongs to the enum CLAuthorizationStatus (like .accepted) print CLAuthorizationStatus? It makes no sense to me, and I suspect some beginner mistake but I can't find it...
In my opition, this code should behave like the following code:
enum TestEnum {
case a
case b
}
var c = TestEnum.a
func test(name e: TestEnum) {
if e == .a {
print("case a")
} else {
print("other case")
}
print(String(reflecting: e))
}
test(name: c)
Which prints, as expected
case a
__lldb_expr_16.TestEnum.a
It's not your fault but a current limitation of Swift on imported enums.
You may need to work with rawValues:
print(status.rawValue)
For example, 3 means CLAuthorizationStatus.authorizedAlways, but as far as I know, there's no simple way to get the symbolic representation of the value.
As #OOPer said, its a limitation of Swift on imported enums.
You can create an extension to your code:
import CoreLocation
extension CLAuthorizationStatus {
var description: String {
switch self {
case .notDetermined:
return "notDetermined"
case .restricted:
return "restricted"
case .denied:
return "denied"
case .authorizedAlways:
return "authorizedAlways"
case .authorizedWhenInUse:
return "authorizedWhenInUse"
#unknown default:
return "unknown"
}
}
}

Reactive Location - Handle success and failure block

It might be simple question.
I'm using Reactive Location, to get user's current location, please find my below code,
ReactiveLocation.authorizeAction.apply(.whenInUse).startWithResult {
switch $0 {
case let .success(status):
print("Current user permission status on WhenInUse is \(status)")
case let .failure(error):
print(error.localizedDescription)
}
}
Here error is .restricted and .denied, I want user to be presented with the error message according to error. How to identify it.
In above code, completion block looks like this,
Please help me to solve the issue.
I suggest learning more about Swift enum.
You can check for .restricted and .denied the same way you checked for .success and .failure.
The only difference is LocationAuthorizationError doesn't have associated values.
ReactiveLocation.authorizeAction.apply(.whenInUse).startWithResult {
switch $0 {
case let .success(status):
print("Current user permission status on WhenInUse is \(status)")
case let .failure(actionError):
switch actionError {
case .producerFailed(.restricted):
print("Authorization Restricted")
case .producerFailed(.denied):
print("Authorization Denied")
default:
break
}
}
}

How to get the exact authorization status of location service?

If user has explicitly denied authorization for this application, or location services are disabled in Settings, it will return Denied status. How can I know the exact reason of it?
I've made this two function to check for each case
If user explicitly denied authorization for your app only you can check it like this,
+ (BOOL) isLocationDisableForMyAppOnly
{
if([CLLocationManager locationServicesEnabled] &&
[CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied)
{
return YES;
}
return NO;
}
If location services are disabled in Settings,
+ (BOOL) userLocationAvailable {
return [CLLocationManager locationServicesEnabled];
}
And I'm using it like this,
if([UserLocation userLocationAvailable]) {
//.... Get location.
}
else
{
if([UserLocation isLocationDisableForMyAppOnly]) {
//... Location not available. Denied accessing location.
}
else{
//... Location not available. Enable location services from settings.
}
}
P.S. UserLocation is a custom class to get user location.
Swift
Use:
CLLocationManager.authorizationStatus()
To get the authorization status. It's a CLAuthorizationStatus, you can switch on the different status' like this:
let status = CLLocationManager.authorizationStatus()
switch status {
case .authorizedAlways:
<#code#>
case .authorizedWhenInUse:
<#code#>
case .denied:
<#code#>
case .notDetermined:
<#code#>
case .restricted:
<#code#>
}
Swift:
I've made this two function to check for each case
If user explicitly denied authorization for your app only you can check it like this,
class func isLocationDisableForMyAppOnly() -> Bool {
if CLLocationManager.locationServicesEnabled() && CLLocationManager.authorizationStatus() == .denied {
return true
}
return false
}
If location services are disabled in Settings,
class func userLocationAvailable() -> Bool {
return CLLocationManager.locationServicesEnabled()
}
And I'm using it like this,
if UserLocation.userLocationAvailable() {
//.... Get location.
} else {
if UserLocation.isLocationDisableForMyAppOnly() {
//... Location not available. Denied accessing location.
} else {
//... Location not available. Enable location services from settings.
}
}
P.S. UserLocation is a custom class to get user location.
Swift 5.0 iOS 14.0
Since 'authorizationStatus()' was deprecated in iOS 14.0 you should use the instance directly.
authorizationStatus is now a property of CLLocationManager.
You can use the following example:
import CoreLocation
class LocationManager {
private let locationManager = CLLocationManager()
var locationStatus: CLAuthorizationStatus {
locationManager.authorizationStatus
}
}
Have also a look at this question: AuthorizationStatus for CLLocationManager is deprecated on iOS 14

Resources