I want to create a game, that will use a level system. So i want to store my levels and to be able to change them during the game (to save the state). So i decided to use XML for storing levels. I found NSXmlParser class for reading from XML, but i can't find a writer to save the level state. In my game the level state and the level are very similar ( i have a lot of movable objects), so i don't wan't to store the level state data separated from the level it belongs. The problem is that i can't find a way to easily modify XML files on iPhone. Maybe i'm using a bad approach.
If you throw the data in an NSDictionary, you could do (with caveats):
[myDictionary writeToFile:pathToPlist atomically:YES];
Try the open source XML stream writer for iOS:
Written in Objective-C, a single .h. and .m file
One #protocol for namespace support and one for without
Example:
// allocate serializer
XMLWriter* xmlWriter = [[XMLWriter alloc]init];
// start writing XML elements
[xmlWriter writeStartElement:#"Root"];
[xmlWriter writeCharacters:#"Text content for root element"];
[xmlWriter writeEndElement];
// get the resulting XML string
NSString* xml = [xmlWriter toString];
This produces the following XML string:
<Root>Text content for root element</Root>
I would recommend using KissXML. The author started in a similar situation as you and created an NSXML compatible API wrapper around libxml. He discusses the options and decisions here on his blog.
You can use the C libary libxml2 to read and write XML. Here's a quick intro: Cocoa Samurai: Getting Some XML Love with libXML2.
However, have you considered using CoreData or implementing the NSCoding protocol? NSCoding would be easier to add to existing classes.
Related
I have a Class for handling my data in my project, and now I need to store the data.
I'd like to use a Plist but I'm a bit unsure of how to start.
My class is pretty simple - 6 pieces of data, flat (no hierarchy).
I want my app to start with no data, so can I assume that I should create the PList programmatically once the User creates their first piece of data? (That is, don't create a .plist file in 'Supporting Files' prior to distribution?)
Then, when the app starts the next time, read the data and create an NSMUtableArray array of Class instances?
To create a property list, all you need to do is use appropriate types (i.e. those that support the property list format: NSData, NSString, NSDictionary, NSNumber, NSDate, NSArray), store them in a single container, and tell the containing object to write itself to a file. To read the data, you can initialize that same type using a path. For example:
// writing some data to a property list
NSString *somePath = ... // replace ... with the path where you want to store the plist file
NSMutableDictionary myDict = [NSMutableDictionary dictionary];
[myDict setObject:#"Caleb" forKey:#"name"];
[myDict setObject:[NSNumber numberWithInt:240] forKey:#"cholesterolOrIQ"];
[myDict writeToFile:somePath atomically:YES];
// reading the file again
NSDictionary *readDict = [NSDictionary dictionaryWithContentsOfFile:somePath];
The simplest way is to simple save an NSArray or NSDictionary to disk. Caleb's answer goes into detail there so I won't repeat it, other than to say you might have to convert a non-compatible object like NSColor to an property list object like NSData. It's up to you to do this each time you save or load your data.
NSKeyedArchiver and NSKeyedUnarchiver give you a little more control over the process, but work pretty much the same way. You provide (or get back) a plist compatible root object (usually an NSDictionary) that contains your data. I recommend creating a dictionary that includes your data structure as well as an arbitrary number (your app's build number is a good choice) to use as a version indicator. This way if you ever update your data model you can easily determine if you need to do anything to convert it to the new version.
If you're putting your own objects into the data file, look into NSCoding. The protocol gives you two methods using NSKeyedArchiver and NSKeyedUnarchiver to save and restore your data. This is by far the most straightforward approach if your data model consists of anything more than a few simple strings and numbers, since you're dealing with your own native objects. In your case, you would have your data class implement NSCoding and use the NSKeyedArchiver and NSKeyedUnarchiver methods to encode your six instance variables. When it's time to save or load, pack the instance of your class into an NSDictionary (along with a versioning number as I mentioned above) and call NSKeyedArchiver's archiveRootObject:toFile:. Your save an load methods deal only with your own data object, which makes things easy for you. The common pitfall to watch out for here is if your custom data object contains other custom object. This is fine, but you have to make sure every object that's going to be saved has its own NSCoding implementation.
Two things you can do:
Use NSUserDefaults:
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/Reference/Reference.html
The objectForKey method is the one you want to use to store your class. But, as pointed out in the comments, this shouldn't really be used for storing lots of user data; it's best for saving preferences.
For storing more data, you might want to look at Core Data. It's more complex, but should be better suited to your needs. Here's a tutorial on it:
http://mobile.tutsplus.com/tutorials/iphone/iphone-core-data/
Neither of these seems best for your simple application, but I leave this answer up since it gives alternatives for saving data to the iPhone.
I have a sample code wherein:
I am unable to understand how data flows from database to reports because there is no place to set or fetch data explicitly. It is all done through custom objects.
Even we are not able to fetch data through console using NSLog as it has ENUM keys
All objects are custom objects so if we put it in NSLogs we get structure like < CustomObject-hexcode>. Many of the classes again comprise of objects from other classes.
Even if I try to debug code by putting breakpoint, then after few steps it show Hexadecimal codes
I wont be able to put sample code as it is a whole project containing lot of files.
Can you please let me know what kind of approach has been used? It looks like some sort of encapsulation.
Thanks!
Write categories that implement the - (NSString* ) description method for each custom object that displays all variables the custom obejects hold, you can there translate each of the enum values to strings. Then you can use NSLog to output objects and get readable results.
you need to debug application. and for this "All objects are custom objects so if we put it in NSLogs we get structure like < CustomObject-hexcode>"
you need to integrate a category class in your project. Search on google for override description of nsobject. You will get nslog in proper format.
It sounds like some kind of custom ORM with serialization / deserialization etc. Euuuwww.
I apologise if this has been asked before but I can't find the info I need.
Basically I want a UITableView to be populated using info from a server, similar to the SeismicXML example. I have the parser as a separate object, is it correct to alloc, init an instance of that parser & then tell RootViewController to make it's table data source a copy of the parser's array.
I can't include code because I haven't written anything yet, I'm just trying to get the design right before I start. Perhaps something like:
xmlParser = [[XMLParser alloc] init];
[xmlParser getXMLData];
// Assuming xmlParser stores results in an array called returnedArray
self.tableDataSource = xmlParser.returnedArray
Is this the best way of doing it?
No, you don't want to do this. You don't want your view controller directly accessing the array of the data-model. This would work in the technical sense but it would be fragile and likely to fail as the project scaled.
As the projects grow in complexity, you will want to increasingly wrap your data model object (in this case the xmlParser) in protective layers of methods to control and verify how the data model changes. Eventually, you will have projects with multiple views, multiple view controllers as well as information entering from both the user and URLs. You need to get into the habit of using the data-model object not just a dumb store you dump stuff into but as an active manager and verifier of your data.
In a situation like this I would have my data-model's array completely wrapped by making it a #protected or #private property. Then I would have dedicated methods for fetching or inserting data into the actual array inside the data-model class itself. No objects outside of the data-model should actually have direct access to the array or have knowledge of its indexes.
So, in this case your data-model would have something like:
- (NSString *) textForLineAtIndexPath:(NSIndexPath *) anIndexPath{
//... do bounds checking for the index
NSString *returnString=[self.privateArray objectAtIndex:anIndexPath.row];
if (returnString=='sometest'){
return returnString;
}
return #""; //return an empty string so the reciever won't nil out and crash
}
as well as a setTextForLineAtPath: method for setting the line if you need that.
The general instructional materials do not spend enough (usually none) time talking about the data-model but the data-model is actually the core of the program. It is where the actual logic of the application resides and therefore it should be one of the most complex and thoroughly tested class in your project.
A good data-model should be interface agnostic i.e. it should work with a view based interface, a web based interface or even the command line. It should neither know nor care that its data will be displayed in a tableview or any other interface element or type.
When I start a new project, the first thing I do is comment out the '[window makeKeyAndVisible];' in the app delegate. Then I create my data-model class and test it old-school by loading data and logging the outputs. Only when it works exactly how I wish it to do I then proceed to the user interface.
So, think real hard about what you want the app to do on an abstract level. Encode that logic in a custom class. Isolate the data from all direct manipulation from any other object. Verify all inputs to the data before committing.
It sounds like a lot of work and it is. It feels like overkill for a small project and in many cases it is. However, getting the habit early will pay big dividends very quickly as your apps grow in complexity.
Not quite. You want the data source to be an object that implements the UITableViewDataSource protocol; what I would do in this situation is create an object that implements that protocol and parses XML, so that you can alloc-init it, then set the data source to that object and have it update the table view as appropriate. So based off your code (and assuming you're running within the table view's controller):
XMLParserAndDataSource xpads = [[XMLParserAndDataSource alloc] init];
[xpads getXMLData];
self.tableView.dataSource = xpads;
It's probably a good idea to give this class itself a reference to an NSXMLParser object, so you can use that to parse the XML, then provide convenience methods (like getXMLData) as well as the UITableViewDataSource methods for your own use. (If you go this route, you should also make your XMLParserAndDataSource class implement the more useful of the NSXMLParser delegate methods, and use them as appropriate to update your table view.)
I'm a Mac programmer and not an iPhone programmer; but on the mac,
self.tableDataSource = xmlParser.returnedArray is not correct. You are supposed to either bind the table's content to an Array Controller (if iPhone has one?) or set the datasource outlet to your RootViewController.
In your rootview controller, you would implement the methods:
– tableView:cellForRowAtIndexPath:
– tableView:numberOfRowsInSection:
For – tableView:cellForRowAtIndexPath: you would return a UITableViewCell with the data you received from the XML parsing according to the index path like so:
UITableCell *myCell = [UITableCell new];
myCell.textLabel.text = [parsedXMLArray objectAtIndex:[indexPath indexAtPosition:indexPath.length-1]];
return myCell;
(Something people don't know is that you can use the + new class method on all NSObject subclasses which automatically call alloc/init.)
For – tableView:numberOfRowsInSection just return the count of the data array:
return parsedXMLArray.count;
Can't edit my question nor post replies, can only post my response as answer.
#TechZen: I'm somebody who tries to form analogies, helps me understand. What you're saying is something like: My original idea was like going into the file room & dumping all the originals on my desk to work on where as you suggest the object be more like an organised file clerk who will search through the data for me and only return the specific datum that I need while being the only one with direct access to that data.
Have I understood correctly?
#Tim: What if I later need the parser to get data for something which is not a table? That's why I thought to dump it into an array & let the caller decide what to do with the data. Would you suggest a second object that would supply the data in the newly required form? (Am I sort of one the right track here or way off?)
I feel a little dumbfounded. I know how to store any kind of "plist supporting" data in a preferences file, such as NSArray, NSDictionary, NSString and their CF counterparts. No problem there.
Now I like to store the values of my custom object this way. I had thought that if I implement some kind of serialization/archiving protocol, I could get NSUserDefaults to understand my class just like it understand NSDictionary.
E.g, implementing the NSCoding protocol should give the NSUserDefaults code all that it needs: I give it the key names along with the values as plist compatible types (NSString, mostly in my case). But my encoder doesn't even get invoked. :(
Then I thought that there must be at least some function that generates a NSDictionary from the NSCoding protocol, so that I can then send this dict to store in the prefs. Ideally, there'd by something like the NSKeyedArchiver that I pass any NSCoding compatible object and it gives me a NSDictionary, and vice versa. But that doesn't appeat to exist in Apple's framework.
Do I have to write that really myself? I'd expect this would be a quite common need.
Note: I realize that NSKeyedArchiver generates a binary plist, which I could write as a plist file. But that's not what I want. I want to add the contents of this object to my app's prefs plist file, i.e. I want to store both my object and other prefs data in the plist file. That's what doesn't seem to be possible with the given functions.
I'm currently just adding objects to an NSDictionary then calling NSDictionary's "writeToFile:atomically:" method. I'm mostly adding strings, and an image as NSData, but I believe anything I add to the NSDictionary that implements the NSCoding protocol should get written to the file.
Then later when I call NSDictionary's initWithContentsOfFile everything gets put back in the dictionary as it had been. Would this work for you - letting the dictionary take care of the serialization stuff?
My app is made up of a TabBarController, each tab with a UITableView.
On launch I parse an XML file from my server into an Object class and then display the objects in the first tableview.
My question is, what do I do when I want to parse a second XML file? Currently, when doing so, the information in "XML-file-2" will overwrite the objects parsed by "XML-file-1". How do I go about this properly? Do I set up another Object class for each XML file or is there another to work around the issue?
I am using NSXMLParser.
I think you should consider having two instances of XMLParser, one for each XML file you want to read. It allows you to read as many XML files concurrently without affecting each other. It is also more modular.
on line 21 of that snippet ( http://pastie.org/537227 ) you are setting the products array (appDelegate.products)to a new mutable array. if you want the second run to append to appDelegate.products, you should see if appDelegate.products already has objects in it, if so, don't assign a new array to it, just add to them to it using NSMutableArray's addObject: method
... Don't overwrite the data that's already there...?
If you're displaying the contents in a UITableView, then I'd be willing to bet you've got an NSArray in there somewhere. Hopefully, if you've set this up properly, the NSArray contains model objects, each one of which corresponds to one row in your UITableView. However, I would suggest using NSMutableArray. Then when you parse the second XML file and build your model objects out of that, just use NSMutableArray's addObject: method and then reloadData on the UITableView.
As notnoop already mentioned, to make multiple NSXMLParser instances would be the best solution.
A open source iPhone RSS reader called Simple RSS Reader would be a good sample of what you want now.
You might use RSSParser class of the Simple RSS Reader as it is.
HTH