SwiftUI direct a User to their settings page if authorizationStatus is denied

Viewed 721

I have a function that I call to access the User's contact information. But, I need this function to prompt the User with an alert that can redirect them to their Settings page if the authorizationStatus is denied.

So far, I have code that successfully prompts the user for their Contact Information. But, I want to run the show_settings_alert function if the case ends up being .denied as shown below:

func requestContactPermissions(completion: @escaping (Bool) -> ()) {
    let store = CNContactStore()
    var authStatus = CNContactStore.authorizationStatus(for: .contacts)
    switch authStatus {
    case .denied:
        show_settings_alert()
}

Here is the show_settings_alert function that I found within a handful of tutorials regarding this problem:

func show_settings_alert() {
    let alertController = UIAlertController(title: "TITLE", message: "Please go to Settings and turn on the permissions", preferredStyle: .alert)
    let settingsAction = UIAlertAction(title: "Settings", style: .default) { (_) -> Void in
        guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
            return
        }
        if UIApplication.shared.canOpenURL(settingsUrl) {
            UIApplication.shared.open(settingsUrl, completionHandler: { (success) in })
         }
    }
    let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: nil)
    alertController.addAction(cancelAction)
    alertController.addAction(settingsAction)
    
    self.present(alertController, animated: true, completion: nil)
}

The issue is, I am attempting to run self.present within a function, and thus, I do not have access to self. I also recognize this is UIKit code, and I'm not entirely sure how to get the same functionality while working with SwiftUI.

I know there is an Alert class that comes with SwiftUI, but I'd prefer to not have this logic within my template view.

1 Answers

I did a similar implementation when I used camera usage before.
I think you can implement it by using Combine and SwiftUI. The following implementation will show Alert when you select denied or restricted.

import Combine
import Contacts

class Contact: ObservableObject {
  var dispose = Set<AnyCancellable>()
  let contactStore = CNContactStore()
  @Published var invalidPermission: Bool = false
  
  var authorizationStatus: AnyPublisher<CNAuthorizationStatus, Never> {
    Future<CNAuthorizationStatus, Never> { promise in
      self.contactStore.requestAccess(for: .contacts) { (_, _) in
        let status = CNContactStore.authorizationStatus(for: .contacts)
        promise(.success(status))
      }
    }
    .eraseToAnyPublisher()
  }
  
  func requestAccess() {
    self.authorizationStatus
      .receive(on: RunLoop.main)
      .map { $0 == .denied || $0 == .restricted }
      .assign(to: \.invalidPermission, on: self)
      .store(in: &dispose)
  }
}

ContactView show an alert by the invalidPermission status of the Contact class.

In this code, you press a button to ask contacts for permission to use it. If denied, show alert for go to settings. If it is authorized, alert does not show.

struct ContactView: View {
  @ObservedObject var contact = Contact()
  
  var body: some View {
    VStack {
      Button(action: {
        // if pressed button, request contact permissions
        self.contact.requestAccess()
      }) {
        Text("Request access to Contacts")
      }
    }
    .alert(isPresented: self.$contact.invalidPermission) {
      Alert(
        title: Text("TITLE"),
        message: Text("Please go to Settings and turn on the permissions"),
        primaryButton: .cancel(Text("Cancel")),
        secondaryButton: .default(Text("Settings"), action: {
          if let url = URL(string: UIApplication.openSettingsURLString), UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
          }
        }))
    }
  }
}
Related