Capture User Permissions

After verifying the user’s information, the IQL program requests the user for certain permissions, in order to track trips. The following sections describe the user permissions required on iOS devices:

Capture User Permissions

Capture User Permissions

Capture User Permissions

Capture User Permissions

Capture User Permissions

Capture User Permissions

1. High-level Capture User Permissions Design

The following section describes the user permissions that you need to enable on iOS devices so that IQL can track trips:

In the IQL Reference Application for iOS v13.4 and above, the user is prompted to set location access to 'Always allow' within the application itself.

Note that if the user selects the 'Allow while using' location permission option, iOS allows the in-app dialog box to switch to 'Always allow', from within the application. However if user selects the 'Allow once' option, iOS does not allow the in-app dialog box to load. The application itself does not distinguish between the two options.

Therefore, we recommend that a backup dialog box to take the user to the device settings menu should always be present. If this backup dialog box is not present, the user will navigate away from the application to enable 'Always Allow' permission.

2.0. Create PermisionsManager Class

Create PermissionsManager class to handle the necessary permissions, as described in the following code snippet:

enum PermissionErrorType {
    case locationNotAutherizedAlways
    case preciseLocationNotEnabled
    case locationServicesTurnedOff
    case notificationsTurnedOff
}
protocol PermissionManagerDelegate: AnyObject {
    func onLocationPermissionUpdated()
}
final class PermissionsManager {
    let locationPermission = PermissionFactory.locationPermission()
    let notificationPermission = PermissionFactory.notificationPermission()
    var permissionErrors = [PermissionErrorType]()
    var delegate: PermissionManagerDelegate?
    init() {
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(handleAppForeground),
                                               name: UIApplication.didBecomeActiveNotification,
                                               object: nil)
    }
    func checkPermissionErrors() {
        self.permissionErrors.removeAll()
        if let permissionStatus = locationPermission.checkPermission(handler: nil) {
            switch permissionStatus {
            case .restricted(let restrictedStatus):
                switch restrictedStatus {
                case .locationServiceOff:
                    permissionErrors.append(.locationServicesTurnedOff)
                case .whileInUse:
                    permissionErrors.append(.locationNotAutherizedAlways)
                case .precisionOff:
                    permissionErrors.append(.preciseLocationNotEnabled)
                case .denied:
                    permissionErrors.append(.locationNotAutherizedAlways)
                case .unknown:
                    permissionErrors.append(.locationNotAutherizedAlways)
                }
            case .granted:
                break
            case .notDetermined:
                permissionErrors.append(.locationNotAutherizedAlways)
            }
        }
        checkNotificationPermission()
    }
    func checkNotificationPermission() {
        notificationPermission.checkPermission { notificationStatus in
            switch notificationStatus {
            case .notDetermined, .restricted:
                self.permissionErrors.append(.notificationsTurnedOff)
            case .granted:
                break
            }
            self.handlePermissionErrors(errors: self.permissionErrors)
        }
    }
    func getLocationPermissionStatus() -> GenericPermissionStatus<LocationPermissionStatus>? {
        return locationPermission.checkPermission(handler: nil)
    }
    func requestLocationPermission() {
        locationPermission.requestPermission { _ in
            DDLogDebug("requested location permission")
        }
    }
    func onLocationPermissionUpdated() {
        self.delegate?.onLocationPermissionUpdated()
    }
}
private extension PermissionsManager {
    @objc
    func handleAppForeground() {
        if Zendrive.isSDKSetup {
            if IQLRef.sharedInstance.loginRepository.isPermissionFlowCompleted {
                checkPermissionErrors()
            }
        }
    }
    func checkNotificationsPermissionStatus(handler:  @escaping (Bool) -> Void) {
        UNUserNotificationCenter.current().getNotificationSettings { settings in
            if settings.authorizationStatus == .authorized {
                handler(true)
            } else {
                handler(false)
            }
        }
    }
    func handlePermissionErrors(errors: [PermissionErrorType]) {
        var isForeground = true
        DispatchQueue.main.sync {
            isForeground = UIApplication.shared.applicationState == .active
        }
        if isForeground {
            let core = IQLRef.sharedInstance
            core.notifHelper.notifyPermissionErrors(errors) // Notify for in-app notifications
            self.permissionErrors.removeAll()
        } else {
            if errors.isEmpty { // Remove already shown displayed notifications if no errors
                let notificationManager = PermissionErrorUserNotificationManager()
                notificationManager.removeLocationDeniedNotifications()
                return
            }
            checkNotificationsPermissionStatus { isEnabled in
                if isEnabled {
                    DispatchQueue.main.sync {
                        let notificationManager = PermissionErrorUserNotificationManager()
                        notificationManager.showLocationDeniedNotification()
                    }
                }
            }
        }
    }
}

2.1. Create a GenericPermissionProtocol Class

Use the following code snippet to create a GenericPermissionProtocol Class which can be used as an abstract class to deal with different types of permissions.

protocol Permission {
    associatedtype T
    func requestPermission(handler: @escaping (_ permissionStatus: GenericPermissionStatus<T>) -> Void)
    func checkPermission(handler: ((_ permissionStatus: GenericPermissionStatus<T>) -> Void)?) -> GenericPermissionStatus<T>?
}
enum GenericPermissionStatus<T> {
    case notDetermined
    case restricted(T)
    case granted
}
final class AnyPermission<S>: Permission {
    typealias T = S
    private let checkPermissionHandler: (((GenericPermissionStatus<T>) -> Void)?) -> GenericPermissionStatus<S>?
    private let requestPermissionHandler: (@escaping (GenericPermissionStatus<S>) -> Void) -> Void
    init<U: Permission>(permission: U) where U.T == T {
        checkPermissionHandler = permission.checkPermission
        requestPermissionHandler = permission.requestPermission
    }
    func checkPermission(handler: ((GenericPermissionStatus<S>) -> Void)?) -> GenericPermissionStatus<S>? {
        checkPermissionHandler(handler)
    }
    func requestPermission(handler: @escaping (GenericPermissionStatus<S>) -> Void) {
        requestPermissionHandler(handler)
    }
}
class PermissionFactory {
    static func locationPermission() -> AnyPermission<LocationPermissionStatus> {
        if #available(iOS 14.0, *) {
            return AnyPermission(permission: LocationPermissionForiOS14())
        } else if #available(iOS 13.0, *) {
            return AnyPermission(permission: LocationPermissionForiOS13())
        } else {
            fatalError("OS version is below 13.0")
        }
    }
    static func notificationPermission() -> AnyPermission<NotificationPermissionStatus> {
       if #available(iOS 13.0, *) {
            return AnyPermission(permission: NotificationPermissionForiOS13())
        } else {
            fatalError("OS version is below 13.0")
        }
    }
}

3.0. Capture Location Permissions

The user needs to enable the Always allow location permission, so that trips can be automatically detected in the background. Obtaining the location permission is a two-step process. You must first capture the Allow while using the app permission, then capture the Always allow location permission. The following sections describe how to obtain these permissions:

3.1. Customize Location Permission Dialog Messages

Update info.plist by adding the following code snippets to customize the system dialog messages for permissions request:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Your precise location helps us to better determine your driving behavior even if you don't have the app open.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Your precise location helps us to better determine your driving behavior even if you don't have the app open.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your precise location helps us to better determine your driving behavior even if you don't have the app open.</string>

3.2. Implement the Permission Abstract Class

Use the following code snippet to create a fresh class to implement the GenericPermissionProtocol abstract class for Location Permission.

You need to define separate classes for iOS 13 and iOS 14. These classes in turn will use Zendrive SDK to fetch the location permission states from the application.

enum LocationPermissionStatus {
    case denied
    case whileInUse
    case precisionOff
    case locationServiceOff
    case unknown
}
@available(iOS 14.0, *)
final class LocationPermissionForiOS14: NSObject, Permission, CLLocationManagerDelegate {
    let locationManager = CLLocationManager()
    var requestPermissionHandler: ((GenericPermissionStatus<LocationPermissionStatus>) -> Void)?
    override init() {
        super.init()
    }
    func checkPermission(handler: ((GenericPermissionStatus<LocationPermissionStatus>) -> Void)?) -> GenericPermissionStatus<LocationPermissionStatus>? {
        var permissionErrors: [PermissionErrorType]
        guard CLLocationManager.authorizationStatus() != .notDetermined else {
            return .notDetermined
        }
        guard CLLocationManager.authorizationStatus() != .denied else {
            return .restricted(.denied)
        }
        if let errors = Zendrive.getSettings()?.errors {
            permissionErrors = errors.compactMap({ error -> PermissionErrorType? in
                switch error.errorType {
                case .locationPermissionNotAuthorized:
                    return .locationNotAutherizedAlways
                case .locationAccuracyAuthorizationReduced:
                    return .preciseLocationNotEnabled
                case .locationServiceOff:
                    return .locationServicesTurnedOff
                case .activityPermissionNotAuthorized:
                    fatalError("Reached invalid case activityPermissionNotAuthorized")
                }
            })
            guard permissionErrors.isEmpty else {
                return getLocationError(error: permissionErrors)
            }
            return .granted
        }
        return nil
    }
    private func getLocationError(error: [PermissionErrorType]) -> (GenericPermissionStatus<LocationPermissionStatus>)? {
        if error.contains(.locationServicesTurnedOff) {
            return .restricted(.locationServiceOff)
        } else if error.contains(.locationNotAutherizedAlways) {
            return .restricted(.whileInUse)
        } else if error.contains(.preciseLocationNotEnabled) {
            return .restricted(.precisionOff)
        } else {
            return nil
        }
    }
    func requestPermission(handler: @escaping (GenericPermissionStatus<LocationPermissionStatus>) -> Void) {
        requestPermissionHandler = handler
        locationManager.requestWhenInUseAuthorization()
    }
}
@available(iOS 13.0, *)
final class LocationPermissionForiOS13: NSObject, Permission, CLLocationManagerDelegate {
    let locationManager = CLLocationManager()
    var requestPermissionHandler: ((GenericPermissionStatus<LocationPermissionStatus>) -> Void)?
    override init() {
        super.init()
        locationManager.delegate = self
    }
    func checkPermission(handler: ((GenericPermissionStatus<LocationPermissionStatus>) -> Void)?) -> GenericPermissionStatus<LocationPermissionStatus>? {
        var permissionErrors: [PermissionErrorType]
        guard CLLocationManager.authorizationStatus() != .notDetermined else {
            return .notDetermined
        }
        guard CLLocationManager.authorizationStatus() != .denied else {
            return .restricted(.denied)
        }
        if let errors = Zendrive.getSettings()?.errors {
            permissionErrors = errors.compactMap({ error -> PermissionErrorType? in
                switch error.errorType {
                case .locationPermissionNotAuthorized:
                    return .locationNotAutherizedAlways
                case .locationAccuracyAuthorizationReduced:
                    return .preciseLocationNotEnabled
                case .locationServiceOff:
                    return .locationServicesTurnedOff
                case .activityPermissionNotAuthorized:
                    fatalError("Reached invalid case activityPermissionNotAuthorized")
                }
            })
            guard permissionErrors.isEmpty else {
                return getLocationError(error: permissionErrors)
            }
            return .granted
        }
        return nil
    }
    private func getLocationError(error: [PermissionErrorType]) -> (GenericPermissionStatus<LocationPermissionStatus>)? {
        if error.contains(.locationServicesTurnedOff) {
            return .restricted(.locationServiceOff)
        } else if error.contains(.locationNotAutherizedAlways) {
            return .restricted(.whileInUse)
        } else if error.contains(.preciseLocationNotEnabled) {
            return .restricted(.precisionOff)
        } else {
            return nil
        }
    }
    func requestPermission(handler: @escaping (GenericPermissionStatus<LocationPermissionStatus>) -> Void) {
        requestPermissionHandler = handler
        locationManager.requestWhenInUseAuthorization()
    }
}

3.3. Ensure Location Services Setting Is Turned On

This section describes how to ensure that the location services setting is turned on.

Make sure that system-wide location services are turned on before requesting for location permission from the user.

3.4. Capture Permission for ‘Allow While Using’

Zendrive SDK requires the user to select the Always Allow permission to work accurately. However, iOS guidelines insist that applications prompt users to enable the Allow While Using permission first. This section describes how to enable location permissions while using the application:

3.4.1. How To Handle Denial Of 'Allow while using' Permission

When the user clicks on the Go to App Settings button, they will be taken to system settings to change the location permission authorization settings. Update the application UI based on user response, as shown in the following code snippet:

3.5. Capture 'Always Allow' Permission

This section describes how to capture the 'always allow' location permission on iOS devices.

3.5.1. How To Handle Denial Of 'Always Allow' Permission

Use the following code snippet to handle denial of the Always Allow permission:

3.5.2. How To Handle Denial Of 'Precise' Location Permission

4.0. Capture Notification Permission

We recommend that you notify your users whenever they qualify for an offer, for which you will require notification permission from the user. We recommend asking this permission after the user has completed the onboarding process.

5.0. IQL Reference Application UX Screens

Here is the overall design implementation for the IQL Reference Application:

6.0. Publisher Integration Testing Checklist

Follow the below-given integration testing checklist while testing the capture user permissions experience in your application:

  • Always Allow: Prompt the user to 'Always allow' location permissions. If the user selects any other location access type, display an error and inform the user that location access needs to be set to, 'Always' for the application to work correctly. In case Precise location access is turned off, make sure that the application displays an error, and prompts the user to grant Precise location access.

  • Allow while using: If the user selects the 'Allow while using' location permission, the application should display a pop-up to change the permission to 'Always allow'. Alternatively, you can choose to retain the 'Allow while using' location permission. If the user selects the 'Change to Always allow' option, the location permission is set as per the application's expectation.

  • Allow Once: If the user selects the 'Allow once' option in response to location permission access, make sure the application displays an error. Prompt the user to set location access to 'Always'.

  • 'Don't allow' Location Access: If the user selects the 'Don't allow', option in response to location permission access, ensure that the application displays an error stating that location permission needs to be set to 'Always' for the application to work accurately.

  • Notification Permission: Ensure that the user is requested for notification permission at least once. This allows the application to inform the user promptly whenever an offer is available.

    It also allows the application to inform users of any possible issues affecting the program and offer generation.