CKContainer accountStatusWithCompletionHandler returns wrong value - ios

accountStatusWithCompletionHandler method returns .NoAccount value. Any idea why returned value is not .Available? I am logged in to iCloud, and connecting to internet.
Doc says .NoAccount means:
The user’s iCloud account is not available because no account
information has been provided for this device.
I do not receive any error. The reason may be that app is not using private database? Doc says:
Call this method before accessing the private database to determine
whether that database is available.

Figured out, iCloud Drive was turned off for the app.

This code sample show status with iCloud. Probably request permission or promo user to login on .NoAccount case. I am thinking this is a case when you are not connected to the iCloude.
let container = CKContainer.defaultContainer()
container.accountStatusWithCompletionHandler({status, error in
switch status {
case .Available, .Restricted:
container.requestApplicationPermission(CKApplicationPermissions.PermissionUserDiscoverability,
completionHandler: { applicationPermissionStatus, error in
// handle applicationPermissionStatus for statuses like CKApplicationPermissionStatus.Granted, .Denied, .CouldNotComplete, .InitialState
})
case .CouldNotDetermine, .NoAccount:
// Ask user to login to iCloud
}
})

Related

How can I devide users into different groups(Identity pools) automatically?

I'm using AWS Cognito in my iOS app to implement the user signup & signin functions. I used the official Amplify SDK DOCs(https://aws-amplify.github.io/docs/ios/authentication) as a reference, and the app works well. But, actually I want to give my users different access authority which can achieve different contents(like files in S3).
While the user signing up the app, they must choose a group. Based on the group, they are given different access authority.
I've read the SDK DOCs and developer guide but I haven't found a good way to implement this function.
Is there any function in cognito I can use to separate users into different Identity pools? Or can anybody show me some samples which allow users having different access authority.
AWSMobileClient.sharedInstance().signUp(username: "your_username",
password: "Abc#123!",
userAttributes: ["email":"john#doe.com", "phone_number": "+1973123456"]) { (signUpResult, error) in
if let signUpResult = signUpResult {
switch(signUpResult.signUpConfirmationState) {
case .confirmed:
print("User is signed up and confirmed.")
case .unconfirmed:
print("User is not confirmed and needs verification via \(signUpResult.codeDeliveryDetails!.deliveryMedium) sent at \(signUpResult.codeDeliveryDetails!.destination!)")
case .unknown:
print("Unexpected case")
}
} else if let error = error {
if let error = error as? AWSMobileClientError {
switch(error) {
case .usernameExists(let message):
print(message)
default:
break
}
}
print("\(error.localizedDescription)")
}
}
You don't have to put users in different identity pools. Cognito already has user groups that you can use to assign different roles.
I am not familiar with IOS but there should be an admin add user to group method that you can use.

Swift - Firebase Authentication State Persistence

I'm currently thinking about implementing Firebase Auth to my Swift project, hence I've been reading some articles. - Namely among others this one.
I need some help understanding the given article. It's about "Authentication State Persistence". Does this mean, that if the value is set to local, the user will stay logged in even after closing the app? In other words, will he be able to sign up once and stay logged in until he decides to log out - even when he's offline?
Let's say a user decides not to create an account and logs in with "Anonymous Authentication" (I assume this is the type of login in this kind of case) - will he stay logged in forever as well or is there a danger of data loss, in case of going offline or closing the app?
First: the link you provided refers to a javascript firebase documentation
Second: the only thing available in IOS is you can create an anonymous user with
Auth.auth().signInAnonymously() { (authResult, error) in
// ...
let user = authResult.user
let isAnonymous = user.isAnonymous // true
let uid = user.uid
}
and you can convert it to a permanent user check This
Finally: whether the user is usual / anonymous , after you sign in you need to check this to show login/home screen every app open
if FIRAuth.auth()?.currentUser != nil {
print("user exists")
}
else {
print("No user")
}
and the user still exists unless you sign out regardless of whether you closed the app or not
If you are using the latest Firebase version, FIRAuth is now Auth:
if Auth.auth()?.currentUser != nil {
print("user exists")
}
else {
print("No user")
}

PhotoLibrary access .notDetermined, when denying then enabling

If I deny at first, then go to settings and allow in settings, in both cases the status is notDetermined, instead of denied then authorized.
Why is that happening?
It doesn't save the image when i click "Don't allow", but status becomes .notDetermined not .denied .
It saves, after i go to settings->Photos, uncheck "Never" and check "Add Photos Only". But the status stays .notDetermined, does not become .authorized
func save(){
guard let image = imageView.image else {return}
UIImageWriteToSavedPhotosAlbum(image, self, nil, nil)
let status = PHPhotoLibrary.authorizationStatus()
switch status {
case .authorized:
print("authorized")
return
case .notDetermined:
print("not determined")
case .denied, .restricted:
print("denied or restricted")
//please go to settings and allow access
promptToSettings()
}
}
I am asking permission to save an image to photo library.
When the first time the user tries to save, he gets asked: "App would like to add to Photos" "Don't Allow" "Ok"
If the user denied then tried to save again,i want to check and if the status is .denied, prompt the user to go to settings and allow.
But the code goes to .notDetermined block when the user does not give access the first time. It stays .notDetermined even after in settings the user allows access.
I downloaded your code and ran it. I was able to experience whatever you said. It always returned Not Determined status.
I did a little bit analysis on your code further. Please find my observation below.
In your current code, "PHPhotoLibrary.requestAuthorization" method is not called before reading the authorization status using "PHPhotoLibrary.authorizationStatus" method.
Though "UIImageWriteToSavedPhotosAlbum" in Save method triggers a Photos app permission pop up, it does not help here completely.
So, I called "askForAccessAgain" method in ViewDidLoad method in order to get the permission using "PHPhotoLibrary.requestAuthorization" method first.
After that, whenever i saved the photos using Save method, it returned the correct status, let it be either "Authorized" or "Denied".
If I choose "Never" and "Allow Photos Only", it returned "Denied or Restricted" status. When "Read and Write" is chosen, "authorized" is returned.
So, it looks like, We need to call "PHPhotoLibrary.requestAuthorization" method to get the permission first instead of relying on other Photo access methods ( like UIImageWriteToSavedPhotosAlbum) for getting the permission. Only then, correct status is returned by "PHPhotoLibrary.authorizationStatus".
One more addition info:
App Permissions are retained even after deleting the app and reinstalling it. Just take a look on this as well. It might help you in troubleshooting similar issues in future.
iPad remembering camera permissions after delete—how to clear?
Please let me know if this has resolved your issues.

Is anonymous auth enabled by default?

I have failed to find some info on this, but it seems that even though I do not force the user to auth(⚠️) at all, it seems as if I call FIRAuth.auth()?.currentUser at least a few seconds after startup, I will get a valid anonymous user back. Does the Firebase SDK log the current user in behind the scenes, or is an unauthed user always regarded anonymous?
⚠️ auth as in:
FIRAuth.auth()?.signInAnonymously() { (user, error) in
if error != nil {
print("Sign in anonymously failed: \(error)")
return
}
if let user = user {
print("user: \(user), is anon: \(user.isAnonymous), uid: \(user.uid)")
self.user = user
}
}
Update 1: It seems I may be wrong, or there is something else important here. It might be the case where a device that has previously signed in will subsequently always (or something... maybe using keychain etc) be treated as signed in, even if app is deleted between runs. Investigating...
Update 2: So after some investigation 🕵🏻 it seems that if we don't sign the user out specifically, the user will likely remain signed in forever OR at least a long time. Even between installs... I swear I tried to delete then install, and the user was still signed in...
No, you must enable anonymous authentication in the Firebase console in the 'Authentication' tab, under 'Sign In Method'

Apple Store build rejected while using CloudKit/iCloud

I just submited my app to the Apple Store and it failed submission because of the following issue and I am quite confuse about how to work around it.
From Apple - 17.2 Details - We noticed that your app requires users to
register with personal information to access non account-based
features. Apps cannot require user registration prior to allowing
access to app content and features that are not associated
specifically to the user.
Next Steps - User registration that requires the sharing of personal
information must be optional or tied to account-specific
functionality. Additionally, the requested information must be
relevant to the features.
My app uses CloudKit to save, retrieve and share records. But the app itself do not ask for any personal details neither share any personal details like emails, names, date of birth..., it just asks the user to have an iCloud account active on the device. Then CloudKit uses the iCloud credentials in order to work.
It becomes confusing because:
1 - I can't change the way CloudKit works and stop asking for the user to login on iCloud. Every app that uses CloudKit needs an user logged on iCloud.
2 - As other apps (facebookas an example) if you do not login the app cannot fundamentally work. So the login is not tied to specific functionality, but to the whole functionality of the app.
The code example bellow is called on an initial screen (before getting inside the app functional areas) every time the app starts to make sure the user has the iCloud going. If the user has iCloud I take him inside the app. If not I stop him and ask him to get iCloud sorted. But I guess that is what they are complaining about here - "User registration that requires the sharing of personal information must be optional or tied to account-specific functionality. Additionally, the requested information must be relevant to the features.".
Which puts myself in a quite confusing situation. Not sure how to resolve the issue. Has anyone has similar issues with CloudKit/iCloud/AppStore Submission? Any insights?
iCloud check code bellow:
func cloudKitCheckIfUserIsAuthenticated (result: (error: NSError?, tryAgain: Bool, takeUserToiCloud: Bool) -> Void){
let container = CKContainer.defaultContainer()
container.fetchUserRecordIDWithCompletionHandler{
(recordId: CKRecordID?, error: NSError?) in
dispatch_async(dispatch_get_main_queue()) {
if error != nil
{
if error!.code == CKErrorCode.NotAuthenticated.rawValue
{
// user not on icloud, taki him there
print("-> cloudKitCheckIfUserIsAuthenticated - error fetching ID - not on icloud")
// ERROR, USER MUST LOGIN TO ICLOUD - LOCK HIM OUTSIDE THE APP
result(error: error, tryAgain: false, takeUserToiCloud: true)
}
print("-> cloudKitCheckIfUserIsAuthenticated - error fetching ID - other error \(error?.description)")
// OTHER ERROR, TRY AGAIN
result(error: error, tryAgain: true, takeUserToiCloud: false)
}
else
{
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
publicDatabase.fetchRecordWithID(recordId!,
completionHandler: {(record: CKRecord?, error: NSError?) in
if error != nil
{
// error getting user ID, try again
print("-> cloudKitCheckIfUserIsAuthenticated - error fetching user record - Error \(error)")
// ERROR, TRY AGAIN
result(error: error, tryAgain: true, takeUserToiCloud: false)
}
else
{
if record!.recordType == CKRecordTypeUserRecord
{
// valid record
print("-> cloudKitCheckIfUserIsAuthenticated - fetching user record - valid record found)")
// TAKE USER INSIDE APP
result(error: error, tryAgain: false, takeUserToiCloud: false)
}
else
{
// not valid record
print("-> cloudKitCheckIfUserIsAuthenticated - fetching user record - The record that came back is not a user record")
// ERROR, TRY AGAIN
result(error: error, tryAgain: true, takeUserToiCloud: false)
}
}
})
}
}
}
}
Initially my application would ask the user to login to iCloud on launch screen. If the users did not have an iCloud account functional they would not be able to get inside the app.
Solution
Let the user get inside the app and click on the main sections. In fact the app was completely useless but the user could see it's odd empty screens without the ability to save or load anything. By the time they tried to load or save things I would prompt them the needed to login on iCloud to make the app usable.
Practical outcome
I don't think apple's change request added anything of value to the UX. In fact it just added complexity for the user to understand what he can do and what he cannot.
As an example Facebook locks the user outside if the user do not provide his personal details because without this data the application has absolutely no use and that is my case... You could arguably say the user should be able to get inside, but what he would see or do? Then you would have to cater for all the exceptions this UX builds and throw warnings for the user to fix the account issue everywhere on an annoying pattern of warnings.
So I am not sure "how" Facebook could get it approved and I could not.
Although I got the app approved I disagree Apple feedback improved the application in any way.

Resources