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.
Related
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'
I am building a project with Firebase that involves users. There is a known Firebase bug where even after a user has been deleted from the Firebase Authentication section, the user is still able to access the app and still has read/write permissions to the database. I believe it has something to do with the token being stored by Firebase on the user's device.
As a work around to this, I have implemented a "user checkup" where my app will check to see if the userID of the logged in user exists in my real-time database. This way all i have to do is delete the userID node from the real-time database and that will cause this checkup to fail and thus log the user out permanently.
The problem i am having is that the call to observeSingleEvent(of: .value...) is sporadically not returning. By this i mean that sometimes it returns immediately as is expected, however sometimes it just doesn't return at all. For example yesterday morning it wasn't working at all. Yesterday afternoon and night it worked fine. Now today it is no longer working again. Code below...
func checkDatabaseForFIRUser(withId: String, callback: #escaping (Bool)-> Void) {
let fireUsersRef = fireRootRef.child("all-users").child(withId)
fireUsersRef.observeSingleEvent(of: .value, with: {(snapshot)-> Void in
print("user check result -> \(snapshot)")
if snapshot.exists() {
print("SNAP EXISTS")
callback(true)
} else {
print("SNAP NOT EXISTS")
callback(false)
}
}, withCancel: {(error)-> Void in
print("CANCEL BLOCK ERROR = \(error) and localized description = \(error.localizedDescription)")
})
}
I have also noticed that during the times when I am unable to retrieve data I am also unable to write anything to the database, it is as if nothing at all works. My Firebase Rules have been set to "Public" so anyone can read/write. And I have a working internet connection on my device
EDIT: everything works fine on Simulator. Issue is only occurring on the actual device
EDIT #2: This issue seems to have subsided, I haven't noticed it for at least a few weeks now
I bet that by the time you are calling observeSingleEvent, you are log out, so probably that's why it is not returning anything.
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
}
})
I have a couple hundred users that I need to remove from my parse app. However, when I delete the user accounts the users are still able to use the app fully without a problem. Is there anyway to "force" the logout remotely? Or what else would you suggest? Thanks!
It sounds like the user is being cached on the device and I don't think parse has a remote way to clear cached data on there. I like to put a user refresh(now fetch since refresh is deprecated) function when app opens to get the latest data for that user.
You could put a fetch function when the app opens and if it returns a specific error, it would mean the user doesn't exist and then set the current user to nil. I'm not sure which error it returns and I'm at work so I can't try it right now. I would hope that if the user doesn't exist, it would return kPFErrorUserWithEmailNotFound = 205...
Here are the error codes: https://parse.com/docs/ios/api/Constants/PFErrorCode.html
You will have to give it a try but I am thinking something like this (sudo-code):
post.fetchIfNeededInBackgroundWithBlock {
(post: PFObject?, error: NSError?) -> Void in
if let someError = error {
if someError = kPFErrorUserWithEmailNotFound {
// User doesn't exist!
}
} else {
// User exists and is fetched successfully
}
}
I'm developing an iOS app that gives users access to their OneDrive/SkyDrive and I've run into a very annoying issue:
The very first time a user links the app to their OneDrive, everything goes as expected:
They have to enter a user id and password
Then they have to agree to let the app access their info
Then they get to browse their OneDrive
That's all good.
But, if the app closes, and you try to access the OneDrive again, rather than skipping straight to #3, and being able to access the OneDrive, they are stopped at step #2 (step 1 is skipped, as expected) and they have to agree again to let the app access their info.
The code is taken directly from the iOS examples in the online documentation (with some slight modification based on samples found here on Stack Overflow), but, here it is for inspection:
- (void) onedriveInitWithDelegate:(id)theDelegate {
self.onedriveClient = [[LiveConnectClient alloc] initWithClientId:MY_CLIENT_ID
delegate:theDelegate
userState:#"initialize"];
}
And then, theDelegate implements this:
- (void)authCompleted:(LiveConnectSessionStatus) status
session:(LiveConnectSession *) session
userState:(id) userState {
NSLog(#"Status: %u", status);
if ([userState isEqual:#"initialize"]) {
NSLog( #"authCompleted - Initialized.");
if (session == nil) {
[self.onedriveClient login:self
scopes:[NSArray arrayWithObjects:#"wl.basic", #"wl.signin", #"wl.skydrive_update", nil]
delegate:self
userState:#"signin"];
}
}
if ([userState isEqual:#"signin"]) {
if (session != nil) {
NSLog( #"authCompleted - Signed in.");
}
}
}
I thought that perhaps the status value might give a clue and that maybe I could avoid the login call, but it's always zero/undefined when I get to authCompleted after calling initWithClientId. (And session is always nil.)
Is there a scope I'm missing? Is there a different call to make rather than a straight-up login call? Or is it more complicated than that? I've seen reference to "refresh tokens" related to OAuth2 login, but I've not been able to find any concrete examples of how they might be used in this situation.
Any help and/or insights greatly appreciated.
Diz
Well, it turns out that the answer is pretty simple here. I just needed to add the "wl.offline_access" scope to my list of scopes during the initial login operation. The docs didn't really imply this type of behavior for this scope, but, that did the trick for me.
With this new scope added, subsequent invocations of the app no longer bring up the "agree to give the app these permissions" dialog, and I can go straight to browsing the OneDrive.
(Credit where it's due: Stephane Cavin over at the microsoft forums gave me the tip I needed to work this out. Gory details are here:
http://social.msdn.microsoft.com/Forums/en-US/8c5c7a99-7e49-401d-8616-d568eea3cef1/ios-onedrive-skydrive-app-displays-permissions-dialog-every-time-it-runs?forum=onedriveapi )
Diz