3

I check if user is logged into iCloud, if not I set a alert. But instead I want to send the user to appropriate settings, where user can actually log into iCloud and once logged in is complete redirect to current view controller.

How to I modify this code?

var iCloudStatus:Bool = false
func ifUserLoggedinToICloud() -> Bool {

        let alertController = UIAlertController(title: "Alert", message: "iCloud Status", preferredStyle: .alert)
        defaultContainer.accountStatus(completionHandler: { (accountStatus, error) in
            if (accountStatus == .available) {
                print("iCloud is available")
                iCloudStatus = true
            }
            else {

                print("iCloud is not available, log into iCloud")
                alertController.message = "iCloud not available, log into iCloud"
                alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
                self.present(alertController, animated: true, completion: nil)
            }

        })


        return iCloudStatus
    }
vrao
  • 545
  • 2
  • 12
  • 33
  • I tried doing this in AppDelegate. I tried UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil). Gives error Type 'UIApplication' has no member 'openSettingsURLString'. What is the correct way to handle checks on user if logged into cloud? – vrao Aug 25 '19 at 19:25
  • Unrelated but be aware that `ifUserLoggedinToICloud` returns always `false`. – vadian Aug 25 '19 at 19:26

1 Answers1

1

The account status check is asynchronous, so as Vadian commented, the funtion will always return false before ifUserLoggedInToICloud is altered. You should run the code in the completion closure.

You can direct a user to settings with the following:

defaultContainer.accountStatus(completionHandler: { (accountStatus, error) in
    switch accountStatus {
    case .noAccount, .restricted:
        guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) else { return }
        if UIApplication.shared.canOpenURL(settingsUrl) {
        UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
            // Completion handler when settings opened
        })
    case .available:
        print("Account available")
    case .couldNotDerermine:
        print("Error")
    }
})

You cannot redirect the user back from the settings page. It is up to the user to navigate back to your app.

There are private APIs to redirect to specific iOS settings pages (such as the "App-Prefs:root=CASTLE" URL), but Apple do not permit use of these.

Chris
  • 4,009
  • 3
  • 21
  • 52
  • Will this work as it is synchronous? if FileManager.default.ubiquityIdentityToken != nil { iCloudStatus = true } else { guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) else { return false} if UIApplication.shared.canOpenURL(settingsUrl) { UIApplication.shared.open(settingsUrl, completionHandler: { (success) in // Completion handler when settings opened }) } } – vrao Aug 25 '19 at 20:17
  • Also in the completion handler when settings are opened what should I do? Capture status and just return and let the user navigate back to the original view controller? – vrao Aug 25 '19 at 20:20
  • @vrao This is asynchronous - it should run inside the completion closure of the account check. I have edited the answer and added a switch statement for the different account status enumeration cases. – Chris Aug 25 '19 at 20:31
  • Ok, since it is inside completion handler, when the handler returns, If set a boolen and return it from a function, then return value would be true correct? case .available: print("Account available") self.iCloudStatus = true return iCloudStatus – vrao Aug 25 '19 at 20:48
  • @vrao If the completion handler is inside a function (like your example), the function will always return before the completion handler (as the iCloud account check involves a network request with a delay). Any code that depends on the value should be inside the completion handler. The order of code execution is different in situations like this. You could also call some other function from the completion handler of course, or give the global Boolean value a `didSet` function (which is called any time the value changes). – Chris Aug 25 '19 at 22:08
  • Thanks you for explaining – vrao Aug 25 '19 at 22:20
  • for got to ask one last question; what to do where you suggested coding for //Completion handler when settings opened – vrao Aug 25 '19 at 22:28
  • @vrao I don’t think you need to do anything when settings opens - at that point the user will be in the settings menu. You could set another global Boolean value like `wentToSettings = true`, then you could check for this when the app comes to the foreground again after settings page - have a search for KVO detecting app coming to foreground to see how to run a function when the app moves from background to foreground. I hope this helps. – Chris Aug 26 '19 at 03:43
  • great help from you – vrao Aug 27 '19 at 04:12