Save game level in my app even when the app is closed? - ios

I am making an RPG game for the iPhone and everything is working out great but I need to know how to save my game level so that even if the user were to close the app running in the background the entire game wouldn't start over again. I was even thinking of bringing back old style gaming and making it so that you have to enter a password to start from where you left off. But even then I wouldn't know how to save the game properly. Plus even if I did save the game how would I be able to make it stay saved even when the app closes completely? So far I have tried adding save data code to the AppWillTerminate line but still nothing. Any help is appreciated.

I'm not sure if you want to save which level the user was on, or if you want to save the game state. If you simply want to save which level the user was on, you should go with #EricS's method (NSUserDefaults). It's a little more complicated to save game state. I would do something like this:
//Writing game state to file
//Some sample data
int lives = player.kLives;
int enemiesKilled = player.kEnemiesKilled;
int ammo = player.currentAmmo;
//Storing the sample data in an array
NSArray *gameState = [[NSArray alloc] initWithObjects: [NSNumber numberWithInt:lives], [NSNumber numberWithInt:enemiesKilled], [NSNumber numberWithInt:ammo], nil];
//Writing the array to a .plist file located at "path"
if([gameState writeToFile:path atomically:YES]) {
NSLog(#"Success!");
}
//Reading from file
//Reads the array stored in a .plist located at "path"
NSArray *lastGameState = [NSArray arrayWithContentsOfFile:path];
The .plist would look like this:
Using an array would mean that upon reloading the game state, you would have to know the order that you stored the items in, which isn't that bad, but if you want a more reliable method, you could try using an NSDictionary like this:
//Writing game state to file
//Some sample data
int lives = player.kLives;
int enemiesKilled = player.kEnemiesKilled;
int ammo = player.currentAmmo;
int points = player.currentPoints;
//Store the sample data objects in an array
NSArray *gameStateObjects = [NSArray arrayWithObjects:[NSNumber numberWithInt:lives], [NSNumber numberWithInt:enemiesKilled], [NSNumber numberWithInt:points], [NSNumber numberWithInt:ammo], nil];
//Store their keys in a separate array
NSArray *gameStateKeys = [NSArray arrayWithObjects:#"lives", #"enemiesKilled", #"points", #"ammo", nil];
//Storing the objects and keys in a dictionary
NSDictionary *gameStateDict = [NSDictionary dictionaryWithObjects:gameStateObjects forKeys:gameStateKeys];
//Write to file
[gameStateDict writeToFile:path atomically: YES];
//Reading from file
//Reads the array stored in a .plist located at "path"
NSDictionary *lastGameState = [NSDictionary dictionaryWithContentsOfFile:path];
The dictionary .plist would look like this:

To save the level:
[[NSUserDefaults standardUserDefaults] setInteger:5 forKey:#"level"];
To read the level:
NSInteger level = [[NSUserDefaults standardUserDefaults] integerForKey:#"level"];
I would set it whenever the user enters that level. You could wait until you are sent into the background, but there's really no point in waiting.

Related

Where to store Dictionary data in iOS

I am developing iPhone app,i have one doubt
I have an NSMutableDictionary which contains data in this format
dict at 0th index:
"ProductId":"77386",
"ProductImage":"http://static.abcd.com/images/product/large/design_gallery_slide_green - Copy (2).jpg",
"Productshortname":"Apple iPhone 5c 16GB",
"categorycode":null,
"categoryid":8,
"categoryimage":"",
"categoryshortname":"",
"favorite":"0",
"price":"31500",
"productnameinUrl":"apple-iphone-5c-16gb",
"storecount":"10"
dict at 1st index:
"ProductId":"11386",
"ProductImage":"http://static.abcd.com/images/product/large/design_gallery_slide_green - Copy (2).jpg",
"Productshortname":"Apple iPhone 5s 16GB",
"categorycode":null,
"categoryid":8,
"categoryimage":"",
"categoryshortname":"",
"favorite":"1",
"price":"31500",
"productnameinUrl":"apple-iphone-5s-16gb",
"storecount":"18"
dict at 2nd index:
"ProductId":"31386",
"ProductImage":"http://static.abcd.com/images/product/large/design_gallery_slide_green - Copy (2).jpg",
"Productshortname":"Apple iPhone 4s 16GB",
"categorycode":null,
"categoryid":8,
"categoryimage":"",
"categoryshortname":"",
"favorite":"1",
"price":"31500",
"productnameinUrl":"apple-iphone-4s-16gb",
"storecount":"38"
and so on...
What i want to do is, i want to store this dictionary indexes some where in my directory and i want to fetch it after some time or even after closing and opening the app after few times.
where should i store this kind of data ? is there any storage for strong this kind of data?
Please help and thanks in advance !!
You can store the data in NSUserdefaults and can access any time and anywhere as you want
yourdict;//Your NSDictionary Object That contains the data to store
[[NSUserDefaults standardUserDefaults] setObject:yourdict forKey:#"dict"];
[[NSUserDefaults standardUserDefaults] synchronize];
At the time of retrieval of data,
dict = [[NSUserDefaults standardUserDefaults] objectForKey:#"dict"];
You've already chosen an approved answer but I'll throw my thoughts in anyway.
This information looks like it could get large.
The user defaults isn't designed for large chunks of data. It's really meant for small bits of information, such as boolean preferences etc etc, not to be treated as an easy-to-use database.
Some problems with the user defaults:
The defaults file is read and parsed when you launch your app, regardless of whether you need your information from it at that time or not. This is because other parts of your app also use it for storing their bits of info too.
The entire defaults file needs to be parsed in order for you to retrieve anything, even if you just want a single entry.
You don't choose when the defaults file is parsed. You can't do any smart threading if it becomes huge (say you put 1000 products in there)
I'd recommend either writing the dictionary to it's own plist using NSDictionary's writeToFile: and reading using initWithContentsOfFile: (this still suffers from point #2 above)
OR
Using CoreData/sqlite to write the information to a real database.
NSDictionary methods: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/Reference/Reference.html#//apple_ref/occ/instm/NSDictionary/writeToFile:atomically:
An other option (And better in my experience) is to use - NSCoder, this option is great as you can use an Object with normal properties to access your data, which make your code more readable.
You can read about it here - NSCoding / NSKeyed​Archiver by NSHipster
An here is the reference - NSCoder docs
NSDictionary has a writeToFile: method which will do it for you.
NSDicationary writeToFile:Atomically:
use NSUserDefault and save your data like this in array
Here you can use this in anyway in your application for store value of NSUserDefaults.
// --- Saving
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// saving an NSString
[prefs setObject:#"TextToSave" forKey:#"keyToLookupString"];
// saving an NSInteger
[prefs setInteger:42 forKey:#"integerKey"];
// saving a Double
[prefs setDouble:3.1415 forKey:#"doubleKey"];
// saving a Float
[prefs setFloat:1.2345678 forKey:#"floatKey"];
// This is suggested to synch prefs, but is not needed (I didn't put it in my tut)
[prefs synchronize];
Here you can use this in anyway in your application for get value of NSUserDefaults.
// --- Retrieving
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// getting an NSString
NSString *myString = [prefs stringForKey:#"keyToLookupString"];
// getting an NSInteger
NSInteger myInt = [prefs integerForKey:#"integerKey"];
// getting an Float
float myFloat = [prefs floatForKey:#"floatKey"];
Thanks & Cheers ..
Looks like there can be more amount of data, so the best approach is to use core data to handle this scenario.
You can check few tutorials on how to use core data - Link1 , Link2
There are advantage of using core data over NSUserDefault and file system, as those load all the data at once and you might face some issue in performance.
You can also check following links which will illustrate you performance of different mechanism used to store data - PerformanceLinks1 PerformanceLinks2
try this
NSMutableDictionary *dictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
#"audio.caf",#"pictureAudioKey",
#"audio.m4a",#"englishAudioKey",
#"audio2.m4a",#"spanishAudioKey",
#"audio3.m4a",#"frenchAudioKey",
#"audio4.m4a",#"germanAudioKey",
#"audio5.m4a",#"italianAudioKey",
#"audio6.m4a",#"chineseAudioKey",
#"image.jpg",#"photoimagekey",
#"name.txt", #"identity",
#"imagename.txt",#"numberkey",nil];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [documentPaths objectAtIndex:0];
NSString *dictionaryPath = [documentsDirectory stringByAppendingPathComponent:[[self imageNameTextField]text]];
dictionaryPath =[dictionaryPath stringByAppendingFormat:#"dicitonary" ] ;
NSDictionary *savedDictionary = dictionary;
NSLog(#"The Save file is:%#", savedDictionary);
[savedDictionary writeToFile:dictionaryPath atomically:YES];

Saving Successfully, but not Visibly Editing plist

So this is my first time trying to save data in an iOS app. I've pieced together this code from various answers on this site in order to save a high score for a game I'm making. I created a plist named saves.plist (in my Supporting Files folder) and added a row of key #"bestScore" and type Number. The test log returns that the save is successful, and everything works; however, when I go to look at the plist after, nothing seems to have changed (the value of bestScore is 0). Am I saving to a different plist that is automatically created in my code? If this is the case, what is the point of being able to create plists in Xcode, and what is the best practice to use here as far as where/how to create/store/access plists?
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.fm = [NSFileManager defaultManager];
self.destPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];//Documents directory
self.destPath = [self.destPath stringByAppendingPathComponent:#"saves.plist"];
// If the file doesn't exist in the Documents Folder, copy it.
if (![self.fm fileExistsAtPath:self.destPath]) {
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:#"saves" ofType:#"plist"];
[self.fm copyItemAtPath:sourcePath toPath:self.destPath error:nil];
}
}
- (void)saveBestScore{
NSNumber *bestScoreBox = [NSNumber numberWithUnsignedLong:self.bestScore];
NSDictionary *data = #{bestScoreBox: #"bestScore"};
BOOL successful = [data writeToFile:self.destPath atomically:YES];
successful ? NSLog(#"YES") : NSLog(#"NO");
}
When you say
when I go to look at the plist after, nothing seems to have changed
(the value of bestScore is 0)
Do you mean looking at the plist in xcode project files ? You have copied the plist into a device directory and therefore you wont be able to see the change in xcode.
If you are using simulator, you can access the changed plist at:
~/Library/Application Support/iPhone Simulator/<Simulator Version>/Applications/<application>/Documents/
One easy way of storing score is to use NSUserDefault, which is a dictionary like persistence store for each application.
Set Score:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:#(score)
forKey:#"score"];
[userDefaults synchronize];
Get Score:
int score = [[[NSUserDefaults standardUserDefaults] objectForKey:#"score"] intValue];
UPDATE:
rmaddy mentioned NSUserDefaults supports setInteger:forKey and integerForKey: Therefore you dont need to wrap the score into a NSNumber
When you write an NSDictionary to a plist using writeToFile:, the keys and values in the dictionary must follow strict rules. All keys must be NSString objects and all values must be property values (NSString, NSNumber, NSDate, NSData, etc.).
The problem you have is your dictionary has a key that is an NSNumber, not an NSString.
It appears you actually create the dictionary incorrectly. The syntax is:
#{ key : value, key : value, ... }
Change your code to:
NSDictionary *data = #{ #"bestScore" : bestScoreBox }; // key : value
Side note - your last line should be:
NSLog(#"%#", successful ? #"YES" : #"NO");
It's not good practice to use the ternary operator to run two different commands. It's meant to return one of two values.

Load array and display based on state of uiswitch

Im trying to develop an app that displays a random truth or dare type question, however the user has the ability to turn off truths or dares in option. I have successfully managed to get the app to display a random quote from a plist file from either the truth or dare array also i have managed to program two switch buttons in the user options view controller.
My problem is how would i go about displaying only a truth or dare or both if the user has turned on of the uiswitchs off?
- (IBAction)button:(id)sender
{
if (!self.plistArray)
{
NSString *path = [[NSBundle mainBundle] pathForResource:
#"data" ofType:#"plist"];
NSUserDefaults *defaults =[NSUserDefaults standardUserDefaults];
if ([[defaults objectForKey:#"truthonoff"] isEqualToString:#"YES"])
{
NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray *plistArray1 = plistDict[#"truth"];
}
if ([[defaults objectForKey:#"dareonoff"] isEqualToString:#"YES"])
{
NSDictionary *plistDict2 = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray *plistArray2 = plistDict2[#"dare"];
}
self.plistArray = [[plistArray1 arrayByAddingObjectsFromArray:plistArray2] mutableCopy];
}
NSLog(#"%#", plistArray);
//check to see if array is empty and display message
if ([plistArray count] == 0)
{
self.text.text = #"array empty";
}
else
{
//display random quote from array
int randV = arc4random() % self.plistArray.count;
self.text.text = self.plistArray[randV];
[self.plistArray removeObjectAtIndex:randV];
}
}
That is my attempt however it will not run and i have the feeling it wont ddo the job i need.
Basicly i need it to display only truth if the user has selected that to true or only dare if that is selected or both if both are set to true.
EDIT
sorry the problem with the above code is the plist isnt being loaded and it is scipping straight to if array ==0 {
How do i ensure it loads the array and then checks which arrays to load from the plist file?
Any help is greatly appreciated
This is the code before i tried to add if statements. Im so confussed how best to do this
- (IBAction)shownext:(id)sender {
//load array and check then cycle through this untill array is empty. Array will add two arrays from plist file.
if (!self.plistArray) {
NSString *path = [[NSBundle mainBundle] pathForResource:
#"data" ofType:#"plist"];
NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray * plistArray1 = plistDict[#"truth"];
NSDictionary *plistDict2 = [[NSDictionary alloc] initWithContentsOfFile:path];
NSArray *plistArray2 = plistDict2[#"dare"];
self.plistArray = [[plistArray1 arrayByAddingObjectsFromArray:plistArray2] mutableCopy];
}
NSLog(#"%#", plistArray);
//check to see if array is empty and display message
if ([plistArray count] == 0) {
self.text.text = #"array empty";
}
else {
//display random quote from array
int randV = arc4random() % self.plistArray.count;
self.text.text = self.plistArray[randV];
[self.plistArray removeObjectAtIndex:randV];
}
}
First, if you have a switch for truth and one for dare I hope you have something in place to deal with when the user turns both switches off and doesn't understand why they get nothing (trust me it will happen).
For the rest I'm not sure exactly how you app works but I will take a guess. I'm thinking you have a utility style app with the main UI in one view and then an info button that flips to a second view where the switches are. I'm also guessing that there is a button in the main view that retrieves a truth or dare string. My final assumption, based on your code above, is that when the user changes the state of a switch that writes a user default that you've use a #define to keep out spelling mistakes.
When your view loads you should load both arrays in case the user changes their mind in the middle of using your app and turns on both options or changes from one to the other. Depending on how many entries you have in each of those arrays you might consider creating a combined array as well to simplify things.
When the button is pressed you should then look at the defaults and see if you need to look at both arrays or just one (the below is pseudo code)
if(truth && dare) {
// if you have a combined array pick a random element from it.
// otherwise first randomly pick one of the arrays to pick from.
}
else if (truth) {
// get a random element from the truth array
}
else {
// get a random element from the dare array
}
Also, your current checks of the switch values will always return no unless you are doing extra work in the switch view controller. You should be using [defaults setBool:<UISwitch.isOn> forKey:<#definedKeyHere>] and [defaults boolForKey:<#definedKeyHere>].
It would really help to know what part isn't working. For one thing, it might help to store your flags as NSNumber objects instead of strings (could your string comparison be failing?). Then you could do something like:
if ([[defaults objectForKey:#"truthonoff"] boolValue])
Use a literal to add the actual NSNumber - #YES or #NO.
Consider changing your logic to something like:
VERIFY CODE - doing this freehand:
if (!self.plistArray)
{
self.plistArray = [NSMutableArray array];
NSString *path = [[NSBundle mainBundle] pathForResource:
#"data" ofType:#"plist"];
// why are you loading this twice?
NSDictionary *plistDict = [[NSDictionary alloc] initWithContentsOfFile:path];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([[defaults valueForKey:#"truthonoff"] boolValue])
{
[self.plistArray addObject:plistDict[#"truth"]];
}
if ([[defaults valueForKey:#"dareonoff"] boolValue])
{
[self.plistArray addObject:plistDict[#"dare"]];
}
}
I am assuming that your code to load the Plists is working. Verify all your keys match what's in the Plist. Set a breakpoint and verify.
Calling a method on a nil object is a no-op in Objective C. So, it'll happily ignore calls to nil objects without telling you. Verify you have what you think you have.
Also, here:
//display random quote from array
int randV = arc4random() % self.plistArray.count;
self.text.text = self.plistArray[randV];
[self.plistArray removeObjectAtIndex:randV];
Consider using arc4random_uniform(self.plistArray.count) as it avoids modulo bias in the generator.
Now, this just gives you say 0 or 1 if you have two elements. Are you sure the two dictionary keys, "truth" and "dare" actually point to arrays?
Ok, everything working to this point. Now, you have an array of ARRAYS! So you need to randomly pick a question array, and THEN randomly pick a question.
Something like:
//get random array
int randArrayIndex = arc4random_uniform(self.plistArray.count);
NSArray* questionArray = self.plistArray[randArrayIndex];
//get random question
int randQuestionIndex = arc4random_uniform([questionArray count]);
NSString* randomQuestion = questionArray[randQuestionIndex];
self.text.text = randomQuestion;
// remove question
[questionArray removeObjectAtIndex:randQuestionIndex];
Something like that anyway. Of course, assuming you are storing NSStrings in those Plist arrays.

Adding Items to plist from another plist on app launch

My app is a list of items that the user can rate and take notes on etc. All of these items (dictionaries that store the user ratings and notes) are stored in a plist which is moved to the documents directory when the app first loads (if the plist file already exists in documents directory it skips this step). Now I've had a few reviews and people have been saying there should be more items in the lists. Now I'm trying to figure out how to go about adding new items to the plist. If I add them to the existing plist the users will never see the new items because the app checks to see if the database already exists in the documents directory. So they will never receive the new list. So I planned on creating a new plist with items in it and appending each new item to the old plist if the item doesn't already exist (because the user can add items themselves so I don't want to have duplicate entries in case the user added an item with the same name). My dilemma is I'm not sure where exactly/how to do the plist merging. Do I do it in the App Delegate? Do I do it in the viewDidLoad of the main view? How do I stop it from checking every time the app loads if it has already added the new items to the old plist? I know there are different ways to do it. I'm just looking for the easiest and "less load heavy on the app" way of doing it.
Ok. I solved it thanks to a couple people in one of the iOS chats. So basically inside the application didFinishLaunchingWithOptions method in my AppDelegate.m I loaded both plists into 2 different NSArrays array1 and array2, I also created an NSMutableArray array3 which I loaded with the original plist that was in the users documents directory.Then I looped through them both checking to make sure I didn't add any duplicate items based on their Name_Key (because the items in the arrays are dictionaries). If the items didn't match then I would add them to the 3rd array (array3). Then I saved array3 out to the original plist file path. Then I set an integer inside the user defaults so It would only perform this task once. I didn't want the app to go through this EVERY time the user opened the app. Below is the code I used.
int haveMergedPlist = [[NSUserDefaults standardUserDefaults] integerForKey:#"merged plist"];
if (haveMergedPlist < 1){
// I used a integer rather than a BOOL because if I ever want to perform this operation again with another app update all I have to do is change the number.
// merge code here
//new plist
NSString *newPlistPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"NewPlist.plist"];
NSArray *newPlistArray = [[NSArray alloc]initWithContentsOfFile:newPlistPath];
//original plist in user documents directory
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [searchPaths lastObject];
NSString *originalPlistPath = [documentsDirectory stringByAppendingPathComponent:#"originalPlist.plist"];
NSArray *originalPlistArray = [[NSMutableArray alloc]initWithContentsOfFile:originalPlistPath];
NSMutableArray *combinedPlistArray = [[NSMutableArray alloc]initWithContentsOfFile:originalPlistPath];
// Loop through both plists arrays and add if it does not match/exist.
BOOL itemExists = YES;
for (NSDictionary *newItem in newPlistArray) {
for (NSDictionary *originalItem in originalPlistArray) {
NSString * newItemNameString = [[newItem objectForKey:NAME_KEY] lowercaseString];
NSString * origItemNameString = [[originalItem objectForKey:NAME_KEY] lowercaseString];
//Compare lowercase strings and if they don't match then add them to the original plist array.
if ([newItemNameString isEqualToString:origItemNameString]) {
itemExists = YES;
break;
} else {
itemExists = NO;
//doesn't match so add it
}
}
if (itemExists == NO) {
[combinedPlistArray addObject:newItem];
NSLog(#"newItem added");
itemExists = YES;
}
}
//Write Array With New Items Added to documents directory
NSString *writeableDBPath = [documentsDirectory stringByAppendingPathComponent:#"originalPlist.plist"];
[combinedPlistArray writeToFile:writeableDBPath atomically:YES];
//set integer to 1 in user defaults so it only merge's once
//I comment this out while getting the loops to work otherwise you will have to delete the app out of the simulator or the phone to make sure its working every time.
[[NSUserDefaults standardUserDefaults] setInteger:1 forKey:#"merged plist"];
}

Custom playlist saving/retrieving

I have an app that allows the user to build a simple playlist which is stored as an array of MPMediaItems. When the app quits or enters the background, I want to store the playlist in a file and retrieve it when the user opens the app again.
Should I just be saving an id in a file as opposed to a whole MPMediaItem object?
Also, as the user's library may have changed how would I compare the saved list against the iPod library, do I loop through and run a bunch of queries?
Save this array into plist file, which is a effective way to save and read from local file.
Here in below are metheds to create, save, read data from plist. Hope it is helpful for you.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doc = [paths objectAtIndex:0]; //path of document folder
NSString *path = [doc stringByAppendingPathComponent:#"nameOfPlist.plist"]; //get the path of your plist in document folder.
NSDictionary* dic = [NSDictionary dictionaryWithObjectsAndKeys:#"123456",#"number", nil]; //save your data into a dictionary or array.
[dic writeToFile:path atomically:YES]; //then write the dic/array into the plist file.
NSDictionary* dic2 = [NSDictionary dictionaryWithContentsOfFile:path]; //read data from plist.
NSLog(#"dic2 is:%#",[dic2 objectForKey:#"number"]);

Resources