I want to run a piece of code -- for data cleanup -- only once after the user upgrades the app or installs the app from scratch.
Putting the piece of code in didFinishLaunchingWithOptions method wasn't helpful since -- it seems -- after the app is opened after the upgrade (without hard-killing it before the upgrade) the didFinishLaunchingWithOptions event wasn't triggered.
I can put the piece of code in the applicationWillEnterForeground event handler and have it conditioned on a dataCleanupDone flag being FALSE. Is there a neater solution?
After the clean up set a flag (kCleanUpCompletedKey) in the user preferences. Check if this flag is set when the app becomes active.
Example:
- (void)applicationDidBecomeActive:(UIApplication *)application {
if ([self shouldDoCleanUp]){
// do your clean up
// after successful clean up:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setBool:YES forKey:kCleanUpCompletedKey];
}
}
- (BOOL)shouldDoCleanUp {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if ([prefs objectForKey:kCleanUpCompletedKey]){
return YES;
}
return NO;
}
You need to add appversion in your new App version in preference and then
call method onUpgradingApp if prefrence value is 0 then set it to 1 and run this code
integerValue CurrentAppVersion = 2; // suppose
Inside Appdelegate :
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSInteger oldVersion = [[defaults objectForKey:#"appVersion"] integerValue];
if (oldVersion == 0) // if old app is running
{
oldVersion = 1;
}
if (oldVersion < CurrentAppVersion)
{
[self onUpgradingApp:oldVersion newVersion: CurrentAppVersion];
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithInteger:CurrentAppVersion] forKey:#"appVersion"]; //save new version in preference
}
-(void)onUpgradingApp:(NSInteger)oldVersion newVersion:(NSInteger)newVersion
{
NSString *query;
switch (oldVersion)
{
case 1:
{
// clean data here
}
break;
default:
break;
}
}
SWIFT VERSION
you can use same logic.
1- Define your app version in the info.plist file
example: Bundle versions string, short: 0.1.1
And then you should increase that variable every time you have a new version.
2- in your controller create a variable app_version and get the right version from that info.plist file.
example: let version = dict["CFBundleShortVersionString"] as? String
3- Use the !UserDefaults.standards.bool to check if the app version changes, if true run your code and then set it to true
example:
if (!UserDefaults.standard.bool(forKey: appVersion)) {
/* Your code */
UserDefaults.standard.set(true, forKey: appVersion)
UserDefaults.standard.synchronize();
}
Hope this helps.
Related
I have a UISwitch which is used to mute the sound effects with.
When switch is set ON the sound effects are mute.
The state is saved using NSUserDefaults. It works great. But when the app is started from the very first time after installing it, and you got to the settings and the switch is set to OFF but there is no sound. But if i press the switch on then off again the sound effect works.
How do i do to set switch default value to OFF.
I have seen similar exempels but i don't get it to work.
In the first ViewController i have this code:
- (void)viewDidLoad {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if ([[NSUserDefaults standardUserDefaults]objectForKey:#"switchKeyLjud"]==nil)
{
[[standardDefaults stringForKey:#"switchKeyLjud"] isEqual: #"off"];
}
if ([[standardDefaults stringForKey:#"switchKeyLjud"] isEqual: #"off"]) {
ImageViewLjud.hidden = YES;
}
}
and in the settings ViewController:
- (void)viewDidLoad {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
self->switchOutletLjud.on = ([[standardDefaults stringForKey:#"switchKeyLjud"]
isEqualToString:#"On"]) ? (YES) : (NO);
if ([[standardDefaults stringForKey:#"switchKeyLjud"] isEqual: #"On"]) {
[ImageViewLjud setImage:[UIImage imageNamed:#"ljudOFF.png"]];
}
else {
[ImageViewLjud setImage:[UIImage imageNamed:#"ljudON.png"]];
}
}
and this is the switch
- (IBAction)switchLjudChanged:(UISwitch *)sender {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if (sender.on == 0) {
[standardDefaults setObject:#"Off" forKey:#"switchKeyLjud"];
} else if (sender.on == 1) {
[standardDefaults setObject:#"On" forKey:#"switchKeyLjud"];
}
if ([[standardDefaults stringForKey:#"switchKeyLjud"] isEqual: #"On"]) {
[ImageViewLjud setImage:[UIImage imageNamed:#"ljudOFF.png"]];
}
else {
[ImageViewLjud setImage:[UIImage imageNamed:#"ljudON.png"]];
}
[standardDefaults synchronize];
}
Thanks for any help!!
It looks like a very simple error in your sample code:
- (void)viewDidLoad {
NSUserDefaults *standardDefaults = [NSUserDefaults standardUserDefaults];
if ([[NSUserDefaults standardUserDefaults]objectForKey:#"switchKeyLjud"]==nil)
{
[[standardDefaults stringForKey:#"switchKeyLjud"] isEqual: #"off"];
}
The logic in this case doesn't actually set the "off" key, you're only evaluating it. First you check if it's nil. If it's nil, then you check if it is equal to "off". If it's nil, it can't also be equal to "off". I think what you want to be doing, in the event that it is initially nil, is then setting it to off:
[standardDefaults setString:#"off" forKey:#"switchKeyLjud"];
Looks like a simple typo.
Additionally, consider setting the BOOL values directly in your user defaults, rather then relying on strings and then translating those back and forth to BOOL values in code. Unless you have a specific need to store them as strings in NSUserDefaults, avoid the additional point of failure.
In my app, I create an EKCalendar. Since the calendar could be deleted by the user outside my app, I need to check if that calendar still exists, like so:
-(BOOL)checkForCalendar {
NSArray *calendarArray = [self.store calendarsForEntityType:EKEntityTypeEvent];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *calNameToCheckFor = [defaults objectForKey:#"calendarName"];
EKCalendar *cal;
for (int x = 0; x < [calendarArray count]; x++) {
cal = [calendarArray objectAtIndex:x];
NSString *calTitle = [cal title];
// if the calendar is found, return YES
if ([calTitle isEqualToString:calNameToCheckFor]) {
return YES;
}
}
return NO;
}
I want my UI to update, if a calendar indeed is deleted, as such:
-(void)initCalendarState {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (![self checkForCalendar]){
[self.LoginSwitch setOn:NO animated:NO];
[userDefaults setObject:#"0" forKey:#"SwitchedOn"];
}
}
I have put this method in the Application's Delegate
- (void)applicationDidBecomeActive:(UIApplication *)application
Things I observe:
1) If I go through this code step by step in the debugger, it is called on the correct moment, and is executed as expected: running in the main thread, honoring all conditions etc.
2) However: when it gets to updating the UI, ([self.LoginSwitch setOn:NO animated:NO];), nothing happens.
3) When I rerun the project (effectively force quitting it, and restarting) the UI actually IS updated.
What am I missing?
The concrete question is: why isn't my UI updated while the app is running?
Thanks ahead
I think what is happening, is that the changes I made in my local calendar were not fully proliferated through the iCloud sync system. Or something like it. Not sure.
However: to solve my problem, I added
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(storeChanged:)
name:EKEventStoreChangedNotification
object:self.store];
to my app.
and
-(void)storeChanged:(NSNotification *) notification {
[self initCalendarState];
}
This works flawlessly.
I am using the below code to save an int and some other stuff into NSUserDefaults from my AppDelegate. The other array that I'm saving is full of objects that conform to NSCoding, so I don't think that that's an issue. If the current place is not zero, then that means that there is a session in progress, so all the data is loaded. When saving my data, it only saves if the current place is not zero which indicates that there is a session in progress. I know this calls when I exit the app on my physical device because the NSLog message appears in the debugger.
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
if (self.currentPlace != 0) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *arr = self.places; // set value
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];
[defaults setObject:data forKey:#"places"];
[defaults setInteger:self.currentPlace forKey:#"current"];
NSLog(#"Saving and Quitting b/c we have a place verified!");
[defaults synchronize];
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSInteger myInt = [defaults integerForKey:#"current"];
self.currentPlace = (int)myInt;
if (self.currentPlace != 0) {
NSData *data = [defaults objectForKey:#"places"];
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
self.places = [arr mutableCopy];
NSLog(#"Current Place: %i",self.currentPlace);
}
}
I am using my AppDelegate to store data that can be accessed from multiple screens in my app. In the first ViewController, the user is presented with a menu. If the appDelegate's currnentPlace value is not 0, then the option to continue with the loaded data from the AppDelegate is presented. However, this option is never presented. I check the currentPlace int's value using the following in my first view controller:
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
// Do any additional setup after loading the view, typically from a nib.
[self setupViews];
self.spinner.alpha = 0;
NSLog(#"Current %i",delegate.currentPlace);
if (delegate.currentPlace == 0) {
self.continueButton.enabled = false;
self.continueButton.alpha = 0.5;
}
else if (delegate.currentPlace != 0) {
self.continueButton.enabled = true;
self.continueButton.alpha = 1;
}
If anyone could see what I'm doing wrong it would be greatly appreciated.
Move your data restoration code block into a method, as below:
- (void)restoreData {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSInteger myInt = [defaults integerForKey:#"current"];
self.currentPlace = (int)myInt;
if (self.currentPlace != 0) {
NSData *data = [defaults objectForKey:#"places"];
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
self.places = [arr mutableCopy];
NSLog(#"Current Place: %i",self.currentPlace);
}
}
Then call this method in application:didFinishLaunchingWithOptions: and applicationWillEnterForeground: methods in AppDelegate.
You need to use
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
in you controller in which you are making the object of AppDelegate class.
If still you are not able to get current value then just store the value in user default as a string and when you acess it, convert it in appropriate data type which you want. Might be this could help you.
The app's current behavior is, the user logged in once will not be logged out unless the user explicitly clicks on the logout.
I keep the user logged in, even if the user closes the app and opens it again.
When newer version of my app is released in appstore, I want to find out whether the user updated my app and opened it for the first time.
At that point I want to make them login again.
Is there a way to find out at the first time launch of the app after its been updated to latest version?
Create some kind of version #'s scheme. Note: You can enable Xcode to create backups and versions whenever you make substantial changes to the code.
There are a number of ways one could create a version constant, save it, and read it back.
When you update an app from the store, there is app data that persists from the previous installed version of the app, which you can read back to determine the version and, then update that persistent data to be ready for the next update cycle.
This answer was a very popular solution in another similar question.
Or, try something like #JitendraGandhi's ObjC answer below, or if you use Swift, try something like my port of #JitendraGandhi's ObjC example to Swift:
func hasAppBeenUpdatedSinceLastRun() -> Bool {
var bundleInfo = Bundle.main.infoDictionary!
if let currentVersion = bundleInfo["CFBundleShortVersionString"] as? String {
let userDefaults = UserDefaults.standard
if userDefaults.string(forKey: "currentVersion") == (currentVersion) {
return false
}
userDefaults.set(currentVersion, forKey: "currentVersion")
userDefaults.synchronize()
return true
}
return false;
}
You can save your currentversion to NSUserDefaults and use this method to check your version every time the app awakes:
#pragma mark - NSBundle Strings
- (NSString *)currentVersion
{
return [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleShortVersionString"];
}
if the currentversion is different from stored... its time to show the login!
Hope it helps you.
Use NSUserDefaults to store the CFBundleVersion. Then check against it every time the application is launched.
// Check if new version
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *currentAppVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
if ([defaults objectForKey:#"savedAppVersionKey"] != nil) {
// Key exists
NSString *savedAppVersion = [defaults objectForKey:#"savedAppVersionKey"];
if ([currentAppVersion isEqualToString:savedAppVersion]) {
// Still running the same app version
// Do nothing
NSLog(#"App version: SAME");
}
else {
// The app version changed from the last launch
// Do something here
NSLog(#"App version: UPDATED");
}
}
// Set the key & synchronize
[defaults setObject:currentAppVersion forKey:#"savedAppVersionKey"];
If you want simple and easy solution, Use this function :
-(BOOL)isAppUpdated
{
NSDictionary *bundleInfo = [[NSBundle mainBundle] infoDictionary];
NSString *currentVersion = [bundleInfo objectForKey:#"CFBundleShortVersionString"];
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"currentVersion"] isEqualToString:currentVersion])
{
return NO ;
}
else
{
[[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:#"currentVersion"];
return YES ;
}
}
Following code will return NO / YES. You can call this method multiple times to know whether app was updated before this launch or not.
- (BOOL)launchedFirstTimeAfterUpdate
{
static NSString *lastVersion;
NSString *currentVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleShortVersionString"];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *versionKeyName = #"lastLaunchedVersion";
lastVersion = [[NSUserDefaults standardUserDefaults] stringForKey:versionKeyName];
[[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:versionKeyName];
[[NSUserDefaults standardUserDefaults] synchronize];
});
if (!lastVersion.length)
{
// No last version means, launched first time
return NO;
}
if ([lastVersion compare:currentVersion options:NSNumericSearch] == NSOrderedAscending)
{
// Last version is less than current version
return YES;
}
return NO;
}
I have created a Prefs Controller class, which dose what it says controlls my prefs values. I have two specific values in my prefs both strings one that's called installSelected and the other called finishSelected, I setting them as a string that's either T or F...
When the app first starts I create the new prefs and the values are set to F automatically as those are their default values in the plist bundle. Then later in the app I overwrite installSelected to T. When I restart the application it returns the correct value as T. Then I write over it again using F. Again I restart but when I read the values this time it still shows T when it should be F.
I have debugged this and I am just not sure why it's not saving the value.
This is what my prefController method looks like that is used to write the new values:
- (void) writeBool:(NSString *)name MyBool:(NSString *)myboolString {
prefs = [NSUserDefaults standardUserDefaults];
if ([name isEqualToString:#"InstallsSelected"]) {
[prefs setObject:myboolString forKey:name];
}
else if ([name isEqualToString:#"FinishSelected"]) {
[prefs setObject:myboolString forKey:name];
}
[prefs synchronize];
}
I call the above method like this
[prefsController writeBool:#"InstallsSelected" MyBool:#"F"];
It just makes no sense that it's not working as I am able to change it from F to T but not back if needed and none of the code is different. What might be causing this problem?
Why are you using strings instead of bool values?
You have to set your boolean by using:
// Notice setBool
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"pref"];
and call synchronize after it:
[[NSUserDefaults standardUserDefaults] synchronize];
To retrieve the value, you call:
// Notice boolForKey
if(![[NSUserDefaults standardUserDefaults] boolForKey:#"pref"]) {
// False
} else {
// True
}
To register default prefs, use:
NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], #"pref1",
[NSNumber numberWithBool:YES], #"pref2",
nil];
NSUserDefaults * prefs = [NSUserDefaults standardUserDefaults];
[prefs registerDefaults:appDefaults];
Are you on the simulator or a real device? I have had serious problems getting the sync right in the Simulator. It turns out that if you just do a rebuild from xCode, the user preferences are not saved. They are saved if you bring the app to the background in the Simulator (press cmd-shift-H).
Also it helps to NSLog as much as possible to see what is written in the prefs.