I've scoured and still haven't found anything that quite works. Either the question/answer is too old or it simply hasn't worked for me. This is my first attempt at "my own" app. As it seems a right of passage, I'm making a checklist app. Here's what I'm looking for:
My Data Store contains 4 attributes: name, category, isChecked, isActive (more will surely follow as I expand)
When my View Controller initially loads, the NSFetchedResultsController has an NSPredicate that only retrieves the records whose attribute isActive is YES (or [NSNumber numberWithBool:YES). It then takes those records and displays them into the appropriate cells for the user. When a user clicks on a cell, the Data Store updates and changes the isChecked attribute accordingly. Everything works good to this point.
What I need to do now is to be able to remove the items (1 or more) from the list. Specifically, I need it to update the Data Store attributes isChecked and isActive to NO only if it's current isChecked attribute is YES. (I'm not looking to delete the record from the data store as they will be used to build up the database for the users future use.)
I've used, among other things:
[[[self fetchedResultsController] fetchedObjects]
setValue:[NSNumber numberWithBool:NO]
forKey:#"isChecked"];
This does actually work, it removes the checkmark(s) and updates the store accordingly. Problem is, not only am I making another request to the data store for the isActive items, it also searches the entire "Active List" that was fetched and sets each of their isChecked attributes to NO. This may not be too big of an issue for small lists, but as the list(s) expand this can be an issue.
The other problem is, if I add:
[[[self fetchedResultsController] fetchedObjects]
setValue:[NSNumber numberWithBool:NO]
forKey:#"isActive"];
It sets ALL of my list items to NO (as well as a second data store request within the same method.)
So my question is: How can I get through the list, find only the items that are checked and update only those records (set both the isChecked && isActive attributes = NO) whose isChecked attribute is YES rather than working through the entire list?
I've tried creating a separate fetchedResultsController specifically for this buttons action, and it did work (that is to say, it didn't crash) but the debugger popped out a rather large 'Serious Application Error'. I won't post the error message as it's long and most likely irrelevant to any solution.
Any assistance would be greatly appreciated. Thanks in advance and please be gentle :-].
EDIT
I have tried using a for loop, for (NSString *item in fetchedResultsController) but I get the error ...may not respond to 'countByEnumeratingWithState:objects:count'
It seems a loop of sorts is what's needed here, but again, nothing I can find is relevant or it's outdated. Again, thanks for any assistance.
Edit 2
Here is the original error I got when I ran a second separate fetchRequestController for this button/method:
An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (4) must be equal to the number of rows contained in that section before the update (4), plus or minus the number of rows inserted or deleted from that section (0 inserted, 3 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
You can just loop over the fetchedObjects collection and change the managed objects. After changing them you'll need to reload your list (I guess you use a tableview).
I don't know what your classes are named, but in general you can just loop over the collection of managed objects and change them. Remember that you need to save your managed object context if you want to keep these changes for when the app closes.
NSArray* myCollection = [[self fetchedResultsController] fetchedObjects];
for(ActiveListData *managedObject in myCollection)
{
if(managedObject != nil && managedObject.isChecked)
{
managedObject.isChecked = NO;
managedObject.isActive = NO;
}
}
If you want to do the check on all object in the database you'll need a new method in your NSFetchedResultsController that has a predicate checking on isChecked and then loops over and edits the result collection.
You might want to post your error code as we could be able to point out what you did wrong.
Edit: If you're not familiar with using Core Data the apple documentation provides a lot of information: http://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdBasics.html
Thanks to #ggfela for his answer. The processes of his answer were spot on. Here is the actual code I put into my button/method, in hopes of it helping someone else in the future:
NSArray *moc = [[self fetchedResultsController] fetchedObjects];
for (ActiveListData *item in moc) {
if (item != nil && item.isChecked.boolValue == 1) {
item.isChecked = [NSNumber numberWithBool:NO];
item.isActive = [NSNumber numberWithBool:NO];
}
}
// Call to Data Store to update the list
NSError *error;
if (![self.managedObjectContext save:&error]) {
FATAL_CORE_DATA_ERROR(error);
return;
Explanation:
Load the contents of the result from calling the fetchedResultsController method into a temporary variable named moc
Use a for loop to cycle through the array of moc. ActiveListData is the NSManagedObject subclass that I created for my Core Data and is the proper place to insert the separated values/attributes from the data store. From there, it's pretty simple, I ensure that item is not nil AND that the item's attribute is the value I need.
NOTE
Core Data does not store the bool values YES and NO but rather 1 and 0, respectively but when you call or compare the values, you simply can not compare the value of item.isChecked because it is being passed back to you as a bool not as an integer. You can not simply compare item.isChecked == YES either since the #property of isChecked is an NSNumber. So, in the case of the if I put item.isChecked.boolValue as this will give me the representing integer for it's bool value, in this case I have it check for a 1 (YES). (Sorry if my explanation is wrong and/or confusing, but this is how I understand it and is the only way this code works.)
Then, setting the new values of these attributes is like you would expect when setting any other variable. The only "tricky" difference with this is that the NSManagedObject subclass sets the #property of the isChecked and isActive to an NSNumber (as mentioned earlier) so in order to send the proper values back to Core Data you use the method numberWithBool of the NSNumber class.
And just in case anyone gets confused by my FATAL_CORE_DATA_ERROR(error) call this is simply a macro that was defined inside the Prefix.pch file to handle my errors from the managedObjectContext. You can use any (or none) error handling you choose.
Thanks again #ggfela for your help!! If anyone else has any other suggestions on how this code should be applied, then please let me know!
You can use NSBatchUpdateRequest to update multiple records
Examples:
https://www.bignerdranch.com/blog/new-in-core-data-and-ios-8-batch-updating/
http://code.tutsplus.com/tutorials/ios-8-core-data-and-batch-updates--cms-22164
Related
When I click a button, a UIAlertView prompts the user to type a name. This name is then created as a new 'Customer' object and inserted into a mutable array.
There is a separate mutable array called 'CustListByName', which stores a list of all names.
The problem im having is that when adding a second or third name, the app crashes. Sometimes it happens on the second try, other times on the third try. There is no information given in the debugger except for (lldb). The program reports EXC_BAD_ACCESS and then it dumps me to a screen with a bunch of assembly code.
The crash is happening in these lines of code:
Essentially, it clears the array of names and then repopulates it based upon the object array. I've studied in step by step with a breakpoint but everything seems correct up until the point of crash. It is also confusing why this happens on the second or third try, never the first.
[custListByName removeAllObjects];
for (Customer *object in custListByObject) {
[custListByName addObject:object->name];
}
Here is the code where a customer is created and inserted everytime the new customer button is clicked:
Customer *tempCust = [[Customer alloc] init];
tempCust->name =[[alertView textFieldAtIndex:0] text];
[custListByObject addObject:tempCust];
[tempCust release];
I would really appreciate help with this, thanks!
What I suspect is happening is that the UIPickerView is attempting to load a row using information from your customer array after you have already cleared it, and before you repopulate it. This would cause a bad access error.
What you may consider doing instead, is keeping two arrays, an NSMutableArray for loading the customers, and an NSArray as the actual data source for the UIPickerView. Then right before you reload the UIPickerView, you say:
dataSourceArray = [loadingArray copy];
[pickView reloadAllComponents];
Hopefully this helps.
Edit:
Here's what your updated code would look like if your loading array was called loadingCustListByName:
[loadingCustListByName removeAllObjects];
for (Customer *object in custListByObject) {
[loadingCustListByName addObject:object->name];
}
custListByName = [loadingCustListByName copy];
[pickView reloadAllComponents];
Doing this will ensure that the UIPickerView's datasource array always matches up with the number of rows it thinks it has.
I already have a tableView with data in it. IF you tap a cell/row it pushes to an edit type of view. Is there anyway to edit core data's data other than: By edit, i mean i already have data inserted into my context. I have loaded my data into my view, the user can change the existing data, and re save it.
.h
//Below is the entity/entity's class name 'Amm'
Amm *amm;
.m
-(IBAction)save
{
[self.amm setValue:self.nameField.text forKey:#"name"];
[self.amm setValue:self.nicknameField.text forKey:#"nickname"];
[self.navigationController popViewControllerAnimated:YES];
NSError *error;
if (![self.managedObjectContext save:&error]) {
//Handle Error
}
}
I want this code to work, however the design pattern of my app isnt allowing this code to work for me as it does in other parts of my app. Thank you very much for any and all help!
I assume from what you've said you have:
A table view listing your managed objects
A view where you can edit the values of a managed object
A save button bound to the save method
What's the actual issue? I'm assuming when you tap save that:
The values in self.nameField.text isn't setting self.amm.name
The values in self.nicknameField.text isn't setting self.amm.nickname
Is that right? If so perhaps try the following code to set the managed object values:
self.amm.name = self.nameField.text
self.amm.nickname = self.nicknameField.text
If that's not the issue and you are actually setting the managed object values properly, is it that you just need to refresh the table-view? Perhaps use some NSLog commands to log every step of the applications progress.
I am wondering if someone could clarify the following from Apples Core Data documentation:
Changes are not reflected until after the controller’s managed object
context has received a processPendingChanges message. Therefore, if
you change the value of a managed object’s attribute so that its
location in a fetched results controller’s results set would change,
its index as reported by the controller would typically not change
until the end of the current event cycle (when processPendingChanges
is invoked). For example, the following code fragment would log
“same”:
NSFetchedResultsController *frc = <#A fetched results controller#>;
NSManagedObject *managedObject = <#A managed object in frc's fetchedObjects array#>;
NSIndexPath *beforeIndexPath = [frc indexPathForObject:managedObject];
[managedObject setSortKeyAttribute:
<#A new value that changes managedObject's position in frc's fetchedObjects array#>;
NSIndexPath *afterIndexPath = [frc indexPathForObject:managedObject];
if ([beforeIndexPath compare:afterIndexPath] == NSOrderedSame) {
NSLog(#"same");
}
What exactly does "would typically not change until the end of the current event cycle" mean? I have this situation in my code but am not really sure if I can 100% rely on my indexes staying the same until I explicitely preform a save on my managed object context. Could the above code be modified, without performing a save to the context, such that it doesn't log "same"?
not really sure if I can 100% rely on my indexes staying the same
until I explicitely preform a save on my managed object context.
I wouldn't. The "current event cycle" is the current iteration of the run loop.
I'm currently using a singleton as a data store for my app. I essentially store a number of events that are pulled and parsed from a web service and then added as needed. Each time I make a request from the web service, I parse the results and see if the items already exist. If they do, I delete them and add the updated version provided by the web service.
Everything appeared to be working properly until I fired up the Instruments panel to find out that my system is leaking the objects every time it loads them from the web service (from the second time on). The core method where things appear to be messing up is this one, which is located in my HollerStore singleton class:
- (void)addHoller: (Holler *)h
{
//Take a holler, check to see if one like it already exists
int i = 0;
NSArray *theHollers = [NSArray arrayWithArray:allHollers];
for( Holler *th in theHollers )
{
if( [[th hollerId]isEqualToString:[h hollerId]] )
{
NSLog(#"Removing holler at index %i", i);
[allHollers removeObjectAtIndex:i];
}
i++;
}
[allHollers addObject:h];
}
Quick explanation: I decided to copy the allHollers NSMutableArray into theHollers because it's being updated asynchronously by NSURLConnection. If I update it directly, it results in a crash. As such, I switched to this model hoping to solve the problem, however the Instruments panel is telling me that my objects are leaking. All the counts are exactly the # of items I have in my data set.
From what I can tell removeObjectAtIndex isn't effectively removing the items. Would love to get the thoughts of anybody else out there on three things:
Is my analysis correct that something else must be retaining the individual hollers being added?
Should I be using CoreData or SQLite for storing information pulled from the web service?
Do you know how long data stored in a Singleton should be available for? Until the app is killed?
Update
I think I've found the source, however perhaps someone can provide some clarity on the proper way to do this. I've created a method called parseHoller which takes a dictionary object created through SBJSON and returns my own model (Holler). Here are the last couple lines:
Holler *h = [[[Holler alloc] initFromApiResponse:hollerId
creatorId:creatorId
creatorName:creatorName
creatorImageUrl:creatorImage
comments:comments
attendees:attendees
wishes:wishes
invitees:invites
createdAt:createdAt
text:text
title:title
when:when]autorelease];
//(some other autorelease stuff is here to clean up the internal method)
return h;
I figured that since I'm returning an autoreleased object, this should be fine. Do you see anything wrong with this?
Have you tried to do a retain count on the objects that is leaking? Maybe that could clear up when or where it is being retained.
The code should be
[putObjectHere retainCount];
and then write to an NSLog
Hope it gives you something
Peter
EDIT Thanks to Matt's post I now understand that I should not be trying to access 'started' as an array. However, if that is the case, I would like to know why this code appears to be working in other places. It still seems to me that this should be "one-or-the-other." It should work or it shouldn't.
ORIGINAL
I'm using the same fetch request in various parts of my code to find the most recent game:
Game *lastGame = [[[CoreDataAccess managedObjectContext] fetchObjectsForEntityName:#"Game" withPredicate:#"started == started.#max"] anyObject];
'Game' is an NSManagedObject and 'started' is a date attribute. 'started' is set exactly once per object in awakeFromInsert. It is never changed after that. Game is never directly instantiated, but it has three subclasses. I have tried making Game both abstract and concrete, but neither has any effect on this problem.
I'm using an NSManagedObjectContext category to perform the fetch, as shown on cocoa with love here http://cocoawithlove.com/2008/03/core-data-one-line-fetch.html.
The error I am getting is the following:
Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. [<__NSDate 0xebb1130> valueForUndefinedKey:]: this class is not key value coding-compliant for the key #max. with userInfo {
NSTargetObjectUserInfoKey = "2010-11-06 11:16:53 GMT";
NSUnknownUserInfoKey = "#max";
}
It looks to me like the predicate might be trying to apply #max to a single NSDate, instead of all 'started' attributes in all games. I'm not sure though. I'm not very good with predicates, and it took me a lot of trial and error to make this one. I don't understand how the exact same fetch can have errors in different places, though.
The fetch is not part of an NSFetchedResultsController, but I am using a fetchedResultsController in the class where I'm getting the error. For example:
- (void)configureCell:(UITableViewCell*)cell atIndexPath:(NSIndexPath*)indexPath{
Game *game = [self.frc objectAtIndexPath:indexPath];
Game *lastGame = [[[CoreDataAccess managedObjectContext] fetchObjectsForEntityName:#"Game" withPredicate:#"started == started.#max"] anyObject]; // Sometimes we get past this line, sometimes we don't...
NSDateFormatter *format = [[NSDateFormatter alloc] init];
[format setDateFormat:#"EEE, MMM d, yyyy h:mm a"];
if (game != lastGame)
cell.detailTextLabel.text = [format stringFromDate:game.started];
else
cell.detailTextLabel.text = #"In Progress";
[format release];
...
}
and also here:
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the specified item to be editable.
Game *lastGame = [[[CoreDataAccess managedObjectContext] fetchObjectsForEntityName:#"Game" withPredicate:#"started == started.#max"] anyObject];
if (lastGame == [frc objectAtIndexPath:indexPath])
return NO;
return YES;
}
This exact fetch is performed several times in several places, for example on startup, but it only crashes in one class. As I said it is intermittent, but it seems to be sometime after I create a new Game object. The game is created in one tab, and the code above is from a second tab which shows history.
I have seen a similar error here. In that case the problem was solved by restarting the computer, which finally allowed XCode to realize that the attribute had been deleted from the model. I have tried that, and I'm still experiencing the problem. I have also tried deleting and recreating the 'started' attribute in the model. I have also read the Core Data Troubleshooting Guide, but was unable to find any help there either.
Predicates are applied to one source object at a time. If your source object does not have an array property, you can't use an array operator.
In your case, the predicate says:
Look at a given "Game". If its own
"started" property is equal to its own
"started" property with the #max KVC
array operator applied, then this
predicate will be true.
This is not what you want. The only situation where you'd use a KVC array operator in a predicate is where a property on an object is an array. e.g.
Fetch every "Season" where the "games.#max.homeTeamScore > 50" (i.e. seasons where a home team scored more than 50 in a game). This would work because the "games" property on a "Season" would be an array, so games.homeTeamScore would also be an array.
However, the "started" property on a single Game is not an array. The array you want to operate on is actually the array of all games, which is not a property of a game.
The only twisted way you could access the array of all games would be to fetch the array of all games first, then apply the array operator outside the predicate and then on the inside of the predicate only apply the equality test.
i.e. fetch all games first, then refetch with:
fetchObjectsForEntityName:#"Game" withPredicate:#"started == %#", [allGames valueForKey:#"#max.started"]
But this is not the smart thing to do either.
Ultimately, the correct way to fetch the game with the latest starting date as you're trying to do, can't be done with the single line fetch method.
You'll need to create a variant of the method that allows you to setSortDescriptors: on the fetch request (to sort by "started", descending) and then setFetchLimit:1 on the fetch request to only get first result.