Saving User's Coin Count - cocoa

While my app is not a game, a coin-based game works as a good analogy.
In a game like this, the user has a certain number of coins that they can earn through gameplay or by purchasing an IAP. The main problem is securely storing how many coins the user has locally.
I know there are other questions like this, such as this. This answer basically says, there is no use in trying to save data securely; just deal with the fact that users can edit the data.
I would rather not just leave the data out in the open for users to change (if they can edit the data, why would they purchase an IAP?). I have looked into the Keychain, which has a public interface to view and edit the data, and NSUserDefaults, which saves to a Plist that can easily be changed.
Is there a better option for me, or should I just not even try (like the answer above suggests)?
Edit: The app is actually a developer tool that has a limited number of tokens. Because the users all have technical skills, the majority of them (I assume) have enough knowledge to find and change the Plist file or the Keychain data.

This solution worked for me very well. Try this: https://github.com/nielsmouthaan/SecureNSUserDefaults. It will store encrypted bool/string/float/integer in your UserDefaults file. Hopefully this is what you want. Make sure you download and add CocoaSecurity to your project. CocoaSecurity is a required element of SecureNSUSerDefaults, so you do not need to import it into any of your files. You must also download Base64, which is a required element of CocoaSecurity. You also need to add Base64 to your project, but do not need to import it into any of your files.
USAGE
Import the header file anywhere you want to use the encryption method.
#import <SecureNSUserDefaults/NSUserDefaults+SecureAdditions.h>
Then, set an encryption key, probably in your awakeFromNib method:
[[NSUserDefaults standardUserDefaults] setSecret:#"your_secret_goes_here"];
I recommend generating a random string of numbers and letters.
Then, you must store the information in your UserDefaults file.
[[NSUserDefaults standardUserDefaults]
setSecretObject:#"your_secret_object"
forKey:#"the_key_your_object_will be_stored_under"];
To retrieve the string, use this method:
NSString *retrievedData = [[NSUserDefaults standardUserDefaults]
secretStringForKey:#"the_key_your_object_will be_stored_under"];
I hope this helps!

Related

Swift: Populating Information from a Data File

I'm new to Swift and programming in general.
I'm working on a small OSX application that displays information for countries when the user clicks on a map of the world. The map interface works fine. I've tried this on a smaller map with just four countries. I've put my country data into a class called Country with variables for the data (e.g. population, landArea, majorExport, etc.) I put the Countries into an array. When the user selects a country, the controller grabs the right Country from the array and populates the data fields. So far, so good.
I'm getting ready to scale up to a map of the world with 150+ countries. Is there a way to store all of my data in a separate file (like a .csv file) so I don't have to hard code all of this Country data directly in a .swift file? If so:
(a) what kind of file should I use?
(b) how would I set it up?
(c) how do I get the application access it?
Thank you.
Now that I think about it, you can use a .plist file. It's the easiest, because you can simple use dict.writeToFile: to write and NSDictionary(contentsOfFile:) to read. And since NSDictionary is bridged to Swift, it just works. I found this great article

Storing a voice in User Defaults - Cocoa

I want to store a voice in the User Defaults. Since we can't store directly a voice, what's the best way for storing it? Using the index in the array [NSSpeechSynthesizer availableVoices] can differ after installing a new voice. And what about converting to an NSData or storing using its identifier?
Yeah, an index is a really bad idea. I use [[NSSpeechSynthesizer attributesForVoice: voice] objectForKey: NSVoiceName].

Identify ABRecord records uniquely: Is [ABRecord uniqueId] immutable?

I need to reference ABPerson records from within an application. I use the unique ID provided by
- (NSString *)uniqueId
and attach it to my in-app contact record.
Additionally, I save ABPerson's vCardRepresentation as a fallback. In case the app isn't any longer able to locate the ABRecord using the uniqueID, the app asks the user to recover the adressbook record using the saved vCardRepresentation. All works fine.
Unfortunately, a friend told me, that uniqueId isn't immutable: During a sync, uniqueId may suddenly change.
According to him, somewhere in iOS documentation, Apple explains that no way exists to immutable identify ABPersons using uniqueId. In OS X' Cocoa documentation, I failed to find such a hint.
On a given Mac, may the uniqueId change suddenly? If that's true, what's the correct way to identify ABPerson records from within an external application?
In case the uniqueID isn't immutable, I certainly may assign a custom property with a GUID. Unfortunately, custom fields do not sync.
Certainly, I'd prefer to use uniqueId.
For whats its worth, from Apple's techdoc:
kABUIDProperty
The unique ID for this record. It’s guaranteed never to change, no matter how much the record changes. If you need to store a reference to a record, use this value. Type: kABStringProperty.
Available in Mac OS X v10.2 and later.
Declared in ABGlobals.h.
It looks like the kABUIDProperty approach might not work anymore. I came across this blog entry with more discussion in the comments at: http://blog.clickablebliss.com/2011/11/07/addressbook-record-identifiers-on-mac-and-ios/.
A case in point: If a user decides to turn on iCloud sync, the unique ids in that user's address book will change. If the users turns off iCloud sync, they'll change again.
Addendum: it might be worthwhile looking at the StackOverflow entry here.
Apple's docs do say this (quoted from the link):
"The recommended way to keep a long-term reference to a particular record is to store the
first and last name, or a hash of the first and last name, in addition to the identifier.
When you look up a record by ID, compare the record’s name to your stored name. If they don’t match, use the stored name to find the record, and store the new ID for the record."

Enable Automatic Lightweight Migration of Core Data on NSPersistentDocument

ALM is great. But I can't get it to work on a project that uses Core Data with NSDocument. It seems that ALM is disabled by default.
Fine. For any normal project, you'd add the two appropriate options to the dictionary in this line:
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error])
...but that line doesn't exist when using CD with NSD (NSPersistentDocument hides the details and I can't see how you can modify them).
The following method seems to offer hope:
configurePersistentStoreCoordinatorForURL:ofType:modelConfiguration:storeOptions:error:
...some people on mailing lists (from 4+ years ago) report success overriding that method, altering the options dictionary, and then re-invoking it on "super". That didn't work for me, sadly. I would advise trying that first if you have similar problems - then come back here if it still doesn't work :)
Making a new Cored-Data-with-NSDocument project from scratch, I tried the approach that didn't work originally, and it worked fine this time:
-(BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url ofType:(NSString *)fileType modelConfiguration:(NSString *)configuration storeOptions:(NSDictionary *)storeOptions error:(NSError **)error
{
NSMutableDictionary *newOptions = [NSMutableDictionary dictionaryWithDictionary:storeOptions];
[newOptions setValue:#"YES" forKey:NSMigratePersistentStoresAutomaticallyOption];
[newOptions setValue:#"TRUE" forKey:NSInferMappingModelAutomaticallyOption];
return [super configurePersistentStoreCoordinatorForURL:url ofType:fileType modelConfiguration:configuration storeOptions:newOptions error:error];
}
NB: I suspect the root cause of my problems is that Xcode4 is incorrectly updating the private hashes it uses instead of version numbers to track a data-model version. Probably I accidentally added something, then deleted it, and it changed the hash - my original model was so very simple it was easy to compare by eye, and there were no differences.
Also, more generally, the problem is that Xcode4 still doesn't handle CoreData projects properly - it creates CoreData models as "versioned" by default (big improvement from Xcode3, which was doomed to always create useless models), but it still doesn't do any handling of changes in the model - you must manually remember to update the version BEFORE you save any changes (or else all your project migration will fail, forever, with no way out).
Also, I couldn't find any "correct" solution once things go wrong - just too many missing pieces from Apple's Core Data lib. Basically: NSPersistentDocument is incomplete, unsupported, so far as I can tell.
In the end, I went with the following BRUTAL workaround: instructed everyone with old versions to manually edit the CoreData stores to claim they are running the current version.
This works 100%, because unlike CoreData, my app is written to intelligently correct any obvious missing data on import. It's trivial, but CoreData doesn't allow you to say "yes, I've done this. It's OK. Go ahead and open the file!"
in old version: "save as XML"
gave them the new header to copy/paste into top of file. NB: Apple uses hashes instead of version numbers, which makes this look scary, even though it isn't. I don't understand why Apple is ignoring their own version system and using hashes instead?
in new version: open the updated file
in new version: "save"
...and because I'm overriding the NSManagedObject method:
-awakeFromInsert
and fixing any incorrect/missing data, the steps 3 and 4 above "automagically" update the data.
I know that the "correct" way would have been to create a mapping model, but it's massive overkill for most situations ... and Apple was refusing to load the old data in the first place. So, Core Data was just refusing to even allow me to correct the data - even though my code was happily doing so.
(all old projects have imported and exported correctly - no problems)
IMHO: CoreData really needs some re-design on Apple's end to fix all the edge-cases they didn't think about first time around.
NB: quick warning: make sure you DON'T change any CoreData variables inside awakeFromFetch - Apple temporarily disables change-tracking before calling that method (which is kind of laughable - it makes the method incompatible with the behaviour of all other "awakeFrom*" methods).
Apple's advice: inside awakeFromFetch, decide what you're going to change, then package it up and use something like this:
[self performSelector:#selector(myAwakeFromFetchFixItemX:) withObject:X afterDelay:0.01];
...just thought I'd add that for anyone trying this - otherwise your import will work fine, but your export will silently fail to include the fixed data!

Get User Account Image

Simply: how do I get the user's account image?
I'm using Cocoa on Mac.
The Address Book method doesn't work if the user deletes the vcard.
I use the CBIdentity method that never breaks, as Dave says:
+ (NSImage *)userImage
{
CBIdentity *identity = [CBIdentity identityWithName:NSUserName() authority:[CBIdentityAuthority defaultIdentityAuthority]];
return [identity image];
}
In order to use CBIdentity you need to add the Collaboration Framework to your targets and use the following include directive
#import <Collaboration/Collaboration.h>
If you only want an image for the logged-in user, it's also possible to get it with a one-liner using AddressBook:
NSData *imgData = [[[ABAddressBook sharedAddressBook] me] imageData];
but I believe that this is not guaranteed to be the same as the log-in image.
If you can get a handle to a CSIdentityRef or a CBIdentity* that represents the user in question, then you can invoke the -[CBIdentity image] method to retrieve their account image.
Edit:
Here's a previous answer of mine that shows how to query for all standard user accounts on a system and convert them into CBIdentity objects: Get all Users on OS X
If you don't want to link against Collaboration.framework, then you can use something like CSIdentityImageGetData (or one of the similar variants) to get the image directly. I personally find working with a native Cocoa object to be nicer, but in this case it's not absolutely necessary.

Resources