Help Users Resolve Permission and Device Setting Errors
Once the user opts into the IQL program, you need to check for errors that might come up while specifying permission and device settings. We have explained how to check for these errors in the various scenarios as described in this section.
This section includes the following topics:
Help Users Resolve Permission and Device Setting Errors
Help Users Resolve Permission and Device Setting Errors
Help Users Resolve Permission and Device Setting Errors
Help Users Resolve Permission and Device Setting Errors
Help Users Resolve Permission and Device Setting Errors
Help Users Resolve Permission and Device Setting Errors
1.0. Listen To Zendrive SDK Callbacks for Permission Or Setting Changes
Use the following sections to listen to SDK callbacks for changes to permissions or settings:
1.1. Create a Permissions Manager
Use the following developer instructions to create a Permissions Manager, which will be responsible for checking device permission changes and settings errors. The Permissions Manager will also notify you whenever a user's permissions are changed, or when any setting errors occur.
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()
}
}
}
}
}
}
1.1.1.Create a Location Permission Class
Use the following code snippet to create a Location Permission Class to handle location permission errors:
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()
}
}j
1.1.2. Create a NotificationPermission Class
Use the following code snippet to create a Notification Permission Class to handle notification permission errors:
enum NotificationPermissionStatus {
case denied
case unknown
}
@available(iOS 13.0, *)
final class NotificationPermissionForiOS13: NSObject, Permission {
typealias T = NotificationPermissionStatus
let center = UNUserNotificationCenter.current()
func requestPermission(handler: @escaping (GenericPermissionStatus<NotificationPermissionStatus>) -> Void) {
let options: UNAuthorizationOptions = [.alert, .sound]
center.requestAuthorization(options: options) { (granted: Bool, error: Error?) in
if let err = error {
DDLogError("Error while requesting notification permission: \(err.localizedDescription)")
handler(.restricted(.unknown))
return
}
if granted {
handler(.granted)
} else {
handler(.restricted(.denied))
}
}
}
func checkPermission(handler: ((GenericPermissionStatus<NotificationPermissionStatus>) -> Void)?) -> GenericPermissionStatus<NotificationPermissionStatus>? {
UNUserNotificationCenter.current().getNotificationSettings { settings in
switch settings.authorizationStatus {
case .notDetermined:
handler?(.notDetermined)
case .authorized:
handler?(.granted)
case .denied:
handler?(.restricted(.denied))
default:
handler?(.restricted(.unknown))
}
}
return nil
}
}
1.2. Enable the Permissions Manager
Use the following developer instructions to listen to onSettingsChanged
Zendrive SDK callback and inform PermissionsManager
to pull the latest permissions status.
Pull the exact permission status using Zendrive SDK and OS APIs within Permissions Manager. This will include location permission status via Zendrive SDK and notifications via UNNotificationCenter
API. Be sure to include the comment, 'Based on the latest permission status, add or remove notifications to, or from the notification center'.
extension ZendriveManager: ZendriveDelegate {
///Implement `settingsChanged` method from ZendriveDelegate to listen to any device or permission error callback from Zendrive SDK.
func settingsChanged(_ settings: Settings) {
core.permissionsManager.checkPermissionErrors() // core = IQLRef.sharedInstance
core.permissionsManager.onLocationPermissionUpdated()
}
}
2. Refresh Permissions and Device Settings Status
Use the instructions provided below to refresh permissions and device settings status periodically.
2.1. Pull the Latest Permission Status Whenever the Application Is Launched
//Create a singleton instance of "PermissionsManager" which in turn handles the application state changes to check if any permission errors
class IQLRef {
static let sharedInstance = swIQLRef()
var permissionsManager: PermissionsManager!
var backgroundFetchManager: BackgroundFetchManager! // Used in next section
func setup(with config: IQLConfig) {
...............
initializeDependencies()
................
}
func initializeDependencies() {
....................
permissionsManager = PermissionsManager()
backgroundFetchManager = BackgroundFetchManager()
......................
}
}
2.2. Integrate Background Fetch
Integrate Background Fetch to periodically wake up your application in the background and check for any permission errors that may have occurred.
AppDelegate Code Snippet
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
.
.
.
.
//Invoke below methods of 'BackgroundFetchManager' to register for background app refresh and background processing, 'BackgroundFetchManager' handles checking for any permission errors whenever app wakes up in the background
if #available(iOS 13.0, *) {
core.backgroundFetchManager.registerBackgroundAppRefersh() // core = IQLRef.sharedInstance
core.backgroundFetchManager.registerBackgroundProcessing()
} else {
DDLogInfo("Cannot register BGTasks,OS version is lower than 13.0 ")
}
.
.
.
.
return true
}
}
BackgroundFetchManager
Use this code snippet to pull the latest permission status on background fetch.
@available(iOS 13.0, *)
final class BackgroundFetchManager {
private let bgAppRefreshIdentifier = "\(Bundle.main.bundleIdentifier!).background_app_refresh"
private let bgProcessIdentifier = "\(Bundle.main.bundleIdentifier!).background_processing"
func registerBackgroundAppRefersh() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: bgAppRefreshIdentifier,
using: nil
) { task in
DDLogInfo("Triggered BGAppRefreshTask on: \(Date())")
self.scheduleAppRefresh()
self.handleAppRefresh(task)
}
scheduleAppRefresh()
}
func registerBackgroundProcessing() {
BGTaskScheduler.shared.register(
forTaskWithIdentifier: bgProcessIdentifier,
using: nil
) { task in
DDLogInfo("Triggered BGProcessingTask on: \(Date())")
self.scheduleBackgroundProcessing()
self.handleAppRefresh(task)
}
scheduleBackgroundProcessing()
}
}
private extension BackgroundFetchManager {
func scheduleAppRefresh() {
let request = BGAppRefreshTaskRequest(identifier: bgAppRefreshIdentifier)
request.earliestBeginDate = Date(timeIntervalSinceNow: 6 * 60 * 60)
do {
try BGTaskScheduler.shared.submit(request)
DDLogInfo("Scheduled a BGAppRefreshTask on: \(Date())")
} catch {
DDLogError("Could not schedule app refresh: \(error.localizedDescription)")
}
}
func scheduleBackgroundProcessing() {
let request = BGProcessingTaskRequest(identifier: bgProcessIdentifier)
do {
try BGTaskScheduler.shared.submit(request)
DDLogInfo("Scheduled a BGProcessingTask on: \(Date())")
} catch {
DDLogError("Could not schedule background processing: \(error.localizedDescription)")
}
}
func handleAppRefresh(_ task: BGTask) {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.addOperation {
if IQLRef.sharedInstance.loginRepository.isLoggedIn {
let core = IQLRef.sharedInstance
core.permissionsManager.checkPermissionErrors()
}
}
task.expirationHandler = {
queue.cancelAllOperations()
task.setTaskCompleted(success: true)
}
let lastOperation = queue.operations.last
lastOperation?.completionBlock = {
task.setTaskCompleted(success: true)
}
}
}
/* For testing background tasks while in development refer below link
https://developer.apple.com/documentation/backgroundtasks/starting_and_terminating_tasks_during_development
Note: Make sure app's buildConfiguration is Release config
*/
2.3. Integrate Silent Push Notification
Integrate silent heartbeat notifications to wake your application up in the background to check for any permission errors that may have occurred. If any errors are found, the user is notified.
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
.
.
.
.
//Register a device to receive remote notifications
UNUserNotificationCenter.current().delegate = self
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options:[.badge, .alert, .sound]) { (granted, error) in
guard granted else { return }
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
.
.
.
.
return true
}
//Check for permission errors when silent notification for permission error check is received
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
DDLogInfo("Received remote notification with userInfo: \(userInfo)")
if let data = userInfo["data"] as? [String: Any],
data["type"] as! String == "heartbeat" {
IQLRef.sharedInstance.permissionsManager.checkPermissionErrors()
}
}
}
3.0. Handle Permissions and Setting Issues
Use the following instructions and UX designs to handle permissions and setting issues, for the following scenarios:
Location setting is turned Off.
Location not set to Always.
Location not set to Precise.
Present notifications experience to fix the permission issues outside the application.
Location Setting Is Turned Off

Location Not Set to 'Always'

Location Not Set To 'Precise'

4.0. Handle Notification Permission Issues
The following code snippet demonstrates how to check for notification permission errors and display them within the app using in-app notifications:

5.0. IQL Reference Application UX Design
Here is the overall design implementation for the IQL Reference Application:
6.0. Publisher Integration Testing Checklist
Follow the checklist given below to test user's permission and device setting error experience:
Permission Errors: Validate that the application displays an error whenever a required permission is missing. Additionally, check that a notification is displayed asking the user to fix the permission issue.