How to migrate NSUserDefaults in new release? - iphone

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.

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

Core Data - Changing attributes type

I have a project and I am using core data.
I have some entities, with attributes. When I started the project I choose some atributes and now I want to change their types (Int to String for example). ANd so I did it.
THe thing is, I am getting errors...
I checked the code, I think every thing is ok.
I even deleted the entire entity and made a new one with some name but it doesn't work.
How can I change it with success?
You can use code data migration for this by creating new version..
To change data types you need to create a new version of the database, you can not just simply modifie it because that way your users would have to delete and redownload your app every time you change something.
Here you can read how to do this.
The simulator or device you are running the app on still 'remembers' the old type and data. Simply hold down on the app and press the 'X' to delete it. When you press play in XCode it will reinstall the app with the new data types.

UIPasteboard is stubbornly persistent, no matter what I do

I am creating a application UIPasteboard with my app's identifier as a name (e.g. com.example.app.pboard) as suggested in the docs.
All the copying and pasting works, but the damn thing never goes away. I set its persistent property explicitly to NO every time I access it to copy something onto it, and I even call UIPasteboard's +removePasteboardWithName: every time my app starts.
But every time I look at it, the most recent thing I copied onto it is always there, despite app restarts.
What gives?
No idea why what you are trying isn't working, here are two guesses and a possible solution:
Removing the paste board may be expected to be executed on exit from the app, so it doesn't execute until the app is terminated or backgrounded?
Calling remove and then asking for the items may be recreating it again in the same "place" so the items still remain.
You could perhaps solve this by setting the pasteboard's items to nil when leaving or entering the app.
You can set UIPasteboard to persistent. Accord to Apple's document,
setPersistent:
A Boolean value that indicates whether the pasteboard is persistent.
When a pasteboard is persistent, it continues to exist past app terminations and across system reboots. App pasteboards that are not persistent only last until the owning (creating) app quits. The system wide general pasteboard is persistent. Named, app-specific pasteboards are not persistent.
Update: For iOS 10, UIPasteboard set persistence automatically.
Note
Starting in iOS 10, the system sets pasteboard persistence automatically. If you try to set the setPersistent: property on a pasteboard, Xcode issues a deprecation warning.
Instead of named persistent pasteboards, use shared containers, as described in the Overview section of this document.

make the language of my IPHONE app change from the app settings without changing the language of the device

I want to make the language changeable from the application settings. For that I made a settings.bundle and i forced the langauge using this code How to force NSLocalizedString to use a specific language , I also put the UIApplicationExitsOnSuspend to YES to make my app reload every time it goes to the background.
It partially worked for me but I have 2 problems :
- The splash screen (which is localized) is not changing with this method.
- I must enter/goBack twice to make the language change effective on the application Views an this only on the OS 4 and higher.
Has anyone a solution for that?
You need to create you own translation framework for that ! Sorry !
Unfortunately there's no built-in way. What would be probably easiest would be integrate service like GetLocalization.com, so that your app would download language file via API (then it's easy to add new languages and translations even app is already released). Then what you need is just simple loader that loads that language file to memory and function that replaces the original string with translated one in run-time. You can calculate hash for original string so it's fast to search them, good algorithm for this is Peter Weinberger's hashPJW.
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

NSUserDefaults flexibility for version updates

I am using the NSUserDefaults' api -registerDefaults api to register the factory defaults of my application. All is fine for the first version of the application. But when I provide an update for the iPhone application, I have 3 criteria to make sure:
The user preferences of older version app should be intact.
Addition to the factory defaults specific to the new version should be applied.
Providing flexibility in the design to the future version updates so that the user defaults modifies itself based on the new version.
The api -registerDefaults does not register factory defaults in new version updates because a plist file containing user preferences already exists in the /Library/Preferences of sandbox. And in case if we reset the preferences with new factory defaults, user preferences of previous version will be lost.
I think there should be a design pattern to handle such cases, so in general how do we cater with such situations? Doesnt Apple provide any inherent support for this? Or am I missing some links, I tried googling around without any success.
Thanks,
Raj
If this list of items you're talking about is an array registered under a single key, then yes, a larger array passed to -registerDefaults: will be ignored in favor of the smaller array stored in the persistent plist. NSUserDefaults tracks stuff on a per-key basis, and doesn't do any interpretation of the contents you store in there, so it's not going to try to automagically merge previous array values with new array values, or anything daft like that.
If you want new items to be added to this array by default when the user upgrades, I would recommend adding the items explicitly to the key yourself by fetching the existing array, adding the new items, and then saving the larger array back to the user defaults again. You can use a separate boolean flag to indicate that the upgrade has been done, to ensure that the addition doesn't get performed multiple times.
What is the problem? If the user never changes (or more exactly: sets) a preference, the factory default will be used. If an update of your app changes the factory defaults, those will be used instead. If the user has set his own preference, that will continue to be used.
A new factory default doesn't (and shouldn't) overwrite an user set preference. NSUserDefaults does exactly what you seem to be after.
I agree with Johan Kool. User defaults work as they should. If there's a preference that you want changed (for whatever reason) in an updated app version, just slightly rename the preference. For example, if someValue was a preference in version 1, then in version 2 name it someValue_v2 and your new factory default will take effect.