String value in Settings.bundle empty after update - iphone

For QA purposes I display our app's build version in the application' settings view as a PSTitleValueSpecifier. I set our app's current build number as the DefaultValue and I update it for every new build.
My problem is, whenever I deploy a new build of our application on a dev phone via Xcode, the version value doesn't update but remains with the previous build. The only way to update the version's value is to delete the app from the dev phone and then deploy the new build. Obviously, this approach does not please our QA department since they then loose all of their persisted data.
I have tried, cleaning the project, re-compiling and deploying but I get the same behavior.
Has anyone else experienced this issue? Any idea on how to work around it/fix it?

It is because this value is stored using NSUserDefaults which is stored on the disk and only removed when the app is removed. You can explicitly set it in code the same way you would any other user default. You could just create a field in your Info.plist that you change when you want to send a new build. Read that value from the plist on startup and then write it to your user defaults with:
NSString *value = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"build_version"];
[[NSUserDefaults standardDefaults] setObject:value forKey#"build_version"];
[[NSUserDefaults standardDefaults] synchronize];
Of course, you replace "build_version" with whatever your key name is in your settings file. It's a hack, but it might satisfy your QA people.
For argument sake, it is actually reasonable to expect that you have to completely remove the previous version before seeing the version number update, however I realize not all QA people are reasonable. ;-)

Related

Preventing a CoreData crash for upgrading users

I built an app about a year and a half ago that I'm coming back to. It was the project I cut my Swift teeth on and obviously a lot has changed since then, both in the language and my Swift abilities.
Yesterday for the first time, I updated my single CoreData model to add an optional string attribute. I did the file generation bit and made sure in the inspector column the new data model is appropriately selected.
On my Simulator and testing devices, I need to delete the old version of the app to install the new version or I get a crash. I assume that's just part of the development environment process. How can I ensure that upgrading users won't have to delete and reinstall when they do a simple update from the App Store? I assume that Xcode/CoreData/Apple have this handled with some internal scripts or processes that are invisible to the user, "it just works." But I wanted to post this here to understand if there's anything additional I need to do to ensure a smooth transition from v1 to v1.1 for the user.
All I did was an an optional string column, as I mentioned. I assume that all existing user data will be migrated over to the new schema with the new field being nil.
Any thoughts here would be very welcomed and appreciated. Thanks!
If your app is crashing in the Simulator when upgrading, your users will have crashes too.
To avoid this, you need to make sure you follow these steps:
Make sure you do NOT change the original version of your data model in any way.
In Xcode, select your xcdatamodel file, then from the menu choose Editor > Add Model Version...
Xcode will suggest a new version name, based on the current model. Make a mental note of the new version name, then click Finish.
Select the xcdatamodel file again, go to File inspector and under Model Version, select the new version name to make this your current version.
In Project Navigator, select the new version of the xcdatamodel. Add your attribute.
It's important to follow these steps in this order. If you add your attribute before creating the new model or making it your current version, you will have crashes.
EDIT: This will only work if you enable lightweight migrations. This is a code snippet of how to do this:
let options = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
do {
//coordinator is an NSPersistentStoreCoordinator
try coordinator!.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: options
} catch var error as NSError {
// handle error however you want here...
abort()
}
In addition to #Mike Taverne's answer, I think it a better if I post some screenshots to illustrate the option
for Xcode 12.3
Choose from the main menu Editor -> Add Model Version
To add mark the New Model as the current model with a green checkmark
Follow the below image

Change an App Setting when updating the iPhone application to a new Version

I want to change the URL that my application connects to, to a new sever. The problem is, the URL value is saved in a settings file. When I update the application to the new version, the old file is read from the device, and overwrites my settings. What I want is to use the new URL the first time the updated version is launched. After that, I am happy to read the URL from the file.
Is there any way I can determine this is the first time after an update when I lauch the application?
Thanks!
As another user, you can get the current version by reading the CFBundleVersion of your app's bundle. The problem with this approach is that a user might not install "version 1" of your app. Instead, I suggest putting something like the following in your app's didFinishLaunching method:
#define kSettings [NSUserDefaults standardUserDefaults];
if(![kSettings objectforKey:#"isFirstRun"]){
// You could check the version here
// and do some initial setting up.
[kSettings setBool:NO forKey:#"isFirstRun"];
}
Then, for each subsequent version, you can add another if block with another flag to check for that version, like so:
if(![kSettings objectForKey:#"isFirstRunForVersionX"]){
// Do some version specific set up here.
[kSettings setBool:NO forKey:#"isFirstRunForVersionX"];
}
I've successfully used this approach in several of my apps.
To determine it it's the first launch after the update, you could retrieve the version number with this piece of code:
NSString* v = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
And compare it to a previous value you have saved.
You can store flag in NSUSerDefault. So You need to check first time if isFirstTime==0 then do your code and make isFirstTime=1; So it only runs first time.

Build configuration for Lite/Full apps in Xcode

I'm developing a Lite version of an existing iPhone app and I'm trying to figure out the best way to conditionally compile the full and lite versions. Ideally I'd like to use a LITE preprocessor define but is there a good way to set this so I can choose a different item from the scheme menu and just compile whichever version I want?
I don't want to duplicate my "full" target because I'm concerned I'll change a setting in one version and not copy it to the other one. I looked at using an aggregate target but it doesn't run when I press Command-R.
What's the best way to achieve this without duplication?
Sounds like your using xcode4? You could do this by defining a new configuration (beyond the standard ones of Debug & Release).
Then, add your own preprocessor macro for the additional configuration.
Finally, add a new scheme to select the new configuration.
In the end I decided it was best to resolve this without using build configurations. My app uses in-app-purchase to support upgrading to the full, non-lite, version. As such, I want to have every feature compiled and ready to go.
I now manually update the bundle identifier if I want to switch between full and lite versions, and check this when the app runs. I have to change the bundle identifier anyway when I submit either version, so this way I change one file and there's no duplication. Within the app I call the method below to enable/disable lite features.
+ (BOOL)isLiteVersion {
static BOOL haveCheckedLiteStatus = NO;
if (haveCheckedLiteStatus == NO) {
BOOL isLiteApp = [[[NSBundle mainBundle] bundleIdentifier] rangeOfString:#"lite"].location != NSNotFound;
isLite = isLiteApp && [[NSUserDefaults standardUserDefaults] boolForKey:kUpgraded] == NO;
haveCheckedLiteStatus = YES;
}
return isLite;
}

How to migrate NSUserDefaults in new release?

I have an iPhone app that stores some settings using NSUserDefault standardUserDefaults.
When I add new features to the app I need to add new default settings and so I have to migrate/upgrade the NSUserDefaults. For now, I just store the version number and check this when the app is launched. It has quickly become very messy as I have to add lots of if statements. I cannot assume that the user is just upgrading from the previous version but perhaps even a couple of versions before.
I like the way that CoreData seems to handle migrating table changes but I want to provide 2.2.1 SDK compatibility and of course CoreData is not the same thing as NSUserDefaults.
Any suggestions or best practices?
Hmm… I wouldn't "upgrade" the user defaults in this way, but instead query NSUserDefaults as usual, only if the default isn't set (objectForKey will return nil in that case), use a default value (which may then be written back to the user defaults to save time the next time). Since you'll have to do this every time a "new" default (i.e. one that didn't exist in 1.0) is read, I advise doing a wrapper class/file that does this, this way the code is written only once for each "new" default.
Also, while the question/problem is different, this answer works just as well in your case.

Creating a localized iPhone app but allowing the user to change language for the application

I'm working on a localized app and everything is working fine. The problem is I want to allow the user to specifically select the lenguage for the specific app, in the app settings folder. This should users that their phone is set to one language (e.g. french) to set the app to work in English.
I'm currently using NSLocalizedString to get localized string but looking through all variation of the macro I can't find one that will let me specify the language.
Any ideas on how to do it?
There are three issues here:
Strings
Other resources (including NIBs)
System messages
The last is almost certainly not fixable, so we will leave it be. They're going to show up in the device language.
The other two are solvable, but you will need to do more things by hand. For strings, instead of creating a single Localizable.strings and then localizing it, create completely separate tables (English.strings, French.strings, etc.) Then, use NSLocalizedStringFromTable(), passing the language as the table.
For NIBs, there are two approaches. You can put each set of localized NIBs into its own Bundle and then pass that Bundle rather than nil to -initWithNibName:bundle:. Alternately, you can hand-load the NIBs after finding them with [NSBundle -pathForResource:ofType:inDirectory:forLocalization:].
There is a better way to do this. You can force the language like so:
[[NSUserDefaults standardUserDefaults] setObject: [NSArray arrayWithObjects:#"en", nil] forKey:#"AppleLanguages"];
And undo this setting by:
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"AppleLanguages"];
NB. you will normally have to restart the app for this to take affect.
Consider if you need to call [[NSUserDefaults standardUserDefaults] synchronize];
I agree there is little need to allow the user to specify a language. However the one exception is being able to override the language and set it to the developer's native language. If the user can speak the developer's language (e.g. English for me) then they may wish to use the App in that language, if the translations are incorrect.
I reference this answer: How to force NSLocalizedString to use a specific language (the answer doesn't actually work for me, but following the ideas in the comments did. The undo stuff I worked out.
The trick to use specific language by selecting it from the app is to force the NSLocalizedString to use specific bundle depending on the selected language ,
here is the post i have written for this http://learning-ios.blogspot.com/2011/04/advance-localization-in-ios-apps.html
and here is the code of one sample app https://github.com/object2dot0/Advance-Localization-in-ios-apps
The correct "User experience" is for the user to select their language via the system preference panel; not your app (or your app's settings panel, etc.). There is no way to override this per-app and we wouldn't want any app changing the system wide setting.