This tutorial will explain how to enable your iOS game app to read and write xml files. The reason we need to be able to do this is to store data that persists even after the app is terminated. A lot of people might use Core Data for this purpose, that’s what I did when I made iHeals. I think Core Data has a steeper learning curve than an XML Parser. Core Data is really powerful however so it really comes down to your needs. I’m going to base my tutorial on GDataXML which is discussed by Ray Wenderlich in this tutorial.
We continue off by using the Testing project source code from Part 2. Once you’ve downloaded it, open it up in Xcode.
GDataXML is one of the fastest XML parsers you can read and write with. It’s written by Google so I guess they know what they’re doing. Just as Ray explained in his tutorial we need to integrate GDataXML with our project before we can use it. Here’s how:
- Download and extract GDataXML.zip
- Drag the XMLSupport folder into the Testing project’s Resources directory in Xcode.
- Double check there are two files GDataXMLnode.h and GDataXMLnode.m in the XMLSupport folder.
Before it will work, GDataXML needs some special project build settings configured:
- Select your project, build settings, all.
- Scroll down and find Header Search Paths in the Search Paths section
- Add /usr/include/libxml2 to the Header Search Paths list as shown below:
- Scroll up again, this time to the Linking section. Add -lxml2 to Other Linker Flags as shown below:
Introducing the Data Template
Before you go down the xml path you should consider if you need things like table relationships. If you do then you should probably learn Core Data instead. If you’re happy with simple persistent data then read on.
I’ve created a template you can use to form the basis of a new data class set. Download & extract Data.zip. Put the extracted Data folder into your Testing project as shown in the picture below. Note the extra folders for Chapters, GameData and Levels are included for later use.
The data template is made up of three parts:
- An xml file – for persistent data.
- A data class – for the data structure usable within your app.
- A data parser class – for creating an instance of the data class from the xml file. Also used to save data back into the xml file again.
1. XML File (TemplateData.xml)
XML is a pretty well known standard for storing application data. The example xml included shows three simple elements ExampleInt, ExampleBool and ExampleString within a root element called ExampleData:
Being an XML file each of the values are all actually string values and not integers or boolean. That’s where the Data Class can set a few things straight before we attempt to use this data in our app.
2. Data Class (TemplateData.h/m)
Before we try reading in data from the xml we need to establish what variables we want from it. The template includes an integer, boolean and string variable example. When you adapt this code to your own needs you might want to use additional variable types. I’ve highlighted in orange the parts you would expect to change when customising this file for your own use later on:
Note: The underscores in front of some variables are only used to stop a warning regarding hiding of the instance variables. We get this warning because there are two variables of the same name in play here. The first is the variable taken by the method. The second is the class variable we populate with the method variable.
The sole purpose of the TemplateData class is to specify the data types we will use with our xml. Again, I’ve highlighted in orange the parts you would expect to change when customising this file for your own use:
3. Data Parser Class (TemplateDataParser.h/m)
The Data Parser Class is where all the action happens. It uses the variables defined by TemplateData to save/load data to/from the specified XML file. At first glance the parser can look intimidating. If you deleted out my heavy commenting you’ll find it’s not that big or bad. The parser class does three things, which is obvious by the three methods:
- Prepares an XML file for use: + (NSString *)dataFilePath:(BOOL)forSave
- Loads data from xml, returning a TemplateData instance: + (TemplateData *)loadData
- Saves data to xml from a TemplateData instance: + (void)saveData:(TemplateData *)saveData
The best way to explain the various parts of the parser is to use it in practice.
Creating GameData from Data Template
This section will detail step-by-step how you use the data template to enable reading and writing to a new XML file called GameData.xml. The point of GameData.xml will be to house the following variables for our game, albeit in the form of a string.
- selectedChapter will be an integer value representing the currently selected chapter. This will be useful to keep track of what level selection screen customisations to load.
- selectedLevel will be an integer value representing the currently selected level. This will be useful to keep track of what level to load when we launch the game scene.
- sound will be a BOOL value representing whether the sound is on or off.
- music will be a BOOL value representing whether the music is on or off.
Step 1 – Copy Data Template
- Using finder copy the 5 files from the Testing/Data/Template folder into the Testing/Data/GameData folder.
- Rename each file replacing the word Template with the word Game.
- Add the files to xcode. They should show up as follows:
Step 2 – Modify GameData.XML
Replace example data in GameData.xml with the following:
<GameData> <SelectedChapter>1</SelectedChapter> <SelectedLevel>1</SelectedLevel> <Sound>1</Sound> <Music>1</Music> </GameData>
Step 3 – Configure GameData.h
The template should guide you pretty well through the customisation process. It will instruct you to perform the following:
- Rename TemplateData to GameData where requested (highlighted below in red)
- Declare variables with an underscore in front (highlighted below in green)
- Declare your variable properties without an underscore (highlighted below in blue)
- Put in a custom init method interface based on the variables you’re going to pass to the init method (highlighted below in yellow). If you’re unsure how to structure this method please read the section titled Passing Multiple Variables to Methods in my previous iOS Newbie Part 5 – Method Man tutorial.
Step 4 – Configure GameData.m
Much like the interface customisation the implementation customisation is as simple as following the template guides. The guides will instruct you to:
- Rename TemplateData to GameData (highlighted below in red)
- Synthesize your variables (highlighted below in green). Note: you could synthesize all the variables on one line if you wanted. It looks neater for the tutorial on multiple lines. Also notice each variable is set to equal _variable.
- Put in your custom init method to match the one in the interface header file (highlighted below in blue)
- Set all class instance variables to the values passed to the method (highlighted below in yellow)
Step 5 – Configure GameDataParser.h
The header is easy, just rename stuff highlighted in red and yellow below:
Step 6 – Configure GameDataParser.m
Customising the implementation is more involved so I’ll break it into parts 6a, 6b and 6c.
Step 6a – Configure GameDataParser.m xmlFileName
Rename stuff highlighted in red and yellow below, including setting the xmlFileName (without .xml on the end)
Step 6b – Configure GameDataParser.m for Loading Data
In the loadData method you’ll need to rename TemplateData to GameData and also put in variables appropriate to the GameData class (highlighted below in green):
Next, put in the appropriate root element name based on what is in GameData.xml:
Now for the fun part. For every variable you will need to:
- Create a new array (highlighted below in red)
- Base the new array on the appropriate element in GameData.xml (highlighted below in yellow)
- Create new element (highlighted below in orange)
- Set the variable (highlighted below in green)
Step 6c – Configure GameDataParser.m for Saving Data
Saving to the xml is done by passing an instance of GameData to the saveData method. For the saveData method to work we need to:
- Rename some things, including the parent element (highlighted below in red)
- Create child elements for all variables (selectedChapter example highlighted below in orange)
- Set the element name for all variables (selectedChapter example highlighted below in yellow)
- Set the value of the string that will go into the element for all variables (selectedChapter example highlighted in green below)
- Double check the variable types you’re converting from are appropriate (highlighted in blue below)
- Set up the gameDataElement to include each variable element & add it to the document to be saved (highlighted in pink below)
Now all the annoying stuff has been set up we can easily read and write to GameData.xml from anywhere in our app.
- Open up the MainMenu.m scene
- Import GameData.h and GameDataParser.h
- Put the following code at the end of the init method:
// Testing GameData GameData *gameData = [GameDataParser loadData]; CCLOG(@"Read from XML 'Selected Chapter' = %i", gameData.selectedChapter); CCLOG(@"Read from XML 'Selected Level' = %i", gameData.selectedLevel); CCLOG(@"Read from XML 'Music' = %i", gameData.music); CCLOG(@"Read from XML 'Sound' = %i", gameData.sound); gameData.selectedChapter = 7; gameData.selectedLevel = 4; gameData.music = 0; gameData.sound = 0; [GameDataParser saveData:gameData];
Before you run the project note the original values in GameData.xml. When you run the app for the first time GameData.xml will be copied to your device (or the simulator). Note: Unless you delete the app the copy of GameData.xml now lives on your device or the simulator. It won’t be overwritten by changes you make to the original xml in Xcode. Be careful with this because once you release an app on the app store people don’t delete their app in order to upgrade it.
When you run the project you will see the variable values from the xml show up in the log window. The other thing the code does is write some new values to the xml file now found on the device. To check the new values have been written properly you’ll need to use finder to go to the address listed in the log window. For me, the location was shown as :
Saving data to /Users/tim/Library/Application Support/iPhone Simulator/5.0/Applications/5C25E098-9CA1-4252-B099-49053334A2FC/Documents/GameData.xml
Phew! That wraps up part 3. Here’s the complete source code
Thanks for reading!
If you liked this tutorial or found something wrong with it please let me know!
If you want to support my work and have an iPad please consider purchasing iSoccer *wink*