In my app I have a table view controller with two buttons. One adds an item, and the other edits the table view. When I add an item, the console says that it has added an item, but it's not until i restart the simulator to see that items are being added to the table view. When I add an item I make sure to reload the tableview like so:
- (IBAction)addNewItem:(id)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
Item *itemData = [NSEntityDescription insertNewObjectForEntityForName:#"Item" inManagedObjectContext:context];
[itemData setValue:userText.text forKey:#"name"];
NSError *error;
if (![context save:&error])
{
NSLog(#"Couldnt find the save %#", error.localizedDescription);
}
else
{
NSLog(#"It saved properly");
}
[self.tableView reloadData];
}
I just don't understand what the problem could be...
All help is appreciated,
Thanks in advance
The tableView keep to show the old fetch result after the new item has been added. You need to fetch the data again after item has been added and then call the [self.tableView reloadData] method.
Related
Hi I am trying to implement a search bar function for a table view using core data and NSFetchedResults Controller.
Quite a number of answers on SO suggest using a predicate for search using something like the following code:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
if ([searchText length] == 0) {
_fetchedResultsController = nil;
}
else {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name contains[cd] %#", searchText];
[[_fetchedResultsController fetchRequest] setPredicate:predicate];
[[_fetchedResultsController fetchRequest] setFetchLimit:50];
}
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[[self tableView] reloadData];
}
I have also tried variations of this. In every case, however, I'm having the same problem.
First, when you search, it says no results. Also if you type in a few letters and hit the x, you get a grayed out screen and if you click again, you get the normal table view.
However, after typing in a letter and seeing "No Results" if you click cancel you get the results you should have gotten. Once that happens you cannot get back to the full tableview without rebuilding the project.
The only idea I have so far is it might have something to do with _fetchedResultsController vs fetchedResultsController (the property in this source file is ftchedResultsController without the underscore) but changing those only throws error messages.
Thank you for any suggestions on what could be causing this.
You have two table views, one from the search and the main one from the main table. You should disable the search controller's table view if you want to keep showing your main table view with the filter implied by the search.
self.searchResultsController.active = NO;
You could set this every time the search bar would otherwise set this to YES, like in textDidChange. In your FRC getter, you check for any string in the search bar and add a predicate if necessary. In textDidChange you nil out the FRC and reloadData. That's it.
I have some really mysterious behaviour with CoreData.
I'll add an Object. I save this object. I fetch the new results and reload the collection view (from which is display the objects). The new object shows up. Hoorah! Just as expected.
I do this a second time, but every time from now (unless the app is restarted) when re-fetching the data from my NSFetchedResultsController and reloading the collection view, the new object doesn't appear.
Equally, if I delete an object. First time, A-OK! The next time I do this, the app actually crashes with the following error:
(Aircraft is my NSManagedObject)
Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xd0000000000c0000 <x-coredata://C418948D-90CD-40E9-A502-C4CAB0134419/Aircraft/p3>''
*** First throw call stack:
(0x18b79f09c 0x197ad5d78 0x18b4a77ac 0x18b4a6cac 0x18b4a6b00 0x100034438 0x18e6d8a44 0x18e6d6dc0 0x18e6d2e44 0x18e66ed78 0x18e26b0cc 0x18e265c94 0x18e265b4c 0x18e2653d4 0x18e265178 0x18e25ea30 0x18b75f7e0 0x18b75ca68 0x18b75cdf4 0x18b69db38 0x19106f830 0x18e6dc0e8 0x1000217dc 0x1980bfaa0)
libc++abi.dylib: terminating with uncaught exception of type _NSCoreDataException
Time for some code. I can't see any issues, but here it is. I won't spam you with everything, but if something rings any alarms, I can always add it on request.
Starting with the main view controller. This contains my collection view. Just as a note, it has two sections each fetching data from an individual NSFetchedResultsController. I am only seeing the issue with this specific one though. Fairly standard fetched results controller.
- (NSFetchedResultsController *)aircraftFetchedResultsController
{
if (_aircraftFetchedResultsController != nil) {
return _aircraftFetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Aircraft" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Set the batch size to a suitable number.
[fetchRequest setFetchBatchSize:50];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray *sortDescriptors = #[sortDescriptor];
[fetchRequest setSortDescriptors:sortDescriptors];
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:#"Master"];
aFetchedResultsController.delegate = self;
self.aircraftFetchedResultsController = aFetchedResultsController;
NSError *error = nil;
if (![self.aircraftFetchedResultsController performFetch:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _aircraftFetchedResultsController;
}
Anywhere I use an NSManagedObjectContext I am getting it from my AppDelegate. When adding the new object, the user is in a modal (form sheet) view controller. I create a new object, but do not insert it immediately, incase the user cancels:
SLAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Aircraft" inManagedObjectContext:managedObjectContext];
self.aircraft = [[Aircraft alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
Then, when done, save the object:
SLAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
//Only need to insert the new object if its 'NEW' else just save the existing one we are editing
if (!isEditing)
{
//Create new aircraft
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
//We are definetly saving the object, so now we insert it
[managedObjectContext insertObject:self.aircraft];
}
//Save
[appDelegate saveContextWithCompletionBlock:^(BOOL didSaveSuccessfully) {
if (didSaveSuccessfully)
{
[self dismissViewControllerAnimated:YES completion:^{
[delegate addAircraftDidSave:YES];
}];
}
else
{
[self dismissViewControllerAnimated:YES completion:^{
//ALERT with error
}];
}
}];
I use a delegate to send a message back to the main view controller saying the object has saved. That method then fetches the new data and reloads the collection view to show the new object:
-(void)fetchAircraft
{
NSError *error;
if (![[self aircraftFetchedResultsController] performFetch:&error])
{
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
[UIAlertView showGenericErrorAlert];
}
//Success, we have results
else
{
[self.collectionView reloadData];
}
}
Done. As I said, this works first time, then start acting up. Equally, you can substitute the save code for the delete code I have, fairly similar, delete and save changes:
SLAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;
[managedObjectContext deleteObject:self.aircraft];
[appDelegate saveContextWithCompletionBlock:^(BOOL didSaveSuccessfully) {
if (didSaveSuccessfully)
{
[self dismissViewControllerAnimated:YES completion:^{
[delegate addAircraftDidSave:YES];
}];
}
else
{
//ALERT with error
}
}];
(From my above comment:) The two fetched results controllers must use different
caches (cacheName: parameter). I also think (but I am not 100% sure about that)
that without sections, a cache does not give any advantages, so you can also
try cacheName:nil.
I believe you'll need to use separate ManagedObjectContexts for saves on a background thread.
https://developer.apple.com/library/ios/documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html
I have an iOS app that utilizes RestKit 0.20.1 to retrieve data from a Restful web service. I should also add the app uses CoreData. When the app is started the main screen is a collection view that is populated by a default search term. There is also a textfield in the header that is used as a search bar.
I am having trouble clearing out the previously loaded cells when the user uses the search bar. It just loads the new cells and pushes the previous ones down. Here is the applicable code.
- (BOOL) textFieldShouldReturn:(UITextField *)textField {
//This sets up the NSDictionary for the Restkit postObject parameters
NSArray *objects =[NSArray arrayWithObjects:textField.text, nil];
NSArray *keys =[NSArray arrayWithObjects: #"query",nil];
NSDictionary *params = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
self.search=params;
//This is the POST request to the server
[[RKObjectManager sharedManager] postObject:nil path:#"/rest/search?ip=255.255.255.0" parameters:search success:nil failure:nil];
//This is what I thought would clear out the old and replace with the new
[self.collectionView reloadData];
[textField resignFirstResponder];
return YES;
}
I referenced this question How to remove all items and add new items to UICollectionView? and [collectionView reloadData] was the accepted answer.
I chose the textFieldShouldReturn: method from this tutorial.
I have also referenced Inserting, Deleting, and Moving Section Items in the apple developer library but I'm not quite sure how to implement the delete items methods.
Any help would be great. This is clearly a rookie question so code snippets are VERY helpful.
UPDATE:
Here is how I got it to work.
Added a call to the deleteAllEntitiesForName method shown below before the postObject method and [self.collectionView reloadData] statements in the code above.
- (void) deleteAllEntitiesForName:(NSString*)entityName {
NSManagedObjectContext *moc = [self managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:entityName inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array != nil) {
for(NSManagedObject *managedObject in array) {
[moc deleteObject:managedObject];
}
error = nil;
[moc save:&error];
}
}
I got it from here: Correct way to refresh Core Data database
From experience with UICollectionView (not with RestKit), you probably need to clear out your data source before calling reloadData. The reason you're having this effect is that the source that you use to determine the number of items and sections in the collection view still has old data in it.
A simple way to verify this is to NSLog the contents of the data source just before calling reloadData.
Hope this helps!
If you are using NSMutableArray then you can simply do:
[array_name removeAllObjects];
Do this before adding new objects to the array.
I am using Core Data in a simple table view app which keeps track of a user's exercises. When one record is deleted, the deletion persists across that launch of the app. However, if I close the app and re-open it, the record re-appears. The user can delete the record from a detail view:
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString *title = [alertView buttonTitleAtIndex:buttonIndex];
if([title isEqualToString:#"OK"])
{
//delete record from database...
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"Parameters" inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name like %#", parameters.name];
[request setPredicate:predicate];
NSError *error;
NSArray *matchingData = [context executeFetchRequest:request error:&error];
for (NSManagedObject *obj in matchingData) {
[context deleteObject:obj];
}
[self dismissViewControllerAnimated:YES completion:nil];
}
else if([title isEqualToString:#"Cancel"])
{
}
}
I can not figure out why the deleted record re-appears. This occurs on the simulator and on the device. I 'reset content and settings' on the simulator but still no luck.
Any suggestions are greatly appreciated. Thanks in advance.
Context is a "scratch pad". The "scratch pad" concept allows you to made changes locally in that "scratch pad", such as modifying the record or deleting the record, and discard the changes if you decide too.
You can also have multiple contexts or "scratch pads", usually one context in each thread, such as one for main thread, and another one for background thread.
Now, if you have decided that the changes are OK, you need to persist the changes by saving the context.
NSError *error = nil;
[context save:&error];
Of course, if you have other "scratch pads" in your app, you will need to sync those changes you have saved in the core data.
Use NSManagedObjectContext's save: method after you're finished doing changes to your Managed Object Models
Hope this helps!
I've seen a bunch of responses on similar threads but after following their recommendations am still having a bizarre issue and am banging my head against the wall.
I have a table of people and have the option for the user to add an entry for another user by filling out a form. That constructor is shown here:
-(id)initWithContext:(NSManagedObjectContext *)context {
self = [super initWithNibName:#"FamilyMemberInfoViewController" bundle:[NSBundle mainBundle]];
if (self) {
self.managedObjectContext = context;
mainUser = [NSEntityDescription insertNewObjectForEntityForName:#"FamilyMember" inManagedObjectContext:context];
Details *userDetails = [NSEntityDescription insertNewObjectForEntityForName:#"Details" inManagedObjectContext:context];
mainUser.details = userDetails;
userDetails.familyMember = mainUser;
...etc.
and then if the user clicks the back button I call:
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
...
[self.managedObjectContext rollback];
[mainUser release];
self.managedObjectContext = nil;
...
And when it goes to the parent table view controller, it reloads the data with the below code and, as expected, the object is gone.
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if (viewController == self) {
NSFetchRequest *familyMemberRequest = [NSFetchRequest new];
NSEntityDescription *familyDescription = [NSEntityDescription entityForName:#"FamilyMember" inManagedObjectContext:self.managedObjectContext];
familyMemberRequest.entity = familyDescription;
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"isMainUser == 0"];
[familyMemberRequest setPredicate:predicate];
NSError *error;
self.familyMembers = [managedObjectContext executeFetchRequest:familyMemberRequest error:&error];
NSLog(#"Retrieved family members! Count = %i", self.familyMembers.count);
[familyMemberRequest release];
}
}
HOWEVER - when I click away from the tab with that tableView and back to it, it runs the same code again and finds the cancelled object in the managed object context.
But - then when I re-run the app, it is nowhere to be found.
If I try creating another new person and backing out, it shows the correct number of entries initially (without any of the cancelled objects), and then when I click to another tab and back it shows only the most recent cancelled object in addition to the ones that should be there.
I've tried all manner of clearing the managed object context in the FamilyMemberInfoViewController: [context reset], [context rollback], [context deleteObject:], [context processPendingChanges] etc. I've tried experimentally loading the familyMembers array twice, and even when I click the back button it still finds the correct number of objects, but when I click a different tab and then back it shows the extra object (which then continues to show up if I go there and back).
Any ideas?