How to reflect the added entries on my UITableView? - iphone

I am using core data along with storyboards. I have 1 UITableView and 1 UIViewController. When I add entires to my entity it gets added in my context(database) but is not reflected in the table view when I go back. I have to run my app again and hen I can see my added entry. This is how I add new objects:
- (void)viewDidLoad
{
[super viewDidLoad];
myAppDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
self.managedObjectContext = myAppDelegate.sharedManagedObjectContext;
NSFetchRequest *personListRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *personEntity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:managedObjectContext];
[personListRequest setEntity:personEntity];
NSError *personListRequestError;
NSArray *personsList = [self.managedObjectContext executeFetchRequest:personListRequest error:&personListRequestError];
for(Person *thisPerson in personsList ) {
NSLog(#"First name is %#", thisPerson.firstName);
NSLog(#"First name is %#", thisPerson.lastName);
}
NSLog(#"the contents of array are %#",personsList);
personArray = [[NSMutableArray alloc] initWithArray:personsList];
}
I want my entries to be updated in my table when I add them, I don't want to run my app again to see them.

You use a fetched results controller to efficiently manage the results returned from a Core Data fetch request to provide data for a UITableView object.
While table views can be used in several ways, this object is primarily intended to assist you with a master list view. UITableView expects its data source to provide cells as an array of sections made up of rows. You configure an instance of this class using a fetch request that specifies the entity, an array containing at least one sort ordering, and optionally a filter predicate. NSFetchedResultsController efficiently analyzes the result of the fetch request and computes all the information about sections in the result set, and for the index. for more details apple doc and other sample

you need to tell the tableview to reload it's data:
[tableView reloadData];

Related

Loading core data into array, then populating table view, reordering array's objects doesnt persist

First let me tell you what im trying to do. Load data into array(from core data entity), populate table view,
if user wants, reorder cells and update the array.
Thats it.
I have found my problem, i just dont know how to fix it:
I am loading my Entities data/attributes into an array and populating my tableview with the data
(BELOW BEHOLDS THE PROBLEM):
-(void)viewWillAppear:(BOOL)animated{
if (self.context == nil)
{
self.context = [(RootAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"HandgunAmmo" inManagedObjectContext:self.context];
[request setEntity:entity];
NSError *error;
//PROBLEM!!! the 2 lines below this.
NSMutableArray *array = [[self.context executeFetchRequest:request error:&error] mutableCopy];
[self setTableArray:array];
[self.ammoTable reloadData];
[super viewWillAppear:YES];
}
SO at this point the table view is loaded with data (accounting for cellForRow being called)
The user moves a few cells around, and i update the array as follows:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
// fetch the object at the row being moved
NSString *r = [self.tableArray objectAtIndex:fromIndexPath.row];
// remove the original from the data structure
[self.tableArray removeObjectAtIndex:fromIndexPath.row];
// insert the object at the target row
[self.tableArray insertObject:r atIndex:toIndexPath.row];
[self.ammoTable reloadData];
}
As you can see the code for reordering the array should work.
But, in the viewWillAppear method, I am loading the entities attributes into the array again and using it to populate the table view which is the problem. When i update the array, its not updating the order of the objects inside of the entity. Does anyone know how to update that? I would really appreciate it!
Thank you!
The managedObjects represented in the array have no sense of their position in the array. Therefore rearranging their place is changing their visible position but not their position in the database.
If you want to sort then you need to do some things:
Have your NSFetchRequest include an NSPredicate that sorts on a sort field
Have your moveRowAtIndexPath method not only reposition the data but also update the sort field to reflect their new position
Save the updated records to the database so that the next fetch will have the correct sort.
If you already have a fetchResultsController you can forgo the array and just use:
NSManagedObject *ammo = [fetchedResultsController objectAtIndexPath:fromIndexPath];
To get an reference to the current object.

Xcode loading core data lag

I have 3 entities saved in core data. I am loading these in several view controllers in the app - sometimes loading data from all 3. Below is how I am loading this data and assign it to an array. Once it is in the array, then I sort, filter, count or whatever I need to do depending on the current page of the app.
if (managedObjectContext == nil)
{
managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
[request setReturnsObjectsAsFaults:NO];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil)
{
// Handle the error.
NSLog(#"mutableFetchResults == nil");
}
[self setEventsArray:mutableFetchResults];
The problems I am having are:
I don't like to have lots of duplicate code - and this is appearing on every view controller where core data is needed.
From one entity, I am saving binary data of images which is causing a lag when I load that data
So, is there a way to load from core data using conditions such as eventId = [NSString stringWithFormat:#"%#", currentEventId]
OR (and probably more suitable) have a separate class that loads the data when the app starts. And then I can access the classes arrays (of the loaded data) to use for the current page. And then just reload the data if I save, edit or delete an object.
Any help is much appreciated.
Fro your first question, you should look at MagicalRecord which brings Ruby on Rails' Active Record to CoreData. it will shorten clear your core data code.
Pay attention that if your images are not small you should store them on a separate entity with a relationship to your main entity. this should help you with the lag problem since you will load the image trough the relationship only when you will explicitly ask it to. You can see here the answer of Marcus Zarra (wrote a great book on core data). There is always an option that your images are too big for core data.
Hope it helps

Two Contexts, 1 Persistent Store: Duplicate Fetched Entries

I am attempting to create a way for users to import contacts to their phone. How it works is this:
There are two managed object contexts. The "real" context has the current data in their address book. The "other" context has incoming data from another source. Both share the same PersistentStoreCoordinator.
I match people by e-mail, so if a contact in the "real" context matches one in the "other", I don't save the other.
When I start the program, I have two entries in the "real" context that I can fetch fine.
Then, I import two other contacts an add them to the "other" context.
When I perform a fetch operation on the "other" context, I get FOUR results - two from the "real" context and two I just added to the "other" context.
However, when I merge the changes, my scheme for detecting duplicates works.
Is there something I'm missing with my understanding of Core Data? How can I make it so that my querying of the "other" context just returns the new results.
The full code is really long, but here's the important part:
AppDelegate *appDel = (AppDelegate*)[[UIApplication sharedApplication] delegate];
// Check to see the original data
NSManagedObjectContext *realContext = [appDel managedObjectContext];
NSFetchRequest *usersFetch= [[[NSFetchRequest alloc] init] autorelease];
[usersFetch setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:realContext]];
[usersFetch setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"email" ascending:YES]]];
NSArray *users = [realContext executeFetchRequest:usersFetch error:&error];
[usersFetch release];
NSLog(#"%#",users); // Returns 2 original objects already in database
otherContext = [[NSManagedObjectContext alloc] init];
[otherContext setPersistentStoreCoordinator:[[appDel managedObjectContext] persistentStoreCoordinator]];
for (contacts in fetchedData){
User *newUser = (User*)[NSEntityDescription insertNewObjectForEntityForName:#"User" inManagedObjectContext:otherContext];
newUser.email = fetchedData.email;
newUser.firstName = fetchedData.firstName;
// etc.
}
NSFetchRequest *newUsersFetch = [[[NSFetchRequest alloc] init] autorelease];
[newUsersFetch setEntity:[NSEntityDescription entityForName:#"User" inManagedObjectContext:otherContext]];
[newUsersFetch setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"email" ascending:YES]]];
NSLog(#"%#",[otherContext registeredObjects]); // 2 objects that were just added
NSArray *newUsers = [otherContext executeFetchRequest:newUsersFetch error:&error];
NSLog(#"%#",[otherContext registeredObjects]); // 4 objects - added AND original
NSLog(#"Count: %i",[newUsers count]); // Count: 4
I think you may be conceptually confusing the managed object contexts with the persistent store. You can have an arbitrary number of context attached to a particular store and the changes made in any single context will eventually show up in all the others.
This is especially true of fetches which go directly to the store to find objects. Your code is working as expected if you call a save on the other context. Once you save an object, it goes into the persistent store and will show up in all entity wide fetches.
You should not be creating managed objects that might have duplicates. Instead, the normal practice is to fetch on new values to see if they already exist and only create a new managed object with the value if the value does not already exist in the store. To make that fast, you can do a fetch for a specific property and see if anything is returned.

CoreData DetailTableView BAD_ACCESS Error

Maybe I'm not going about showing a detail for a selected row using CoreData, but I can't figure out why I'm getting a "BAD_ACCESS" error. I've googled around and can't find what I'm looking for.
Basically I use CoreData to populate the data for a Table View. It retrieves all of the title attributes for all of the entities. When the user clicks on a row, I have a Detail View that needs to show the description for that entity. I think I need to make a new NSManagedObjectContext and a new NSEntityDescription for a new NSFetchRequest in my DetailViewController and then use a NSPredicate to say "where title = [user selected title]". I get an error when I select a row. See code:
- (void)viewDidLoad
{
// Do any additional setup after loading the view from its nib.
// Get the objects from Core Data database
Caregiver_Activity_GuideAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Definition"
inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(title = %#)", self.title];
[request setPredicate:pred];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
if (objects == nil) {
NSLog(#"There was an error!");
// Do whatever error handling is appropriate
}
for (NSManagedObject *oneObject in objects) {
[definitionDescriptionTextView setText:[oneObject valueForKey:#"desc"]];
}
[objects release];
[request release];
[super viewDidLoad];
}
I comment out that code and everything works. But when I try to debug with breakpoints, nothing catches. So I'm more confused.
I know CoreData is probably overkill for what I'm doing but this is a learning app for me.
EDIT: I didn't include that I'm using a sqlite database that is pre-populated with the entities.
You can also download my project on my github page.
Normally, with a Core Data backed Master-Detail interface, you don't fetch for the Detail view.
When you select a row in the Master tableview, you are selecting a particular managed object instance. You then pass that managed object instance to the detail view. There is no need to refetch the object that you selected in the tableview.
A good example of this would be the Contacts app. The Master tableview would be a list of Contact objects (displaying the name.) When you select a row, the Master tableview controller takes the specific Contact object associated with the selected row and then passes it to the Detail view controller which then populates the Detail view using data taking from the properties of the passed Contact object.
So, that entire code block where the error occurs is unnecessary.
However, the immediate error in this code is that you are releasing an object you didn't create. In this line:
NSArray *objects = [context executeFetchRequest:request error:&error];
... you are not creating a NSArray instance with a init, new or create method. Instead, you are merely receiving an autoreleased NSArray instance created and returned by the context NSManagedObjectContext instance. When you release an object you did not create here:
[objects release];
... you cause the crash.
Conversely, you do create a NSFetchRequest here:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
... because you used init so you do have to balance that with:
[request relwase];
BTW, this type of code should not be put in viewDidLoad as the method is only called when the view is read in the first time from the nib file on disk. That is only guaranteed to happen once as the view may remain in memory when the user switches to another view. Instead, put code that needs to run each time the view appears in viewWillAppear.

Core Data object gets invalidated

I have a problem with some Core Data objects that are somehow invalidated.
The managed object context is in the app delegate and I use it in a view table to fetch 'notes' objects from a database and display them.
I build an array for the sections (today, yesterday, etc.) and for each section an array with the notes in the section like this:
// in the .h file
NSMutableArray* data; // An array containing an array of thoughts for each section.
#property (nonatomic, retain, readonly) NSManagedObjectContext* objectContext;
// in the .m file, when loading the view
ThoughtsAppDelegate* appDelegate = (ThoughtsAppDelegate*)[[UIApplication sharedApplication] delegate];
objectContext = appDelegate.managedObjectContext;
NSEntityDescription* descriptor = [[NSEntityDescription entityForName:#"Note"
inManagedObjectContext:objectContext] autorelease];
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:descriptor];
NSError* error;
NSArray* notes = [objectContext executeFetchRequest:request error:&error];
// example for one section
data = [[NSMutableArray alloc] init];
NSMutableArray* ccurrentSection = [[NSMutableArray alloc] init];
[data addObject:currentSection];
for(Note* t in notes)
[currentSection addObject:t];
When the view loads the first 5 notes are displayed (the rest don't fit in the view) and all is OK. But when I scroll down to view the next notes I get an
NSObjectInaccessibleException The NSManagedObject with ID... has been invalidated.
This happens for all objects in the array.
How is this possible? I checked and don't reset/release the context. Or is it bad to store a Core Data object and refer to it later?
Edit: this seems to happen also if I don't scroll and want to display details about a note when it's selected. Seems that as soon the first notes are displayed they're invalidated.
It would appear to be something with the way you manage the notes objects, but the code that is doing this is not in your example. The notes array is an autorelease array so unless you are retaining it somewhere it may be releasing before your load the next section from it.