Swift 1.2
Xcode 6
Long-time listener, first-time caller.
Hello,
Straight from the horse's mouth: "To handle changes in iCloud availability, register to receive the NSUbiquityIdentityDidChangeNotification notification."
Here is the code they provide to implement this:
[[NSNotificationCenter defaultCenter]
addObserver: self
selector: #selector (iCloudAccountAvailabilityChanged:)
name: NSUbiquityIdentityDidChangeNotification
object: nil];
I Swiftified it in my app to:
var observer = NSNotificationCenter.defaultCenter().addObserverForName
(NSUbiquityIdentityDidChangeNotification, object: nil, queue: NSOperationQueue.mainQueue()
){...completion block...}
src: https://developer.apple.com/library/ios/documentation/General/Conceptual/iCloudDesignGuide/Chapters/iCloudFundametals.html#//apple_ref/doc/uid/TP40012094-CH6-SW6
What is the correct way to implement this? Does it go in the AppDelegate? Do we remove the observer when the app gets sent to the background?
The problem I'm encountering is that when the Ubiquity Token changes, the app is terminated anyway because the user has changed iCloud settings.
How do you all manage to subscribe to this notification, and, if you don't, what do you do instead to keep track of the current logged in iCloud user?
Thank you!
Short Answer
To be notified in iOS when a user logs in or out of iCloud while using your app, use CKAccountChangedNotification, not NSUbiquityIdentityChanged.
Long Answer
I've been trying to get this to work as well. I remembered something from one of the talks at WWDC16 that there was something like this that they recommended to use. However, from the sample code they provide, I've only been able to find NSUbiquityKeyIdentityChanged, which I haven't been able to get to work.
So I went back to the video (it's from 2015). That's where I saw them refer to CKAccountChangedNotification – and it works exactly as expected:
Launch your app on the simulator from Xcode
Exit to the Settings app on the simulator
Log in (or out) of iCloud account
Go back into your app (tap icon on simulator home screen)
A notification is received.
Exit to Settings app again
Log back out (or in) to iCloud account
Go back into your app again
Another notification is received.
In Swift 3.0 there was another renaming:
Now the NSUbiquityIdentityDidChangeNotification has changed into NSNotification.Name.NSUbiquityIdentityDidChange.
So the full registering is the following:
// Register for iCloud availability changes
NotificationCenter.default.addObserver(self, selector: #selector(...), name: NSNotification.Name.NSUbiquityIdentityDidChange, object: nil)
On iOS 10 I found that NSUbiquityIdentityDidChangeNotification was never sent. Provided I had a CKContainer (as per the docs), CKAccountChangedNotification was sent in very limited circumstances.
Built with xCode 9.1 then tested on iOS 10.02 iPhone 6+ and iOS 11.0.3 iPhone SE
CKAccountChangedNotification was sent if
User logged into iCloud account, or
User enabled iCloud Drive in iOS 11. This always resulted in iCloud Drive->App being enabled. However, fetching the account status afterwards yielded NoAccount!
User enabled iCloud Drive in iOS 10. The subsequent state of iCloud Drive->App was whatever it was when I disabled iCloud Drive. The account status was appropriate. However, if iCloud Drive->App was disabled at this point, enabling it did not produce a termination or a notification.
Application was terminated if
User logged out of iCloud regardless of iCloud Drive status
User disabled iCloud Drive->App
User disabled iCloud Drive (even if iCloud Drive->App already disabled)
User started the app with iCloud Drive enabled, then enabled iCloud Drive->App
I found the same issues, see this question for my comments.
To summarize: On iOS I think apps are killed anyway when the iCloud account changes (or is just signed off). So no need to listen to NSUbiquityIdentityDidChangeNotification.
If you are on tvOS, however, your app is not killed. When your app becomes active, you do receive one or more NSUbiquitousKeyValueStoreDidChangeExternallyNotification with NSUbiquitousKeyValueStoreChangeReasonKey set to NSUbiquitousKeyValueStoreAccountChange because tvOS exchanges your entire ubiquity key-value-store.
Maybe you could use that with some trickery to detect account changes, e.g. store a NSUUID in the ubiquity key-value-store, and when the value changes, it means there is a new account. But you cannot detect if an account is logged off.
Related
Currently, when I change the camera permissions for my app in Settings, then navigate back to my app, the app will force a refresh and I will lose my place in the app. I follow these steps exactly:
Open an app that uses the camera permission.
Navigate to some screen within the app (so you can visibly see the refresh later)
Go to the Settings app, navigate to the app's settings, and toggle
the camera permission
Double click home and go back to the app.
After a few seconds, it will refresh, bringing you back to the
first screen
Note: I'm using an iPhone 6 running iOS 8.4
I've noticed this behavior on all apps that have the camera permission. My question is:
Is there some way to prevent the app from refreshing/restarting (on resume) after changing the camera permission? It doesn't seem to happen when you toggle location services, for example, and from a usability perspective this is horrible.
User scenario: If a user navigates deep into your app, then needs to change the camera permission (because say they accidentally clicked no last time), they will be forced to navigate back to that screen when they return. This is especially harmful for an app trying to sell you something, or sign you up for a new account. They might try to introduce a new feature where you can use the camera to take a profile picture or scan your credit card. Because the user didn't know about this feature, they might have previously denied camera access, but now want to enable it. After trying to reenable, they come back to your app to find they have to spend 5+ minutes signing up / making a purchase, again! After that, even I would probably give up.
I'm sure that there is no other ways to prevent restarting app. Actually you will get a SIGKILL message but no Crash log when toggling settings. See below links-
https://devforums.apple.com/message/715855
https://devforums.apple.com/message/714178
The only way to prevent this scenario is to save the previous state of you application while terminating.
Store app your current data into a json/plist/NSUserDefaults/archive user model at applicationWillTerminate: method and
restore saved data at applicationWillEnterForeground:
For example- #SignUpViewController register for UIApplicationWillTerminateNotification which will fire when the app is about to terminate. Store user information there.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillTerminate:)
name: UIApplicationWillTerminateNotification object:nil];
}
- (void)applicationWillTerminate:(NSNotification *)notification
{
// store your data here
}
Hope this will help you:)
The accepted answer is correct, however the workaround does not appear to work in the current version of iOS (9.2) - the application seems to terminate before UIApplicationWillTerminateNotification is fired. However by listening to UIApplicationDidEnterBackgroundNotification, the same can be achieved. e.g in Swift, put this in viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "enteringBackground:", name: UIApplicationDidEnterBackgroundNotification, object: nil)
and have a function like this:
func enteringBackground(sender:AnyObject){
// Save application state here
}
In my iOS app I keepSynced enabled as follows
[[self.userManagementRef child:#"PUBLISHED_CONTENTS"] keepSynced:YES];
While user device does not have internet connection firebase database has changed several times. After user device connect to the internet only receive last change of the database. Otherwise user have to crash the app and reopen the app to get all the changes. How to solve this problem?
In firebase you can just use
Database.database().isPersistenceEnabled = true or
[FIRDatabase database].persistenceEnabled = YES;
(swift/obj-c)
to allow your app to store changes and sync once the network connection is restored. Documentation attached.
https://firebase.google.com/docs/database/ios/offline-capabilities
Currently, when I change the camera permissions for my app in Settings, then navigate back to my app, the app will force a refresh and I will lose my place in the app. I follow these steps exactly:
Open an app that uses the camera permission.
Navigate to some screen within the app (so you can visibly see the refresh later)
Go to the Settings app, navigate to the app's settings, and toggle
the camera permission
Double click home and go back to the app.
After a few seconds, it will refresh, bringing you back to the
first screen
Note: I'm using an iPhone 6 running iOS 8.4
I've noticed this behavior on all apps that have the camera permission. My question is:
Is there some way to prevent the app from refreshing/restarting (on resume) after changing the camera permission? It doesn't seem to happen when you toggle location services, for example, and from a usability perspective this is horrible.
User scenario: If a user navigates deep into your app, then needs to change the camera permission (because say they accidentally clicked no last time), they will be forced to navigate back to that screen when they return. This is especially harmful for an app trying to sell you something, or sign you up for a new account. They might try to introduce a new feature where you can use the camera to take a profile picture or scan your credit card. Because the user didn't know about this feature, they might have previously denied camera access, but now want to enable it. After trying to reenable, they come back to your app to find they have to spend 5+ minutes signing up / making a purchase, again! After that, even I would probably give up.
I'm sure that there is no other ways to prevent restarting app. Actually you will get a SIGKILL message but no Crash log when toggling settings. See below links-
https://devforums.apple.com/message/715855
https://devforums.apple.com/message/714178
The only way to prevent this scenario is to save the previous state of you application while terminating.
Store app your current data into a json/plist/NSUserDefaults/archive user model at applicationWillTerminate: method and
restore saved data at applicationWillEnterForeground:
For example- #SignUpViewController register for UIApplicationWillTerminateNotification which will fire when the app is about to terminate. Store user information there.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillTerminate:)
name: UIApplicationWillTerminateNotification object:nil];
}
- (void)applicationWillTerminate:(NSNotification *)notification
{
// store your data here
}
Hope this will help you:)
The accepted answer is correct, however the workaround does not appear to work in the current version of iOS (9.2) - the application seems to terminate before UIApplicationWillTerminateNotification is fired. However by listening to UIApplicationDidEnterBackgroundNotification, the same can be achieved. e.g in Swift, put this in viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "enteringBackground:", name: UIApplicationDidEnterBackgroundNotification, object: nil)
and have a function like this:
func enteringBackground(sender:AnyObject){
// Save application state here
}
I'm creating a cloudkit app, and have been trying multiple ways to get the NSUbiquityIdentityDidChangeNotification, but I never am able to get this notification.
I've tried both of these code versions under the delegate didFinish and the viewDidLoad methods. And I tried calling it from another notification - UIApplicationDidBecomeActiveNotification. I also put import Foundation at top of files.
Here's the basic code I've tried:
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "handleIdentityChanged:",
name: NSUbiquityIdentityDidChangeNotification,
object: nil)
// And this one I tried too from another post here on SO:
var localeChangeObserver = NSNotificationCenter.defaultCenter().addObserverForName(NSUbiquityIdentityDidChangeNotification, object: nil, queue: NSOperationQueue.mainQueue()) { _ in
println("The user’s iCloud login changed: should refresh all user data.")
}
Does anyone know how to get this notification to work for only a cloudkit app in swift? I really just want to detect the iCloud status change and then initiate fetching the userID if there's been a change.
Not that I need to access the ubiquityIdentityToken, but I was wondering why not store the token and every-time the app starts compare the current token with the one in local storage to see if it's a different account or nil? Therefore, why is getting the notification necessary?
Also, the code for getting the token only seems to work if I turn on "iCloud Documents", which I don't need. Does anyone know the implications of having that turned on for a social app that doesn't need it? And is there another way to get the token without enabling iCloud Documents?
This is the code I used to get token and placed in the delegate didFinish method, but only works if iCloud documents is turned on:
var token = NSFileManager.defaultManager().ubiquityIdentityToken
println("token is \(token!)")
On iOS, when I sign out of iCloud, my app is killed. So there seems not really to be a need to receive a NSUbiquityIdentityDidChangeNotification. Like you have said, it seems to be sufficient to compare the current token to the saved token.
On the Apple TV though, my app was not killed when I logged out of iCloud. Here I had noticed the notification was not fired, like you described. Since the app is not killed, a notification would be in order. (Did Apple forget to kill apps on Apple TV when iCloud account is changed?)
With Apple TV there is no iCloud documents container available (unless I explicitly share one from an iOS app). I found that on the dev center website, for the app identifier, iCloud was shown as "Configurable" and not "Enabled" if no document container was selected. I wonder if this has an effect on receiving notifications.
Both on the Apple TV and iOS, I can also confirm that the iCloud token is nil when not using documents (here: key-value-store only). Now that makes it difficult for Apple TV apps (because the app is not killed on iCloud account change, like on iOS) to detect account changes.
I have just noticed that my Apple TV app does received several NSUbiquitousKeyValueStoreDidChangeExternallyNotification when I log into another iCloud account, to reflect the changes. I guess this is as good as it gets. These notifications come with the NSUbiquitousKeyValueStoreChangeReasonKey key in userInfo, and a value of NSUbiquitousKeyValueStoreAccountChange indicates the account has changed.
Sorry for not being able to provide a direct solution, maybe it helped to share my experience.
To be notified in iOS when a user logs in or out of iCloud while using your app, use CKAccountChangedNotification instead of NSUbiquityIdentityChanged notification.
(Longer explanation: https://stackoverflow.com/a/38689094/54423.)
I think I did everything I could find on tutorials and apple's documentation.
But my Core Data do no get out of my iOS device to the iCloud servers.
In short, the following calls are made :
Get Ubiquity token and check that user wants to use iCloud - done - works OK
Connect to Ubiquity containers using URLForUbiquityContainerIdentifier: - done - though this should be useless (according to a previous discussion thread)
Registered to receive and handle NSPersistentStoreCoordinatorStoresWillChangeNotification, NSPersistentStoreCoordinatorStoresDidChangeNotification, NSPersistentStoreDidImportUbiquitousContentChangesNotification notifications
I do see in the console the messages :
-[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](771): CoreData: Ubiquity: mobile~xxxx
Using local storage: 1
and
-[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](771): CoreData: Ubiquity: mobile~xxxx
Using local storage: 0
and my handlers are called.
I can use the app perfectly in the iOS device, but no data is uploaded to iCloud.
When I delete the app from the device, iOS ask me confirmation if I really want to delete the app, and then asks for a second confirmation because "some iCloud data is pending upload, and I will loose them".
I checked that data could be pending for more than 24 hours.
And, of course, my iOS device has network and iCloud is working fine.
Any idea of what I could have done stupidly ?
I got it back to work.
Actually, I did not change anything. But I fully restored the iOS device.
Any explanations will be welcome.