How to refresh uitableview/nsmutablearray after adding object to underlying nsmanagedobjectcontext - objective-c

I have a basic UITableViewController that displays a list of schools, fetched from a simple two-entity core data repository. The tableview is bound to an NSMutableArray of schools that is loaded from an NSManagedObjectContext.
I added a "add new school" button which presents the user with a form for adding a new school. When the user adds the new school and clicks "save", I save the new school object to the NSManagedObjectContext and pop the "add school" view from the navigation stack. I am sent back to the original uitableview and the new school IS NOT in the list.
I know that I need to refresh the tableview, upon return, but am unsure how. NOTE: if I exit the simulator and run the program again, the new school record appears, so I know it is getting added properly to the underlying store.
Pretty sure I need to implement an NSFetchedResultsController but that seems like a TON OF CODE for what seems to be something quite straightforward.
If NSFetchedResultsController is the only way, can someone direct me to a tutorial or a code listing that may show this flow?
Many thanks.
Phil

Try reloading the data in your viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated {
[self.tableView reloadData]
}
PS: I'd still recommend you look in to using a fetched results controller. A lot of the code is boiler plate and you won't normally need to touch it.

Related

MagicalRecord: Create now, (possibly) save later

I've been using MagicalRecord quite a bit lately - and blimey it's been making my Core Data life much, much easier, so full credit to the author of what is a quality library!
Anyway, the scenario is I have navigation controller stack of 3 controllers, referred here from bottom to top as A-B-C:
View controller A is a form where I would like to fill in the details about the entity (i.e., its properties).
View controller B shows the result of a calculation from the entity (e.g., via an instance function).
For the sake of simplicity, view controller C is just a confirmation & is where I'd like to save the entity.
Is there a way in MagicalRecord I can call [MyEntity createEntity] in view controller A with its properties set, pass it through to C via B & only save it in C? This would also include the possibility of not saving it at all should the user decide to go back to A from B or C.
I fully appreciate I may well be getting the wrong end of the stick with Core Data & it may not be possible. As a workaround, already I know I can create a class method that does the same calculation given the relevant parameters & pass all the parameters through the stack from A to C.
Edit: Just to make it clear, I want to call [[NSManagedObjectContext defaultContext] save] in View Controller C.
yes sure.. just dont save the managedContext.
the VCs should run all on the main thread anyways.. so all can use
[NSManagedObjectContext defaultContext]
OR
pass the MOC between the three classes using a property on the controllers.
#property NSManagedObjectContext *context;
After some testing, I knew #Daij-Djan had the right idea in that I don't call [[NSManagedObjectContext defaultContext] save] if I don't want to save my entity. As a result I leave that call until View Controller C to do the saving.
However, my testing showed I need to do a little more to avoid saving any unwanted entities. I've noticed if I go to A from B via the back button, I want to discard the entity there. In my use-case, it doesn't matter if I just create another new entity going from A to B, and I never pass back through View Controller B if I have successfully saved the entity.
So basically I need to delete the unsaved entity if the back button is pressed on View Controller B. This answer helps me massively there, resulting in this code in View Controller B:
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self] == NSNotFound) {
// self.entity is the instance of my entity
[self.entity deleteEntity];
self.entity = nil;
}
[super viewWillDisappear:animated];
}

Magical Record, saving, and NSFetchedResultsController

Not sure if this is an issue with the way Magical Record does saves, or I'm just making an noob mistake somewhere.
I'm using an NSFetchedResultController (FRC) and UITableView to display a list of entities, when the user taps "Add" a new View Controller with an editor is pushed, a new entity is created with [MyEntity MR_createEntity]. The user can add additional entities here that are added to the main entity via a relationship. When the user taps "Save" in this View Controller the context is saved using [[NSManagedObjectContext MR_contextForCurrentThread] MR_save]
The NSFetchedResultsController appears to update, but when I tap to edit the entity none of the child entities are there. Debugging seems to show that even though the entity has been saved the FRC still has the entity with it's temporary ID.
I'm doing a naive [self.tableView reloadData] in the FRC controllerDidChangeContent delegate method.
Restarting the application loads the correct entities and the child entities are show properly in the editor view controller.
It looks like the FRC responds to the "main thread" save event, but the save is actually happening on a background thread so the FRC doesn't see it. I've checked and all "my" operations (setting up the FRC, creating and fetching entities) are all happening on the main thread context.
I tried listening for change notifications on MR_rootSavingContext and merging them with the main thread context, which sort of worked but I ended up with duplicates rows in the FRC (one was the correct "permanent" entity and one was the temporary one).
OK, I'm not sure if this is "the right way to do it" but I've found that it works correctly if I created my NSFetchedResultsController in the MR_rootSavingContext instead of the default context using the "inContext" version of MR_fetchAllSortedBy.
I guess this makes sense from the point of view that the FRC is now watching the rootSavingContext instead of one of it's children. Still, I would have thought since I'm doing all my operations on the same thread that wouldn't be an issue.
Update: The only gotcha with this approach is that if I just grab the entity using [frc objectAtIndexPath:] to give it to the editing view controller then it's no longer in the default context. Worked around this by re-fetching the entity in the default context using NSManagedObjectContext's existingObjectWithID. Still doesn't all feel quite right, but it's working for me.
Aware this is an old answer but none of the above worked for me and hopefully will help future readers
For me the problem was caused by trying to setup a purely local store using a sqlite file that had previously been used with iCloud.
Basically I tried to implement iCloud with my CoreData app, did the basic steps to set it up with an ubiquity container etc but then reverted back because of the inherent instability this seemed to cause (why is it that CoreData and iCloud STILL dont get along?!), but cocoa doesn't like you backtracking like that.
Fortunately I haven't done this in a live app so it's relatively easy to change as it will only affect development devices, but if you're moving from iCloud to a local store in a live app I think you might need to check out one of these solutions:
Migrating a Core Data Store from iCloud to local

Can anyone help out an Objective C novice?

I feel i am totally out of my depth here, im very new to objective c but have been asked to design an iphone app as part of my uni course. I designed a sinple quiz before, but I was hoping to design a more advanced quiz game with 3 levels (each level will have a different quiz).
I dont know how to use the UIViews and I have tried a tutorial online to help me code a navigation controller. It allows gives me 3 options to go into subview 1, 2 or 3. All the subviews have the same screen though, with one label and a button.
I have 3 classes so far, RootViewController, BasicNavigationAppDelegate and SubViewOneController.
I really dont understand the code at all, im familiar with Java but this objective c is nothing like it. Could someone maybe take a minute to help out a person in distress and explain if i am doing this right by using the navigation controller to display my levels? When i check the xib interface files i dont see the button or label, or dont know where to add the quiz interface objects!! I really am confused by all this. Could anyone help?
You should search google for sample source code, and see how some of the views can be handled. There are many ways you can handle a view, whether its by a UINavigationController, UITabBarController, etc. If you are new to Objective-C, then your not really going to get an answer to this question that will instruct you on what exactly to do.
Interface Builder + View Controllers
Here's a good one for you: View Controllers Programming Guide
(Apple's) View Controller Reference Guide
Some Code Samples
Getting Started Sample Code
I recommend Head First iPhone Development: A Learner's Guide to Creating Objective-C Applications for the iPhone. Within a few chapters you'll know everything you need to build this app and you'll actually understand what you're doing.
(I don't know the authors or the publisher, I just think it's a great book for quickly getting up to speed.)
for a 3 level quiz, UINavigationController is definitely an option.
if you need to find out how to use a class, in xcode, type its name, then press -alt- and double click the class name, this will bring up a short description, with two icons, one will take you to the header file, and the other to the documentation.
to add elements to the nib/xib files, you will need to open the library window, where you will find labels, buttons etc. to use a button, you will need to define an action in your header file, and hook it up in IB, to be able to interact with UIElements in your code, you want to set up outlets in the header file, and hook them up in IB.
something you need to decide on, is how you are going to present the questions, and will also depend if the answer will be true/false, multiple choice, or text entry.
if you arent familiar with obj-c and xcode, it is probably worth picking up an ebook from someone like http://www.pragprog.com. they have an iPhone one up there by Bill Dudney which is quite good(i believe he now works for apple.)
for the standard slide out transition you could use this.
//you would probably want to call this something like level1NavBarItemWasPushed: instead
- (IBAction)lvl1pushNavBarItem:(id)sender {
//create instance of AnswersViewController class.
AnswersViewController *level1AnswersVC= [[Level1AnswersViewController alloc] init];
//pass it some kind of identifier so it can tell which quiz/question it is dealing with and pull in the answers, so that you can reuse the view
[level1AnswersVC setAnswersObject:<<insert object dictionary here>>];
//push the view controller onto the navigationController's view stack
[self.navigationController pushViewController:level1AnswersVC animated:TRUE];
//pushing it onto the view stack has given it +1 retain, so we can now release it without worrying about it disappearing prematurely.
[level1AnswersVC release];
}
for the page flip transition you could use this.
- (IBAction)lvl1pushNavBarItem:(id)sender {
//create instance of AnswersViewController class.
AnswersViewController *level1AnswersVC= [[Level1AnswersViewController alloc] init];
//pass it some kind of identifier so it can tell which quiz/question it is dealing with and pull in the answers, so that you can reuse the view
[level1AnswersVC setAnswersObject:<<insert object dictionary here>>];
//set the current viewController as the delegate, so that it can call back to us when its done
level1AnswersVC.delegate = self;
//set the modal transition style
level1AnswersVC.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
//show answers as modal view, which has been setup to use the page flip transition.
[self presentModalViewController:level1AnswersVC animated:YES];
//pushing it onto the view stack has given it +1 retain, so we can now release it without worrying about it disappearing prematurely.
[level1AnswersVC release];
}

Get newest object added to NSFetchedResultsController?

Not even sure if this is feasible, but here's the use case:
I have a RootView tableview that uses NSFetchedResultsController to manage the list. I tap an add button, which presents the AdditionViewController. The AdditionViewController uses a separatemanagedObjectContext to create the new object. On Save, the object is passed back to the RootView, where the new object (in the new managedObjectContext) is merged into the main managedObjectContext. The AdditionViewController is then dismissed, revealing the RootView.
What I would like to do, is to push my DetailViewController with the new object loaded after the merge, so that when the AdditionViewController is dismissed, the full detail view is revealed.
How can I get the object that has just been added to the fetchedResultsController in order to pass it to the DetailViewController?
--UPDATE--
Still nothing on this. Let me try to explain what I need to do (hopefully) a bit better. If this is still confusing, ask questions. I'm having a hard time thinking of how to describe the problem.
I am fully aware how to push the Detail view underneath the modal addition view upon saving the new object. The problem is that the object I am saving is in its own fetchedReaultsController. I am merging this frc into the main fetchedResultsController, so if I try to sent the object to the detailview, I get a crash, because the object has been invalidated (due to the merge) by the time the modal addition view is dismissed, and the detailview calls viewWillAppear. That is what I am trying to get around. How can I figure out what object was just added to the main fetchedResultsController in order to send it to the detailViewController?
--UPDATE--
Adding a bounty for anyone who can tell me how to retrieve the most recently added object from a fetched results controller. Or how to retrieve a specific object from a fetched results controller without knowing it's indexPath.
Here's how I did it in an almost identical use case:
While the AdditionViewController is displayed, the user has the option of saving the item they created or cancelling out of the new item dialog. I communicated the user's choice back to the RootViewController.
If the user cancelled, remove the object you created from your context.
If the user chose to save, save the context and display the DetailViewController.
For the record, the answer was to grab the object ID of the object in the addingManagedObjectContext AFTER saving the context (since the ID changes after saving), and passing that ID to the main managedObjectContext after the merge. The full code required for this is below (if anyone has an easier way, let me know)
detailViewController.object = (customObject *)[[fetchedResultsController managedObjectContext] objectWithID:[[[[addingManagedObjectContext registeredObjects] allObjects] objectAtIndex:0] objectID]];
Thanks to frenetisch applaudierend for pointing me in the right direction.

What am I doing wrong with NSManagedObjectContext dependency injection?

I am trying to use NSManagedObjectContext dependency injection as recommended by Marcus Zarra -- I'm creating an M.O.C. in my AppDelegate and passing it as a retained property to each of my view controllers.
Generally this seems to work well, but in a modal table view controller that presents data via an NSFetchedResultsController, I only see what was in the database when the app was launched. That is, if the user adds data at runtime, it gets added correctly to the database, but does not appear when the modal ViewController is opened and the NSFetchedResultsController is created (using the injected NSManagedObjectContext). However, if I close the app and restart, then open the modal view controller, I do see the data added in the previous session.
Do I have to refresh the M.O.C. in some way prior to creating the NSFetchedResultsController? I am absolutely sure that the modal view controller and the NSFetchedResultsController are being created, and the fetch is being executed, AFTER the new user data has been entered.
To start, you should log the moc in both app delegate and your view controller to confirm that the moc in both places has the same address and is therefore the same object.
If it is, then most likely you've got an issue with the FRC's cache. Set the cache to nil and/or refresh the cache and see if that resolves it.