iOS: Easy way of accessing XML data records? - objective-c

I have an application which needs to load data from a remote server (businesses by location) and display them on a map. The feed from the server is XML based. I have successfully done an implementation of this but wondered if I could make my life a bit easier.
Currently, I am using Google's GDataXML library, I have implemented a library for getting remote data in the background and it calls me back when complete or during loading if I want.
When the full data has been loaded, I traverse the document and convert the different levels to be an object, adding that object to an NSMutableArray (as I want to do lazy loading so I want to add as more is requested) and then passing that array to the next bit of my app that then interprets and pins/annotates the map for me.
Example XML data (abstracted):
<businesses>
<business>
<name> Fred Bloggs and Co </name>
<address> 123 No Street, Nowhere </address>
<town> Somesville </town>
<county> Someshire </county>
<postcode> XX11 1XX </postcode>
</business>
..... more records but you get the idea .....
</businesses>
Example storage object (abstracted)
-- businessrecord.h --
#interface BusinessRecord : NSObject {
NSString *name;
NSString *address;
NSString *town;
NSString *county;
NSString *postcode;
}
#property (nonatomic, copy) NSString *name;
#property (nonatomic, copy) NSString *address;
#property (nonatomic, copy) NSString *town;
#property (nonatomic, copy) NSString *county;
#property (nonatomic, copy) NSString *postcode;
#end
-- businessrecord.m --
#implementation BusinessRecord
#synthesize name, address, town, county, postcode;
#end
Right, so you can probably guess that i'm manually parsing each XML element in each node and manually transferring them to the BusinessRecord object.
This is particularly time consuming as I have to write three or more lines of code in different places for each property.
Under GDataXML you access each element with something like:
NSArray *businesses = [[[xmlRoot elementsForName:#"businesses"] objectAtIndex:0] elementsForName:#"business"];
NSMutableArray *businessList = [[NSMutableArray alloc] initWithCapacity: businesses.count];
for (int i=0; i<businesses.count; i++) {
GDataXMLElement *business = [businesses objectAtIndex: i];
BusinessRecord *busRec = [[BusinessRecord alloc] init];
busRec.name = [[[bus elementsForName:#"name"] objectAtIndex:0] stringValue];
.... etc for each element ...
[businessList addObject: busRec];
[busRec release];
}
This all seems extremely long winded to me, there must be a better way of doing this?
What I want to end up with is everything in my XML at the level "business" in some kind of random access array, I don't want to individually specify each element.
Ideally, something like in PHP where you can just have a sequential array containing associative arrays like:
$businessList = Array(
Array(
"name" => "Fred Bloggs and Co",
"address" => "123 No Street",
"town" => "Sometown",
"county" => "Someshire",
"postcode" => "XX11 1XX"
)
);
So i'm guessing I need to write a wrapper class that can interpret a specific element and get all its sub elements right (ie, enumerate the elements and then work on them).
What do I store this in? NSDictionary?
Maybe something like (pseudo code):
NSArray *businesses = [[[xmlRoot elementsForName:#"businesses"] objectAtIndex:0] elementsForName:#"business"];
NSMutableArray *businessList = [[NSMutableArray alloc] init];
[xmlBreakerUpper breakUpXMLAtElement: businesses addToMutableArray: &businessList];
Has anyone any experience with GDataXML and can help me with enumeration as I can't follow the sparse documentation.
I'm not totally attached to GDataXML at this stage, i've only two classes that count on it so if there's a better way.
I am in control of the server output but the client has a preference for XML for other data access API's they may or may not want to implement later.
I don't need to send XML back to the server at this stage, the requests are HTTP GET based at present but may become POST later when people fill in forms.
All help or pushes in the right direction greatfully received.

Option 1:
You could create your own init method for your businessrecord class that accepts a GDataXMLElement argument and parse out the values you need using some XPath expressions, which are a bit more pleasant on the eyes ;)
Option 2:
To achieve the PHP-like array of associative arrays, you can:
Iterate over the array of "business" elements, and iterate over each business element's children, using the child element names as keys for your NSMutableDictionary business record - and stash the newly created dictionary in the businessList array.
/* THIS IS JUST A QUICK PSEUDO-CODE-ISH SAMPLE */
NSArray *businesses = [xmlRoot nodesForXPath:#"//businesses/business" error:nil];
NSMutableArray *businessList = [[NSMutableArray alloc] initWithCapacity: businesses.count];
GDataXMLElement *business;
for (business in businesses)
{
NSMutableDictionary *businessRecord = [[NSMutableDictionary] array];
// iterate over child elements of "business", use element names as keys for businessRecord
NSArray *businessChildren = [business children];
GDataXMLElement *businessChild;
for (businessChild in businessChildren)
{
[businessRecord setValue:[businessChild value] forKey:[businessChild name]]
}
[businessList addObject:businessRecord];
[businessRecord release];
}
I stumbled upon this article "How To Read and Write XML Documents with GDataXML" while researching options for XML parsing that was a solid read, however they are parsing in specific model objects, rather than a more generic data object like you're looking for.
You should check out their other article "How To Choose The Best XML Parser For Your iPhone Project" as GDataXML seems like overkill.

Related

Mutual changes in two NSMutableArray objects

Assume, your .h file looks like this:
#property (retain, nonatomic) NSMutableArray *songs;
- (NSMutableArray *)popularSongs;
- (void)make20PercentDiscountToPopularSongs
Your .m file looks like this:
- (NSMutableArray *)popularSongs
{
NSMutableArray *popularSongs = [NSMutableArray array];
for (Song *song in self.songs) {
if (song.isPopular) {
[popularSongs addObject:song];
}
}
return popularSongs;
}
- (void)make20PercentDiscountToPopularSongs
{
for (Song *song in self.popularSongs) {
song.price = song.price * 0.8;
}
}
The code above adds a 20% discount to popular songs. I recognise that there is a more simplistic way of doing this. You could have the "make20PercentDiscountToPopularSongs" and "popularSongs" function in a single function but let's assume that the code is written the way it is written above.
In the example above, will the line:
song.price = song.price * 0.8;
Actually, make any changes to the object:
#property (retain, nonatomic) NSMutableArray *songs;
Or not? Because, it seems as if the line will change the newly created popularSongs NSMutableArray rather than the songs NSMutableArray, am I right? This problem is bothering me for a lot of time now. I would like to make changes just to the original songs array. To me popularSongs works as if you are storing the pointers to the songs in the songs array.
It changes neither array. It changes some of the objects in the songs array. And as the same objects are in the popularSongs array returned then they are 'changed' too.
This:
[popularSongs addObject:song];
Adds the pointer to the current song into the array. It doesn't create a new copy of the song.
You can create a quick mutable array like this:
NSMutableArray *popularSongs = self.songs.mutableCopy;
Rather than iterating through all the songs to determine if they are popular, you could use a filter predicate at the time you create the array.
NSMutableArray *popularSongs = [[self.songs filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"isPopular = %#", #YES]] mutableCopy];
With this one-liner you can avoid your methods altogether ;-)- Not sure what you want, but if you want to alter the original songs, leave out the mutableCopy part.

Reading a specific line from a Plist

Hi I've been looking all over and can't seem to find an answer to my issue. I have very little experience with programming, especially Objective-C.
In my app there is a plist with a certain number of strings. When the user taps a button, the program counts the number of strings in the plist, creates a random number within that range, and is supposed to read the line of the plist with that specific number.
Say if there were 20 strings in the plist, and the app generated the number 5, then the app is supposed to read the 5th string in the plist.
Is there a way to do such a thing or is there another, more efficient way to accomplish this? ANY help is GREATLY appreciated. Thanks.
Edit:
In my .h file I have
NSMutableArray* myArray;
int plistCount;
int randomNumber;
}
#property (nonatomic, retain) NSMutableArray* myArray;
And in my .m file I have
NSString *path = [[NSBundle mainBundle] pathForResource:#"myplist" ofType:#"plist"];
NSMutableArray* tmpArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
self.myArray = tmpArray;
plistCount=[self.resorts count];
randomNumber=arc4random() % plistCount;
I dont know where to go from here.
NSString * randomString = self.myArray[arc4random_uniform(self.myArray.count)];
The above code is selecting a random element from self.myArray.
arc4random_uniform generates a number between 0 and the argument (excluded) so using self.myArray.count as a bound you'll get a valid random index for the array.
NOTE
You have to explicitly check that the array is never empty in order for the above code to work, since
arc4random_uniform(0) returns 0, therefore in case of an empty array it will generate a NSRangeException.

Updating contents of class variable NSMutableArray

I'm new to Objective C and iOS development in general, so if I missed some information that needs to be included please let me know and I'll do my best. At the same time, if any of my assumptions are incorrect or if I set this up totally dumb, please don't hesitate to yell at me. Thanks!
I have a class object called feeds. Here's how I initialize it in the .h file:
#interface ClassViewController : ContentViewController <UITableViewDelegate,
UITableViewDataSource> {
NSMutableArray *feeds;
}
#property (nonatomic, strong) NSMutableArray* feeds;
and in the .m:
#dynamic feeds;
I'm trying to get results from JSON and load them up into the feeds. Let's assume that resArr has correct data in it:
NSArray *resArr = [results objectForKey:#"data"];
if([self->feeds count]) {
[self->feeds removeAllObjects];
[self->feeds addObjectsFromArray:resArr];
}
else {
self->feeds = [[NSMutableArray alloc] initWithArray:resArr];
}
Now, this works fine the first time (i.e., the first time I put data into the array), but I get the following error subsequent times: *** -[NSMutableArray addObjectsFromArray:]: array argument is not an NSArray
I'm clueless. Any ideas?
Edit: JSON structures
1:
{"code":200,"data":[{"name":"ABM"},{"name":"ACC"}]}
2:
{"code":200,"data":{"100":{"subject":"ABM","title":"Decision Making in Agri-Food"},"130":{"subject":"ABM","title":"Farm Management I"}}}
you should use self.feeds instead of self->feeds, here's a good read Dot (“.”) operator and arrow (“->”) operator use in C vs. Objective-C
are you sure the subsequent times are actually NSArrays?
edit additon
Looks like the first JSon is just a array of names, where's as the second is a dictionary keyed with 100, 200, etc. So the JSON converts this into a dictionary keyed to 100, 200, ...
NSArray* resArr = [results objectForKey:#"data"];
if([self.feeds count]) {
[self.feeds removeAllObjects];
if([resArr isKindOfClass:[NSDictionary class]])
[self.feeds addObjectsFromArray:resArr.allValues];
else
[self.feeds addObjectsFromArray:resArr];
}
else {
self.feeds = [[NSMutableArray alloc] initWithArray:resArr];
}

Creating an Array From a CSV File Using Objective C

I'm new to coding so please excuse me if this seems like a simple question.
I'm trying to plot coordinates on a map.
I want to read a CSV file and pass the information to two separate arrays.
The first array will be NSArray *towerInfo (containing latitude, longitude and tower title)
the second, NSArray *region (containing tower title and region) with the same count index as the first array.
Essentially, I believe I need to;
1) read the file to a string.....
2) divide the string into a temporary array separating at every /n/r......
3) loop through the temp array and create a tower and region object each time before appending this information to the two main storage arrays.
Is this the right process and if so is there anyone out there who can post some sample code as I'm really struggling to get this right.
Thanks to all in advance.
Chris.
I have edited this to show an example of my code. I am having the problem that I'm receiving warnings saying
1) "the local declaration of 'dataStr' hides instance variable.
2) "the local declaration of 'array' hides instance variable.
I think I understand what these mean but I don't know how to get around it. The program compiles and runs but the log tells me that the "array is null."
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize dataStr;
#synthesize array;
-(IBAction)convert {
//calls the following program when the onscreen 'convert' button is pressed.
NSString *dataStr = [NSString stringWithContentsOfFile:#"Towers.csv" encoding:NSUTF8StringEncoding error:nil];
//specifies the csv file to read - stored in project root directory - and encodes specifies that the format of the file is NSUTF8. Choses not to return an error message if the reading fails
NSArray *array = [dataStr componentsSeparatedByString: #","];
//splits the string into an array by identifying data separators.
NSLog(#"array: %#", array);
//prints the array to screen
}
Any additional help would be much appreciated. Thanks for the responses so far.
NSString* fileContents = [NSString stringWithContentsOfURL:filename ...];
NSArray* rows = [fileContents componentsSeparatedByString:#"\n"];
for (...
NSString* row = [rows objectAtIndex:n];
NSArray* columns = [row componentsSeparatedByString:#","];
...
You'll probably want to throw in a few "stringTrimmingCharactersInSet" calls to trim whitespace.
Concerning your warnings:
Your code would produce an error (not a warning), since you need to declare your properties in the interface file before you synthesize them in the implementation. You probably remember that #synthesize generates accessor methods for your properties. Also, before using the #synthesize directive, you need to use the #property directive, also in the interface.
Here's an example:
#interface MyObject : NSObject {
NSString *myString;
}
#property (assign) NSString *myString;
#end
#implementation MyObject
#synthesize myString;
// funky code here
#end
Note that the property declaration is followed by a type (assign in this case, which is the default). There's an excellent explanation about this in Stephen G. Kochans's book: Programming in Objective-C 2.0
But assuming for argument's sake, that you omitted the correct #interface file here.
If you first declare a property in the #interface, and then declare another property in your method, using the same variable name, the method variable will take precedence over the instance variable.
In your code, it would suffice to omit the declaring of the variable name, like so:
dataStr = [NSString stringWithContentsOfFile:#"Towers.csv" encoding:NSUTF8StringEncoding error:nil];
array = [dataStr componentsSeparatedByString: #","];
I'm assuming that the core of your question is "how to parse a CSV file", not "what to do with the data once it's parsed". If that's the case, then check out the CHCSVParser library. I have used it in projects before and find it to be very reliable. It can parse any arbitrary string or filepath into an NSArray of rows/columns for you. After that, whatever you do with the data is up to you.

Where do I create global variables for an iOS app?

Here is my code:
I want to be able to create a global NSMutableArray that can store Budget* objects that can then be written to a .pList file... I'm only learning what pLists are, and I am a bit hazy about how to implement them...
Where am I going wrong here?
- (IBAction)btnCreateBudget:(id)sender
{
Budget *budget = [[Budget alloc] init];
budget.name = self.txtFldBudgetName.text;
budget.amount = [self.txtFldBudgetAmount.text intValue];
// Write the data to the pList
NSMutableArray *anArray = [[NSMutableArray alloc] init]; // I want this to be a global variable for the entire app. Where do I put this?
[anArray addObject:budget];
[anArray writeToFile:[self dataFilePath] atomically:YES];
/* As you can see, below is where I test the code. Unfortunately,
every time I run this, I get only 1 element in the array. I'm assuming
that this is because everytime the button is pressed, I create a brand new
NSMutableArray *anArray. I want that to be global for the entire app. */
int i = 0;
for (Budget * b in anArray)
{
i++;
}
NSLog(#"There are %d items in anArray",i);
}
-(NSString *) dataFilePath
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [path objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"BudgetData.plist"];
}
edit: I'd like to add that I am creating the anArray array so that it can be accessible by other views. I understand that this can be done with NSNotification? or Should I do this the appDelegate classes? The end goal is to have the anArray object populate a UITableView that is in a separate View.
Just put the declaration outside the method instead of inside it.
NSMutableArray *anArray = nil;
- (IBAction)btnCreateBudget:(id)sender
{
...
if ( anArray == nil )
anArray = [[NSMutableArray alloc] init];
...
}
If it's only used inside the one file, make it "static" instead to prevent name collisions with other files:
static NSMutableArray *anArray = nil;
If it's only used inside the one method, make it "static" and put it inside that method:
- (IBAction)btnCreateBudget:(id)sender
{
static NSMutableArray *anArray = nil;
...
if ( anArray == nil )
anArray = [[NSMutableArray alloc] init];
...
}
Note that people usually use some kind of naming convention for global variables, like "gArray", to easily differentiate them from local variables, instance variables, or method parameters.
Global variable is not necessary in this case. You can do something like this:
Read old data to mutable array (initWithContentsOfFile:).
Add new record to the array.
Save the array to same file.
But the second problem in your code is that if your Budget class is not a property list type (NSString, NSData, NSArray, or NSDictionary objects) writeToFile: will not save it sucessfully.
You need to make sure that your Budget class invokes NSCoder and then the NSCoder initWithCoder: and NSCoder decodeWithCoder: methods. Otherwise, writeToFile: will not work for you NSObject class.
But I digress. The answer to the original question should be the following.
In your .h file you need to do the following.
#interface WhateverClassName : UIViewController
{
NSMutableArray *anArray;
}
#property(nonatomic, retain) NSMutableArray *anArray;
#end
Then, you need to make sure you #synthesize the NSMutableArray so that you don't get any freaky warnings. This is done just after the #implementation line in your .m file.
Then, within the function that you want it to be allocated into memory, simply do the following.
anArray = [[NSMutableArray alloc] initWithObjects:nil];
This is now a global variable. It is global in the sense that it can be used from any function and is not limited to use in one function.
If you would like to have data accessible to the entire application or context ("global"), you can use a singleton. However, do this with care and make sure it is actually necessary and appropriate. I would suggest doing plenty of reading up on it prior to any implementation of a singleton. Carter Allen has a good basic implementation here.
According to "The end goal is to have the anArray object populate a UITableView that is in a separate View" you wouldn't need to write anything to a file, database or singleton. Just set the object. Like stated by Sebastien Peek.
If you wish for offline data storage, look into sqlite, json, plist , etc