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.
Related
I'm trying to follow team treehouse's course on objective-C.
This part, the course is trying to teach us about implementing categories. So I have the code below
main.m
#import <Cocoa/Cocoa.h>
#import "NSArray+mahem.h"
int main()
{
NSArray *letters = #[ #"alfa", #"bravo", #"charlie"];
NSLog(#"letters %#", letters);
NSLog(#"cap %#", [letters capitalizeStrings]);
return 0;
}
NSArray+mahem.h
#import <Foundation/Foundation.h>
#interface NSArray (mahem)
-(NSArray *)capitalizeStrings;
#end
NSArray+mahem.m
#import "NSArray+mahem.h"
#implementation NSArray (mahem)
-(NSArray *)capitalizeStrings{
NSMutableArray *cap = [NSMutableArray array];
for (NSString *string in self) {
[cap addObject:[string capitalizedString]];
}
return cap;
}
#end
Basically, I am trying to capitalize every word in the NSArray letters. However, when I run main.m in xcode 6, the program reaches a breakpoint at the line for (NSString *string in self) { in the file NSArray+mahem.m.
I have never used the xcode debugger before, so am unsure what I'm seeing or how I should fix this. It seems to say that self does have 3 objects (#"alfa", #"bravo", #"charlie"), but cap has 0, and string apparently equals 0xa1a1a1a1. I'm assuming this means it broke on the first loop, or else cap would have at least one object. Why did string get gibberish?
Is this because NSArray is const and is not mutable? How do I fix this? If anyone can explain this to me, it would be very helpful. Thanks
From your description, everything works just fine. When execution flow gets to the string for (NSString *string in self) {
Your self array is fully populated - it was created previously in main()
Your cap has been declared and initialized, but not populated - for loop wasn't executed once.
Your string was declared but not initialized - it exists, but points to some random place in memory. It is not broken. If you'll step over in debugger, you will see, that string becomes #"alpha", but cap is still empty. One more step over - string is still #"alpha" and cap is populated with one object.
Everything should work fine. Feel free to ask anything left unclear
I am trying to create a game in which people answer questions. I am having the questions be loaded from a text file. Different parts are separated with • and questions with §. The code for separating works fine, except for when I try to create a question with it.
for(int n = 0; n<[questionsFromFile count]; n++)
{
NSArray *params = [((NSString *)questionsFromFile[n]) componentsSeparatedByString:#"•"];
NSString *fact = params[0];
NSString *prompt = params[1];
NSString *image = params[2];
NSString *answerAsString = params[3];
BOOL answer = [answerAsString boolValue];
YNQuestion *question = [[YNQuestion alloc]initWithFact:fact prompt:prompt image:image answer:answer];
[self.allQuestions addObject:question];
}
In questions.txt:
fact•prompt•unknown.png•YES
§fact•prompt•unknown.png•NO
When I run it, it works fine until a question is loaded. Then, it crashes with EXC_BAD_ACCESS(code=2). If i replace fact, prompt, image etc. to equal #"hello" or #"goodbye" it works fine.
I am using ARC. Also, here is the code for the YNQuestion.
#interface YNQuestion : NSObject
#property(assign, nonatomic)NSString *fact;
#property(assign, nonatomic)NSString *prompt;
#property(assign, nonatomic)NSString *image;
#property(assign, nonatomic)BOOL answer;
-(id)initWithFact:(NSString *)fact prompt:(NSString *)prompt image:(NSString *)image answer: (BOOL) answer;
-(BOOL)checkIfCorrect: (BOOL)answer;
#end
Now, it works. Only with ones that are not my default.
Surprise! It doesn't work again. I believe the error is with having hardcoded answers and answers in the .txt file. I'm testing.
You need to keep strong references to the strings you pass to your initializer. Set NSString properties to strong instead of assign and it will stop crashing.
You are accessing an index that probably doesn't exist in the array. Try logging the count of the array, to see how many entries are in the array.
Also, set all-exception breakpoint to see where exactly the app crashes. Or you could set a breakpoint right after you load the array, to see its contents.
I have a PList where I load a couple of rows of data in a dictionary. I want to add the a line like
<key>StandardValue</key>
<string>STANDARDVALUEFORCERTAININSTANCE</string>
Now when I read out the values I get a NSString. How can I get the value of the constant that I previously defined with
#define STANDARDVALUEFORCERTAININSTANCE 123
Is there a way to get the constant representation of a string? So essentially to parse it?
What you want to do isn't exactly possible. The constants created with #define only exist at compile-time, and at run time there is no way to access them by name - they have been converted to the constant value already.
One alternative that might exist is to define a number of methods that return constant values, say in a Constants class. Then, at run time, load the name of the method from the plist and call it using NSSelectorFromString() and performSelector:.
However, a possible issue with this is that for safety with performSelector: you'd have to rewrite all your constants as Objective-C objects (since performSelector: returns type id). That could be quite inconvenient.
Nevertheless, here is an example implementation of the Constants class:
#implementation Constants : NSObject
+ (NSNumber *)someValueForACertainInstance
{
return #123;
}
#end
And example usage:
NSDictionary *infoDotPlist = [[NSBundle mainBundle] infoDictionary];
NSString *selectorName = infoDotPlist[#"StandardValue"];
SEL selector = NSSelectorFromString(selectorName);
NSNumber *result = [Constants performSelector:selector];
And how the selector name would be stored in the info plist:
<key>StandardValue</key>
<string>someValueForACertainInstance</string>
You can't do it this way. I suggest a nice alternative: KVC.
You declare this variable as class instance:
#property (nonatomic,assign) int standardValueForCertainInstance;
Then you get the value with valueForKey:
NSString* key= dict[#"StandardValue"];
int value= [[self valueForKey: key] intValue];
So the short of it is I want to define a global string variable that I can reference whenever. The function that I reference it in, it returns a string. As soon as I store it and reference it in another function it outputs as <CGPath 0x5bbf50>
What the heck? The code is below and keep in mind this is a module for Titanium.
First, the definition of the global variable..
#interface ComTestModule : TiModule <CBCentralManagerDelegate, CBPeripheralDelegate>
{
NSString * teststring;
}
The next part is the function where I first send the string variable from titanium to xcode..
-(void)setService:(id)args{
ENSURE_ARG_COUNT(args, 2);
teststring = [args objectAtIndex:0];
NSLog(teststring);
}
The output of the NSLog displays the actual string that was passed.
Now the final function where I call the string again and attempt to output it to the log..
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
NSLog(#"---%#", teststring);
}
As I said before, during this step it outputs as ---<CGPath 0x3ef4e0>
I'm really not sure what's going on.. Any help at all about getting this to return as the original string instead of the CGPath would be great!
Effects like this typically happen when you store a pointer to an object that was released and deallocated. The runtime will then replace that chunk of memory that previously held an NSString instance with, in this particular case, a CGPath instance.
If you want to ensure that your NSString stays alive, you need to take ownership of it. You can do that by either retaining it or copying it. Copying is the preferred method when talking about strings, so try replacing this line:
teststring = [args objectAtIndex:0];
With this:
teststring = [[args objectAtIndex:0] copy];
Now just be sure to release it when you're done.
The other poster's suggestion was good. You might want to make your testString variable into a copied property:
In your .h file:
#property (nonatomic, copy) NSString teststring;
And in your .m file
#synthesize teststring;
Then when you assign to it, use code like this:
self.teststring = [args objectAtIndex:0];
That "dot syntax" invokes the setter for your property rather than changing the instance variable directly. Since you declared your property with the "copy" qualifier, the setter method copies the string before putting it into the instance var.
Finally, you would add this code to your dealloc method:
self.teststring = nil;
The setter method also releases any old value in the property before assigning a new value, so setting it to nil releases the old value.
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.