Reading a specific line from a Plist - objective-c

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.

Related

Getting EXC_BAD_ACCESS after splitting NSString

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.

How do I concatenate strings together in Objective-C?

So I am trying to concatenate a bunch of input strings together as one string so I can save that to a text file.
So far I am trying to write something like this
NSString *tempString = [[NSString alloc]initWithFormat:#"%#%#%#", text1, text2, text3];
The only problem with this is that I need a total of 30 strings stored this way. I need a way to do this without typing out each string name. Is there a way to use a for loop or something to accomplish this? Type the strings like this perhaps?
text(i)
So that the variable name would change each time it went through the for loop. I've tried doing something like this and I can't get it to work. If you can help me with this method or another way that you know to do it I would be very thankful.
Okay, so all of the answers here take the wrong approach (sorry guys).
The fundamental problem is that you are using your "text boxes" as a data source, when they should simply be views. When someone changes the text, you should immediately store them in your model (which could be a simple array) and then reference that model later. (This is part of MVC. Look it up if you aren't familiar, as you should be if you are programming for iOS!)
Here is what I would do. (I'm assuming that your "text boxes" are UITextField's.)
Set the delegate for each text field to your view controller.
Set the tag for each text field to a number which represents the order that you want the strings joined in. (ie 1-30)
If you don't have a separate class for your data model, then setup a declared property in your view controller which stores a reference to a NSMutableArray which can contain all of the strings in order. Let's call it dataSource. In viewDidLoad: set this to an actual mutable array filled with empty values (or previously stored values if you are saving them). The reason that we store empty values is so that we can replace them with the user entered strings for any index, even if they are entered out of order:
- (void)viewDidLoad
{
[super viewDidLoad];
self.dataSource = [[NSMutableArray alloc] initWithCapacity:20];
for(int i = 0; i < 30; i++)
[self.dataSource addObject:#""];
}
Then, use the following text field delegate method which stores the strings into the array as they are entered:
// This is called every time that a text field finishes editing.
- (void)textFieldDidEndEditing:(UITextField *)textField
{
if (textField.tag > 0)
[self.dataSource replaceObjectAtIndex:textField.tag-1 withObject:textField.text];
}
Congratulations! All of your strings are now stored in one array. Now we just have to combine them all:
NSMutableString *theString = [self.dataSource componentsJoinedByString:#""];
Note that I have NOT tested all of this so there may be typos. This should get you pointed in the right direction though!
If you set up your text boxes in Interface Builder with an IBOutletCollection(UITextField) you would have an array of text boxes that you could access the text value using KVC and join them.
//interface
...
#property (nonatomic, strong) IBOutletCollection(UITextField) NSArray *textBoxes;
//implementation
...
NSString *tempString = [[textBoxes valueForKey:#"text"]
componentsJoinedByString:#""];
Using iOS 4's IBOutletCollection
If you programmatically create your text boxes then add them to an array as you create them.
NSMutableString's appendString: method is your friend.
NSArray *strings = [NSArray arrayWithObjects: #"Hi", #" there", #" dude", nil];
NSMutableString *result = [[NSMutableString alloc] init];
for (NSString *string in strings) {
[result appendString:string];
}
NSLog(#"result: %#", result); // result: Hi there dude

Change values of NSArray after init

For my homework I have to write the .h and .m to a Command-line application, the main is given by my teacher. It a simple Chutes and Ladders style board print-out of 100 spaces.
The Ladders and Chutes will be indicated by "L10", and "C10" inside of a single dimension array that stores the board's representation. The first part of the given main is the initBoard which I think I will have create an empty string array. The number of Ladders and Chutes are given in the next two lines of the supplied code, and I am suppose to populate the board with them randomly via their methods.
If I use a for loop in a "initBoard"(the first thing the main calls)method to build a NSArray to a size of 100, populating it with empty stings, will I be able to change the empty strings into the "L10" and "C10" strings that I want in my "makeLadders" and "makeChutes" methods, or do I have to use an NSMutableArray for something like this?
I have read that NSArray is immutable, meaning that it cannot be modified after init, so one must use NSMutableArray if one wishes to modify the contents of arrays. My teacher specifically mentions NSArrays as what should hold the board data so I'm confused.
You have two choices here:
A mutable array containing NSString objects;
An immutable array containing NSMutableString objects.
Example with the first choice:
NSMutableArray* array=[[NSMutableArray alloc] initWithCapacity: 100];
for(NSUInteger i=0; i<100; i++)
{
[array addObject: #""];
}
To change the values use:
[array replaceObjectAtIndex: i withObject: #"New String"];

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.

Crashing Strings.... :(

I have in the .h file :
NSString *dataHML;
NSString *dataHML2;
NSString *dataHML3;
NSString *dataHML4;
NSString *dataHML5;
NSString *dataHML6;
NSString *dataHMLtotal;
in the .m file I merge them with :
NSString *dataHtmlTotal = [NSString stringWithFormat:#"%#%#%#%#%#%#", dataHtml, dataHtml2, dataHtml3, dataHtml4,dataHtml5,dataHtml6];
But unfortunately it crashes at some point because of this.
Could anyone give me a other solution and post it please, because I already tried nsuserdefault or a nsarray, but without I couldn't get it working.
If you really do have 6 variables numerically named like that, you could be better off with an array.
NSMutableArray *dataHMLStrings = [NSMutableArray array];
[dataHMLStrings addObject:#"String1"];
[dataHMLStrings addObject:#"String2"];
.
.
.
[dataHMLStrings addObject:#"String100"]; // or however many you have.
NSString *dataHMLTotal = [dataHMLStrings componentsJoinedByString:#""];
You can give the componentsJoinedByString: method a different string (I passed an empty string here because you didn't want anything to appear between each dataHML string).
Please make sure your strings are all allocated and initialized (neither points of which you mention in your question.) If you do not do so then you run the risk of manipulating data at the location of the garbage pointers, and your application will very likely crash.