I'm using JSON to parse data into an NSArray 'courseArray'. I then filter this data using NSPredicate and store it into 'rows'. The problem is, i dont know how to determine if 'rows' is empty or not. For example : If a record exists, rows will contain the appropriate objects, but if the record doesnt exist then the application crashes (also when i use NSLog to see the contents of the array its blank, i believe its supposed to say its nil? if there are no objects). How can i fix this?
if (dict)
{
courseArray = [[dict objectForKey:#"lab"] retain];
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"labNumber== %#",labSelected]; //filter
rows = [[courseArray filteredArrayUsingPredicate:predicate]retain];
}
[jsonreturn release];
self.formattedTextView.opaque = NO;
self.formattedTextView.backgroundColor = [UIColor clearColor];
NSLog(#"array %#",rows);
**NSDictionary *currentSelection = [rows objectAtIndex:0];** (app crashes at this line - [NSArray objectAtIndex:]: index 0 beyond bounds for empty array')
NSString *labNumber = [currentSelection objectForKey:#"labNumber"];
-objectAtIndex: will raise an exception if there is no object at the nominated index. If rows is empty there won't be an object at index 0, hence the exception. As you don't catch the exception, the result is that your app is terminated.
You probably want to compare [rows count] to 0 to check that there's something in it and to react appropriately if not.
Try if([courseArray count] > 0) also another method that can be of help here is
[courseArray containsObject:(id)]
Related
I have an NSArray containing NSDictionary's of client data. I am using NSPredicate to filter the array and pass it through ViewControllers with segues. I am at a stage where I would like to change some values of the keys stored in the dictionary. How is this achievable?
My prepareForSegue method
if ([[segue identifier] isEqualToString:#"showClients"]) {
// Detect selected row
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
// Extract value from the Clinic_Location attribute
NSString *cString = [[_clinicList valueForKey:#"Clinic_Location"] objectAtIndex:indexPath.row];
NSLog(#"Row pressed: %#", cString);
// Set predicate where Clinic_Location equals the clinic string extracted from the selected row
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"Clinic_Location == %#", cString];
// Filter the client array using this predicate
filteredClientArray = [dataStart filteredArrayUsingPredicate:predicate];
NSLog(#"Filtered array: %#", filteredClientArray);
// Reload the table
[self.tableView reloadData];
// Sort the array by Appt_Time in ascending order
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]initWithKey:#"Appt_Time" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil];
NSArray *sortedArray = [filteredClientArray sortedArrayUsingDescriptors:sortDescriptors];
filteredClientArray = [NSMutableArray arrayWithArray:sortedArray];
// Pass the newly filtered & sorted array to next view
PCClientsViewController *pcClients = segue.destinationViewController;
pcClients.clientArray = filteredClientArray;
}
The array now only contains certain dictionaries based on this predicate (i.e. when user selects a row on the TableView, let's say 'Manchester', then only dictionaries with a Clinic_Location of Manchester get passed to the next view.
My prepareForSegue method on the next TableView is similar to the previous one, in this case my predicate filters the dictionary with a matching surname on the selected row and pushes that to the next view. Now I am left with an array constructed like the one below:
Filtered array: (
{
"Appointment_Attended" = Yes;
"Appt_Time" = "2:00";
"Client_Address_Line_1" = "Ap #452-4253 Massa. Street";
"Client_Address_Line_2" = Montebello;
"Client_Address_Line_3" = Hull;
"Client_Address_Line_4" = Quebec;
"Client_Address_Line_5" = Micronesia;
"Client_Forename" = Veronica;
"Client_Postcode" = 69591;
"Client_Surname" = Ellis;
"Client_Tel" = "(019376) 26081";
"Clinic_Location" = Manchester;
"Passed_To_Medical" = No;
"Passed_To_Sol" = No;
}
)
I have tried many different approaches to update specific dictionary keys in this array such as using predicates, but nothing seems to work. For example:
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"Client_Address_Line_1 == %#", [[_clientDetails valueForKey:#"Client_Address_Line_1"]objectAtIndex:0]];
NSLog(#"%#", predicate);
NSArray* filteredArray= [_clientDetails filteredArrayUsingPredicate: predicate];
[filteredArray performSelector: #selector(setValue:forKey:) withObject:self.clientAddLine1.text withObject:#"Client_Address_Line_1"];
No matter what I try I am continuously getting error messages such as:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x7904f590> setValue:forUndefinedKey:]: this
class is not key value coding-compliant for the key
Client_Address_Line_1.'
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI
setObject:forKey:]: unrecognized selector sent to instance 0x78774c70'
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSDictionaryI 0x7bff6eb0> setValue:forUndefinedKey:]:
this class is not key value coding-compliant for the key
Client_Address_Line_1.'
It seems no matter what I do I cannot change or update any dictionary keys within the array. After hours of research, I'm beginning to wonder if this is this even possible? Am I looking at the wrong approach completely? Is there a better way to do this? After the keys have been changed, I need to save them back to the array and pass it back through ViewControllers also (possibly with Unwind Segues?) Any help is much appreciated.
You may only change the values for key in mutable subclasses of NSDictionary, NSDictionary itself is immutable.
so since you have changed to a mutable array, you can do the same thing for each of the dictionaries also:
filteredClientArray = [NSMutableArray arrayWithArray:sortedArray];
for(NSDictionary *d in [filteredClientArray copy])
{
NSMutableDictionary * m = [d mutableCopy];
[filteredClientArray replaceObjectAtIndex:[filteredClientArray indexOfObject:d] withObject:m];
}
I get an array from a JSON and I parse it into an NSMutableArray (this part is correct and working). I now want to take that array and print the first object to a Label. Here is my code:
NSDictionary *title = [[dictionary objectForKey:#"title"] objectAtIndex:2];
arrayLabel = [title objectForKey:#"label"];
NSLog(#"arrayLabel = %#", arrayLabel); // Returns correct
//Here is where I need help
string = [arrayLabel objectAtIndex:1]; //I do not get the first label (App crashes)
NSLog(#"string = %#", string);
other things that I have already tried are as follows:
string = [NSString stringWithFormat:#"%#", [arrayImage objectAtIndex:1]];
and
string = [[NSString alloc] initWithFormat:#"%#", [arrayImage objectAtIndex:1]];
Any help is greatly appriciated!
EDIT: The app does not return a single value and crashes.
Your code doesn't match the structure of your JSON. In your comment on the deleted answer, you said you got an exception when sending objectAtIndex: to an NSString. In your case, arrayLabel isn't an array when you think it is.
If your JSON has an object, your code needs to treat it as an NSDictionary. Likewise for arrays and NSArray and strings and NSString.
In addition to whatever else was going on, you repeatedly refer to "first" but use the index 1. In most C-based programming languages (and others, as well) the convention is that indexes into arrays are 0-based. So, use index 0 to get the first element.
I am using a singleton class to transfer a NSMutableArray across views.
The issue I am having is my array is getting displayed multiple times.
Right now I am working with three UITextFields. Each is getting added to the array and outputted in a specific format. Here is what my output looks like:
A / B
C
A / B
C
A / B
C
All I need shown is:
A / B
C
Here is my code if someone can help me find what is missing or needs to be reworked:
To display the text:
UILabel * myLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 300, 200)];
[myLabel setFont:[UIFont fontWithName:#"Helvetica" size:10.0]];
myLabel.numberOfLines = 0;
NSMutableString * string = [NSMutableString string];
Education * myEducation = [Education sharedEducation];
for (Education * output in myEducation.educationOutputArray)
{
[string appendString:[NSString stringWithFormat:#"%# / %# \n%#\n", [myEducation.educationOutputArray objectAtIndex:0], [myEducation.educationOutputArray objectAtIndex:1], [myEducation.educationOutputArray objectAtIndex:2], nil]];
}
myLabel.text = string;
[self.view addSubview:myLabel];
Here is where I am saving the text:
-(void)saveButtonTapped:(id)sender
{
[_educationArray addObject:_aTextField.text];
[_educationArray addObject:_bTextField.text];
[_educationArray addObject:_cTextField.text];
Education * myEducation = [Education sharedEducation];
myEducation.educationOutputArray = _educationArray;
[self.navigationController popViewControllerAnimated:YES];
}
EDIT *
When a user is entering text into the text fields they can also add a new set of text using the same three text fields.
If I removed the for loop only the first is displayed. How can I get all of the text to display?
EDIT TWO *
I tried this:
[string appendString:[NSString stringWithFormat:#"%# / %# \n%#\n", [output major], [output university], [output timeAtSchool]]];
Results in this error:
2012-12-29 17:03:07.928 EasyTable[14501:c07] -[__NSCFString major]: unrecognized selector sent to instance 0x7176850
2012-12-29 17:03:07.941 EasyTable[14501:c07] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString major]: unrecognized selector sent to instance 0x7176850'
*** First throw call stack:
(0x1c99012 0x10d6e7e 0x1d244bd 0x1c88bbc 0x1c8894e 0x6162 0xff817 0xff882 0xffb2a 0x116ef5 0x116fdb 0x117286 0x117381 0x117eab 0x1184a3 0x118098 0x2c10 0x10ea705 0x21920 0x218b8 0xe2671 0xe2bcf 0xe1d38 0x2e5213 0x1c61afe 0x1c61a3d 0x1c3f7c2 0x1c3ef44 0x1c3ee1b 0x1bf37e3 0x1bf3668 0x1e65c 0x240d 0x2335 0x1)
libc++abi.dylib: terminate called throwing an exception
You add three elements at a time to the array, and later you want to process them three at a time.
It would probably be better to create a container class that holds the three elements, and add instances of the container class to the array.
But if you really want to do it this way, you can't use a for/in loop. You have to use an index variable:
NSArray *array = myEducation.educationOutputArray;
for (NSUInteger i = 0, count = array.count; i < count; i += 3) {
[string appendFormat:#"%# / %#\n%#\n", array[i], array[i+1], array[i+2]];
}
you don't have duplicates in the array: the problem is in the for loop
for (Education * output in myEducation.educationOutputArray)
{
[string appendString:[NSString stringWithFormat:#"%# / %# \n%#\n", [myEducation.educationOutputArray objectAtIndex:0], [myEducation.educationOutputArray objectAtIndex:1], [myEducation.educationOutputArray objectAtIndex:2], nil]];
}
here you are looping through the array 3 times (because there are 3 objects in the array) and then printing each object each time. I think you should just do the appendString: line wihtout the for loop
Your for loop is really odd; You loop through each object in the array but then you hardcoded the indexes inside the for loop. I'm not sure what's going on there but it's certainly the source of the odd output. That pointer to an Education object is what you should access. Each time through the loop, use the value of output.
If you're also having uniqueness issues, consider using an NSSet instead of an NSMutableArray. If you care about order, use NSOrderedSet. Set collection types do not allow duplicated values.
Also one more point: If you know you're only going to ever access the values at index 0, 1, and 2, there's really no point to having an NSArray. Just store each value separately and access it directly. At the very least there's no reason for the for loop.
Edit:
You're crashing because you think your array has Education objects in it but it really has plain strings:
-[__NSCFString major]: unrecognized selector sent to instance 0x7176850
Notice that it's saying that you tried to call the method major on an __NSCFString (a private subclass of NSString that apple uses internally). This is a good indicator that your array doesn't contain what you think it does. Make sure you're loading your data into your array correctly by constructing Education objects with the three pieces of data.
You also suggested that you tried
[string appendString:[NSString stringWithFormat:#"%# / %# \n%#\n", output]];
Basically what stringWithFormat: does is take that format string and use it like a template and then it takes as many arguments as you need to fill up the placeholders in the template. %# is a placeholder for an object's description so stringWithFormat: is going to expect three parameters after the format string to fill each %#.
I'm making a code which shows the names of people on a list.
The list is different for each date, so my problem is when there is only like 1 or no people signed and I make an array with index beyond the limit of people, it crashed. I know that this happens because the array is empty, but how do I make the code ignore empty arrays?
I have tried to make an "if" that count the number of arrays and then decide to post the array or just post no name. But it doesn't work like this, I still get the out of bounds exception.
How should I manage empty arrays?
My code:
NSString *html = [request2 responseString];
NSMutableArray *arr2 = [html componentsSeparatedByString:#"vagter"];
NSString *html1 = [arr2 objectAtIndex:1];
//name1
NSMutableArray *arr3 = [html1 componentsSeparatedByString:#"<td><font color=#ffffff>"];
NSString *html2 = [arr3 objectAtIndex:1];
NSMutableArray *arr4 = [html2 componentsSeparatedByString:#"</font></td>"];
NSString *html3 = [arr4 objectAtIndex:0];
_name.text = html3;
//name 2
NSMutableArray *arr5 = [html1 componentsSeparatedByString:#"<td><font color=#ffffff>"];
if ([arr5 count] > 4) {
NSString *html4 = [arr5 objectAtIndex:5];
NSMutableArray *arr6 = [html4 componentsSeparatedByString:#"</font></td>"];
NSString *html5 = [arr6 objectAtIndex:0];
_name.text = html5;
}
else
{
_name1.text = #"No name";
}
It should be:
if ([arr5 count] > 5) {
NSString *html4 = [arr5 objectAtIndex:5];
...
Indeed, index 5 will correspond to the sixth array item, so you have to have at least 6 objects in it.
Use the same pattern, if you want to check for the array bounds, in all cases.
The problem is that you expect the return from componentsSeparatedByString to return consistent results according to your expectations.
Clearly thats not working.
Array handling is simple. Dont ask for objects that arent there.
Check the count and only access indexes from 0 to count - 1;
If count is zero dont access anything.
working with a basic objective c example here, tried to use replaceObjectAtIndex for an array and it doesn't seem to be working.
my code:
NSMutableArray *myArray=[NSMutableArray array];
[myArray addObject:#"First string"];
[myArray addObject:#"Second string"];
[myArray addObject:#"Third string"];
NSString *newElement=[myArray objectAtIndex:1];
NSLog(#"New object at index 1 BEFORE is %#", newElement);
[myArray replaceObjectAtIndex:1 withObject:#"Hello"];
NSLog(#"New object at index 1 AFTER is %#", newElement);
theoretically the output for newElement should now display "Hello", but it's still displaying "Second String"
output:
2012-05-30 11:21:16.638 cocoa lab[753:403] New object at index 1 BEFORE is Second string
2012-05-30 11:21:16.641 cocoa lab[753:403] New object at index 1 AFTER is Second string
please advise
thank you
You need to get the new value from the array after replacing it
// ...
[myArray replaceObjectAtIndex:1 withObject:#"Hello"];
/* ADD THIS */
newElement=[myArray objectAtIndex:1];
NSLog(#"New object at index 1 AFTER is %#", newElement);
At the moment you fetch the original string, replace the array's object, and print out the original string again.
You need to reset the value of newElement after calling replaceObjectAtIndex:, so add another line before logging the value the second time:
newElement=[myArray objectAtIndex:1];
UPDATE 1
And don't feel bad -- we've all done this to ourselves at one time or another. :-)
UPDATE 2
By the way, a good way to avoid this kind of problem in general is to get in the habit of using separate local variables in these kinds of situations, for example, you could rewrite the code you posted as follows:
NSString *initialValue = [myArray objectAtIndex:1];
NSLog(#"New object at index 1 BEFORE is %#", initialValue);
[myArray replaceObjectAtIndex:1 withObject:#"Hello"];
NSString *newValue = [myArray objectAtIndex:1];
NSLog(#"New object at index 1 AFTER is %#", newValue);