This question already has answers here:
declaring global variables in iPhone project
(6 answers)
Closed 9 years ago.
I am making a login system in objective c (for an iPhone app) and I would like to record if the user is logged in or not. I figured the best way to do this would be to use a global variable BOOL isLoggedIn after I have validated their credentials. Can someone please help me or give me some advice because I am completely lost on how to do this.
Thanks in advance
-(void)saveToUserDefaults:(BOOL)isLoggedIn
{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults) {
[standardUserDefaults setBool:isLoggedIn forKey:#"isLoggedIn"];
[standardUserDefaults synchronize];
}
}
Use NSUserDefaults, It will retain the value until your app is deleted from the phone.
use this two function
-(void)addToNSUserDefaults:(id)pobjValue forKey:(NSString *)pstrKey{
NSUserDefaults *objUserDefaults = [NSUserDefaults standardUserDefaults];
[objUserDefaults setObject:pobjValue forKey:pstrKey];
[objUserDefaults synchronize];
}
-(id)fetchFromNSUserDefaults:(NSString *)pstrKey{
NSUserDefaults *objUserDefaults = [NSUserDefaults standardUserDefaults];
return [objUserDefaults objectForKey:pstrKey];
}
When you store it
[self addToNSUserDefaults:#"YES" forKey:#"isLoggedIn"];
When retrive it
if ([self fetchFromNSUserDefaults:#"isLoggedIn"]==nil || [[self fetchFromNSUserDefaults:#"isLoggedIn"] isEqualToString:#""]) {
//Go to Login
}
else
//all ready Login
You can either use NSUserDefaults to store the app's login state as stated by the others. The value will stay as is until the app is deleted. i.e. even when you kill the app or restart the device, if the last value was 'logged in'.
If you want to 'reset' the user's state when the app is killed or device is restarted, then another approache is to make your User's object as a singleton. When app is killed or device is restarted, unlike the NSUserDefault, the user's state will be reset (as the object no longer exists) and user will need to login again. Using this method: http://lukeredpath.co.uk/blog/a-note-on-objective-c-singletons.html to create singleton of your user's object
+ (id)sharedInstance
{
static dispatch_once_t pred = 0;
__strong static id _sharedObject = nil;
dispatch_once(&pred, ^{
_sharedObject = [[self alloc] init]; // or some other init method
});
return _sharedObject;
}
Add the above to your User's class and then you can then add the appropriate properties related to the user's state, e.g BOOL isLoggedOn, and then you can access this along the line:
BOOL isLoggedIn = [[User sharedInstance] isLoggedIn];
Related
I can't get NSUserDefaults to save a BOOL value. It's really simple and people do it but it just simply will not save. I tried everything I know. The most recent attempt being this:
if (_EndHide == YES) {
NSDictionary *aProperties = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]forKey:#"EndHide"];
BOOL boolValue;
if ([aProperties valueForKey:#"EndHide"])
boolValue = [[aProperties valueForKey:#"EndHide"] boolValue];
} else if (_EndHide == NO) {
NSDictionary *aProperties = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO]forKey:#"EndHide"];
BOOL boolValue;
if ([aProperties valueForKey:#"EndHide"])
boolValue = [[aProperties valueForKey:#"EndHide"] boolValue];
}
What happens is I am making an iOS app game for iPhone and when you dodged these well objects for period of time, you end the game and unlock a button on the start menu named endless, after the story.
Now I need this button to be constantly unlocked. I managed to unlock it through a scene. It's all working but it just won't stay unlocked. Does anyone have any advice to help me with this?
Here is the endless button configuration and bool configuration:
#property(nonatomic, getter=isActive) bool EndHide;
#property(nonatomic) IBOutlet UIButton *endless;
That's all the code for the buttons and bools anyway to keep it unlocked.
I know it's simple but it won't work. Maybe a deep bug I tried it on another more updated version of Xcode but still to no avail. The issue is persistent and a real problem. I even tried switch saving that didnt work as well.
I cannot actually see any code that tries to save anything to the user defaults. You first need to get the user defaults object
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
To write a bool value:
[defaults setBool:YES forKey:#"EndHide"];
To read a bool value:
BOOL boolValue = [defaults boolForKey:#"EndHide"];
Fix any mistakes that I may have made yourself.
PS. Do NOT use valueForKey unless you really understand what it does. Use objectForKey or array syntax.
After days of searching, I found it. What happens is, instead of keep the bool changed aka always YES i just saved another bool with this:
in view did load top part:
if (_EndHide == YES) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"boolSet"];
} else if (_EndHide == NO) { }
so when the bool is yes you finished the game than this code:
in view did load top:
bottom:
if (![[NSUserDefaults standardUserDefaults] boolForKey:#"boolSet"]) {
NSLog(#"not workin -.-");
} else {
NSLog(#"finallyyyy workin !! YAeh");
_endless.hidden = NO;
}
code simply always remembers :D thank you everyone for trying to help me this method was best, endless is a button, create another bool that save in memory use it to unlock the button.
try this in the view did load
NSUserDefaults* prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:#"0" forKey:#"test"];
NSString * testValue = [prefs ObjectforKey:#"test"];
NSLog(testValue);
can you please tell me what is the console printing out?
I am using AFNetworking 2.0 and Mantle in order to connect to an API and return a user account.
My plan is to call the function that gets the user data in the application:didFinishLaunchingWithOptions: method. I will then encode the data and save the user into NSUserDefaults
Is this the best way to approach this task? What alternatives are there? (I'd like to stay away from creating singletons)
UPDATE
Some code to maybe help show what I am thinking in my head:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *encodedUserData = [defaults objectForKey:#"currentUser"];
if (encodedUserData) {
self.currentUser = [NSKeyedUnarchiver unarchiveObjectWithData:encodedUserData];
} else {
NSLog(#"No current user");
// Show login ViewController
}
If you are going to get the user account every time user launches the app, then you don't need to store it in NSUserDefaults. Just use a singleton or static object to store it as your model object type.
static Account *_userAccount = nil;
+ (Account*)userAccount {
return _userAccount;
}
+ (void)setUserAccount:(Account*)account {
_userAccount = account;
}
You can use NSUserDefaults for this purpose and can access it through out application.
// to save data in userdefaults
[[NSUserDefaults standardUserDefaults] setObject:#"your object" forKey:#"your key"];
[[NSUserDefaults standardUserDefaults] synchronize];
// for getting saved data from User defaults
NSData *encodedUserData = [[NSUserDefaults standardUserDefaults] objectForKey:#"your key"];
if (encodedUserData) {
self.currentUser = [NSKeyedUnarchiver unarchiveObjectWithData:encodedUserData];
} else {
NSLog(#"No current user");
// Show login ViewController
}
I have one question near the end.
I am working from the belief/experience that seeding iCloud more than once is a bad idea and that if a user can do the wrong thing, he probably will sooner or later.
What I want to do:
A. When the user changes the app preference "Enable iCloud" from NO to YES, display AlertView asking (Yes or No) if the user wishes to seed the cloud with existing non-iCloud Data.
B. Ensure that the app seeds iCloud only once on an iCloud account, refraining to put up the AlertView once seeding is completed the first time.
My Method:
Following Apple's Docs concerning the proper use of NSUbiquitousKeyValueStore, I am using the following method in, - (void)application: dFLWOptions:
- (void)updateKVStoreItems:(NSNotification*)notification {
// Get the list of keys that changed.
NSDictionary* userInfo = [notification userInfo];
NSNumber* reasonForChange = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
NSInteger reason = -1;
// If a reason could not be determined, do not update anything.
if (!reasonForChange)
return;
// Update only for changes from the server.
reason = [reasonForChange integerValue];
if ((reason == NSUbiquitousKeyValueStoreServerChange) ||
(reason == NSUbiquitousKeyValueStoreInitialSyncChange)) { // 0 || 1
// If something is changing externally, get the changes
// and update the corresponding keys locally.
NSArray* changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
// This loop assumes you are using the same key names in both
// the user defaults database and the iCloud key-value store
for (NSString* key in changedKeys) {//Only one key: #"iCloudSeeded" a BOOL
BOOL bValue = [store boolForKey:key];
id value = [store objectForKey:#"iCloudSeeded"];
[userDefaults setObject:value forKey:key];
}
}
}
Include the following code near the top of application: dFLWO:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateKVStoreItems:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:store]; // add appDelegate as observer
After loading iCloud Store, then seed it with non-iCloud data ONLY if seeding has never been done
- (BOOL)loadiCloudStore {
if (_iCloudStore) {return YES;} // Don’t load iCloud store if it’s already loaded
NSDictionary *options =
#{
NSMigratePersistentStoresAutomaticallyOption:#YES
,NSInferMappingModelAutomaticallyOption:#YES
,NSPersistentStoreUbiquitousContentNameKey:#"MainStore"
};
NSError *error=nil;
_iCloudStore = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:[self iCloudStoreURL] options:options error:&error];
if (_iCloudStore) {
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
BOOL iCloudSeeded =
[store boolForKey:#"iCloudSeeded"];//If the key was not found, this method returns NO.
if(!iCloudSeeded) // CONTROL IS HERE
[self confirmMergeWithiCloud]; // Accept one USER confirmation for seeding in AlertView ONCE world wide
return YES; // iCloud store loaded.
}
NSLog(#"** FAILED to configure the iCloud Store : %# **", error);
return NO;
}
Once the seeding is completed do the following to prevent any repeat seeding:
if (alertView == self.seedAlertView) {
if (buttonIndex == alertView.firstOtherButtonIndex) {
[self seediCloud];
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[store setBool:YES forKey:#"iCloudSeeded"]; // NEVER AGAIN
//[store synchronize];
}
}
}
Be sure to get a total iCloud reset before the above process using:
[NSPersistentStoreCoordinator
removeUbiquitousContentAndPersistentStoreAtURL:[_iCloudStore URL]
options:options
error:&error])
This is a very tidy solution to my problem, IMHO, but I can not quite get it done.
MY QUESTION:
How do I respond to the first notification to updateKVStoreItems: above? It is a notification with bad info. I says the value is TRUE, but I have never set it to TRUE. How do I set default values for a key in NSUbiquitousKeyValueStore?
I find that the first notification is of reason : NSUbiquitousKeyValueStoreInitialSyncChange
When that note comes in, bValue is YES. THIS IS MY PROBLEM. It is as if, iCloud/iOS assumes any new BOOL to be TRUE.
I need this value to be NO initially so that I can go ahead and follow the Apple Docs and set
the NSUserDefault to NO. And then Later when the seeding is done, to finally set the value: YES for the key:#"iCloudSeeded"
I find I can not penetrate the meaning of the following from Apple:
NSUbiquitousKeyValueStoreInitialSyncChange
Your attempt to write to key-value storage was discarded because an initial download from iCloud has not yet happened.
That is, before you can first write key-value data, the system must ensure that your app’s local, on-disk cache matches the truth in iCloud.
Initial downloads happen the first time a device is connected to an iCloud account, and when a user switches their primary iCloud account.
I don't quite understand the implications of number 2 below, which I found online:
NSUbiquitousKeyValueStoreInitialSyncChange – slightly more complicated, only happens under these circumstances:
1. You start the app and call synchronize
2. Before iOS has chance to pull down the latest values from iCloud you make some changes.
3. iOS gets the changes from iCloud.
If this problem was with NSUserDefaults and not NSUbiquitousKeyValueStore, I believe I would need to go to registerDefaults.
I am almost there,
How do I do this please!
Thanks for reading, Mark
The code was looking for both
A. NSUbiquitousKeyValueStoreInitialSyncChange and
B. NSUbiquitousKeyValueStoreServerChange
I was unable to figure out what to do with the notifications. I know see that I did not need to do anything with either. My app only needs to read and write, in order to solve the problem I laid out in my question header.
The app gets the current value with:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
BOOL iCloudSeeded = [store boolForKey:#"iCloudSeeded"];
The app sets the value in the NSUbiquitousKeyValueStore with:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[store setBool:YES forKey:#"iCloudSeeded"];
I believe I am correct in saying the following: Writing is done into memory. Very soon thereafter the data is put by the system onto disk.
From there it is taken and put into iCloud and is made available to the other devices running the same app on the same iCloud account. In the application I have described, no observer needs to be added, and
nothing else needs to be done. This is maybe an "unusual" use of NSUbiquitousKeyValueStore.
If you came here looking for a an more "usual" use, say when a user type something into a textview and it later
appears on a view of other devices running the same app, check out a simple demo I came across at :
https://github.com/cgreening/CMGCloudSyncTest
The better functioning (monitoring only) notification handler follows:
- (void)updateKVStoreItems:(NSNotification*)notification {
NSNumber *reason = notification.userInfo[NSUbiquitousKeyValueStoreChangeReasonKey];
if(!reason) return;
// get the reason code
NSInteger reasonCode = [notification.userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] intValue];
BOOL bValue;
NSUbiquitousKeyValueStore *store;
switch(reasonCode) {
case NSUbiquitousKeyValueStoreServerChange:{ // code 0, monitoring only
store = [NSUbiquitousKeyValueStore defaultStore];
bValue = [store boolForKey:#"iCloudSeeded"];
id value = [store objectForKey:#"iCloudSeeded"];
DLog(#"New value for iCloudSeeded=%d\nNo Action need be take.",bValue);
// For monitoring set in UserDefaults
[[NSUserDefaults standardUserDefaults] setObject:value forKey:#"iCloudSeeded"];
break;
}
case NSUbiquitousKeyValueStoreAccountChange: {// ignore, log
NSLog(#"NSUbiquitousKeyValueStoreAccountChange");
break;
}
case NSUbiquitousKeyValueStoreInitialSyncChange:{ // ignore, log
NSLog(#"NSUbiquitousKeyValueStoreInitialSyncChange");
break;
}
case NSUbiquitousKeyValueStoreQuotaViolationChange:{ // ignore, log
NSLog(#"Run out of space!");
break;
}
}
}
Adding 9/3/14
So sorry but I continued to have trouble using a BOOL, I switched to an NSString and now
all is well.
METHOD TO ENSURE THAT THE "MERGE" BUTTON FOR SEEDING ICOUD IS USED AT MOST ONCE DURING APP LIFETIME
Use NSString and not BOOL in KV_STORE. No need to add observer, except for learning
In Constants.h :
#define SEEDED_ICLOUD_MSG #"Have Seeded iCloud"
#define ICLOUD_SEEDED_KEY #"iCloudSeeded"
Before calling function to seed iCloud with non-iCloud data:
NSUbiquitousKeyValueStore* kvStore = [NSUbiquitousKeyValueStore defaultStore];
NSString* strMergeDataWithiCloudDone =
[kvStore stringForKey:ICLOUD_SEEDED_KEY];
NSComparisonResult *result = [strMergeDataWithiCloudDone compare:SEEDED_ICLOUD_MSG];
if(result != NSOrderedSame)
//put up UIAlert asking user if seeding is desired.
If user chooses YES : set Value for Key after the merge is done.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (alertView == self.seedAlertView) {
if (buttonIndex == alertView.firstOtherButtonIndex) {
[self seediCloudwithNoniCloudData];
NSUbiquitousKeyValueStore* kvStoretore = [NSUbiquitousKeyValueStore defaultStore];
[store setObject:SEEDED_ICLOUD_MSG forKey:ICLOUD_SEEDED_KEY];
}
}
}
Thereafter on all devices, for all time, the code
NSUbiquitousKeyValueStore* kvStoretore = [NSUbiquitousKeyValueStore defaultStore];
NSString* msg =
[kvStore stringForKey:ICLOUD_SEEDED_KEY];
produces: msg == SEEDED_ICLOUD_MESSAGE
Is it possible to save and load data on Today Extension using NSUserDefaults?
After closing the Notification Center, the widget behaves like an app which is terminated, so any data results lost. How could I solve this issue?
This is my code:
NSUserDefaults *defaults;
- (void)viewDidLoad {
[super viewDidLoad];
defaults = [NSUserDefaults standardUserDefaults];
NSArray *loadStrings = [defaults stringArrayForKey:#"savedStrings"];
if ([loadStrings objectAtIndex:0] != nil) {
[display setText:[NSString stringWithFormat:#"%#", [loadStrings objectAtIndex:0]]];
}
if ([loadStrings objectAtIndex:1] != nil) {
calculatorMemory = [NSString stringWithFormat:#"%#", [loadStrings objectAtIndex:1]].doubleValue;
}
}
- (IBAction)saveData:(id)sender {
NSString *displayString;
NSString *memoryString;
NSArray *saveStrings = [[NSArray alloc] initWithObjects: displayString, memoryString, nil];
defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:saveStrings forKey:#"savedStrings"];
[defaults synchronize];
}
You need to use app group identifier instead of com.*
For instance:
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:#"group.company.appgroup"];
Don't forget to synchronise when you store data
[shared synchronize];
You need to add the App Group stuff detailed under here and then if it actually worked (pretty iffy under beta) it should allow you to share NSUserDefault data like normal between the host and widget.
Edit: Normal NSUserDefaults does not work. Apple has implemented a new method. To use, simply redefine your NSUserDefaults instance like this:
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:#"com.you.app.container"];
For anyone wondering how in the world do you save and get values then look at this code.
In your regular app add this to save whatever you like in your *.m file.
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:#"group.yourcompanyname.TodayExtensionSharingDefaults"];
//save dic
[shared setObject:dictionary2 forKey:#"dicForTodayWidget"];
//save array
[shared setObject:tempArray2 forKey:#"arrayForTodayWidget"];
//save some value
[shared setObject:#"1234" forKey:#"myValForTodayWidget"];
[shared synchronize];
In your today widget under TodayViewController.m in viewDidLoad add this.
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:#"group.yourcompanyname.TodayExtensionSharingDefaults"];
//get dic
NSMutableDictionary *dictionary = [shared objectForKey:#"dicForTodayWidget"];
You first need the App Groups set up for both targets (application and the extension).
Then, use the
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:#"group.company.myapp"];
to obtain the defaults object which you can read from/write to as usual.
If you want to be notified of changes to the defaults, use the NSUserDefaultsDidChangeNotification in your widget (or app).
For a step-by-step tutorial explaining all this, take a look at this blog post.
#edukulele
Today Extension and Main app run on two processes. Today Extension can't receive NSUserDefaultsDidChangeNotifications. I tried use MMWormhole. It is very good.
I want to display a help message on a view controller when the app is installed and opened for the very first time ONLY.
Is there a method I can use to do this?
You can display the help message once, and then store a boolean value in NSUserDefaults to indicate that it should not be shown again:
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
BOOL appHasBeenLaunchedBefore = [userDefaults boolForKey:#"HasBeenLaunched"];
if (!appHasBeenLaunchedBefore)
{
[self showHelpMessage];
}
[userDefaults setBool:YES forKey:"HasBeenLaunched"];
Use Grand Central Dispatch's dispatch_once() and check some persistent storage.
static dispatch_once_t pred;
dispatch_once(&pred,^{
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
BOOL hasLaunched = [userDefaults boolForKey:kAppHasLaunched];
if (!hasLaunched) {
[self showFirstLaunchMessage];
[userDefaults setBool:YES forKey:kAppHasLaunched];
}
});
This is the easiest way to ensure code only gets run once in your app per launch (in this case the block will only be executed once). More information about this is in the Grand Central Dispatch Reference. In this block you simply need to check some persistent storage, such as your preferences, to see if your app has ever launched before and display a message as appropriate.
Use a key in the user defaults. For example:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
BOOL launchedBefore = [userDefaults boolForKey:#"hasRunBefore"];
if(!hasRunBefore)
{
NSLog(#"this is my first run");
[userDefaults setBool:YES forKey:#"hasRunBefore"];
}
The user defaults are backed up by iTunes, so it'll normally be the user's first launch rather than the first launch per device.
EDIT: this is explicitly the same answer as e.James gave before me. The 'other answers have been posted' bar failed to appear. I'll leave it up for the example code but don't deserve credit fir a first answer.