Where should I put my NSFetchedResultsControllerDelegate? - iphone

I am teaching myself to program by making a simple core-data drill-down app with a UINavigationController where you select a grandparent entity to see a UITableView of parents, and then select a parent to see children. Whenever the user selects an item, I use properties to hand over the NSManagedObjectContext and NSFetchedResultsController to the next view. Each view controller is a UITableViewController, and they all conform to the NSFetchedResultsControllerDelegate Protocol.
This works fine, but means every view controller is implementing the delegate methods etc., which seems inefficient.
To make the app simpler, would it be better to have a single NSFetchedResultsControllerDelegate that is referenced by all my view controllers? And where would the best place for this be - the app delegate?
Thanks!
---------------------------EDIT----------------------------
I'm trying to get GorillaPatch's answer to work below. In my child view, I have this method which is a delegate method for the modal "Adding View Controller":
- (void)addingViewController:(AddingViewController *)addingViewController didAdd:(NSString *)itemAdded
{
NSManagedObjectContext *context = [parent managedObjectContext];
Child *newChild = [NSEntityDescription insertNewObjectForEntityForName:#"Child" inManagedObjectContext:context];
[self.children insertObject:newChild atIndex:0];
newChild.name = itemAdded;
newChild.dateStamp = [NSDate date];
// Save the context.
NSError *error = nil;
if (![context save:&error])
{
// Handle The Error.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self dismissModalViewControllerAnimated:YES];
}
And there is the following in the header file:
#property (nonatomic, retain) Trip *trip;
#property (nonatomic, retain) Checklist *checklist;
#property (nonatomic, retain) NSMutableArray *checklists;

In my humble opinion, definitely no. In general, you need a different NSFetchedResultsControllerDelegate implementation for each view, because what you actually do in each delegate method may and will usually differ (unless your application is really simple).

I would recommend using a FetchedResultsController instance for each UITableView. The reason is because of the way that the FRC responds to changes of data.You can read about the FRC here: http://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html
If you set a delegate for a fetched
results controller, the controller
registers to receive change
notifications from its managed object
context. Any change in the context
that affects the result set or section
information is processed and the
results are updated accordingly. The
controller notifies the delegate when
result objects change location or when
sections are modified (see
NSFetchedResultsControllerDelegate).
You typically use these methods to
update the display of the table view.
Besides, if you're drilling down as you said, each new child view should be a unique set of data that would not allow you to share the FRC.

No, you'll probably be better off by writing a base class that implements NSFetchedResultsControllerDelegate (which would probably be a UITableViewController subclass) and contains a NSFetchedResultsController instance, and then extending your base class wherever you need it.
If you have multiple levels to drill down to, the most likely scenario is that the only thing that will change among your implementations is the predicate used to obtain your NSFetchedResultsController instance.

Why do you hand over the NSFetchedResultsController and the NSManagedObjectContext (MOC) to the child or detail view controller? I would strongly suggest defining a property on the detail view controller, which is the object you want to show.
For example if you have a list of recipes fetched from CoreData and you tab on a recipe, you would have a detail view controller sliding in which would show the recipe's details. I would suggest implementing it by having a UIViewController subclass which has a currentRecipe instance variable. You would then set this instance variable to the recipe which you tabbed in your list and then push the view controller on the stack.
By doing this you would decouple your user interface really nicely. This makes this view controller reusable in the whole program.
Update
Due to our lengthy discussion I would like to provide more material which could be helpful if you want to know more about MVC design patterns and how to implement a drill down navigation on the iPhone.
Sample code: Have a look at the iPhoneCoreDataRecipes app which was demoed on WWDC09 and WWDC10 to illustrate how to implement a stack of detail view controllers and how they interact with each other.
WWDC session videos: there are some WWDC session videos which could be helpful:
WWDC10: Session 116 - Model-View-Controller for iPhone OS
WWDC09: Session 125 - Effective iPhone App Architecture

To expand on the previous answers:
The NSFetchedResultsController is part of the controller layer of a Model-View-Controller app design. The name of the design should be Model-Controller-View because the controller mediates between the data model (Core Data in this case) and the view. As such the FRC has to be customized for the needs of each particular tableview whose data it fetches, sorts and manages. It properly belongs in the tableview's datasource delegate which is usually just the tableview controller object.
The design you are contemplating would only work if every single table used the exact same entity with the exact same sort order. In that case, why bother with multiple tables?

Related

Passing Data From Views in Core Data

Hello everyone — I am a beginner in iPhone programming and Core Data. I am currently trying to learn some of the theory behind Core Data, and have been using this tutorial to help me implement it in my app.
The tutorial teaches by making the main view a UITableViewController that lists the saved objects and another UITableViewController that saves objects (where you enter in the attributes).
The app that I am creating has 3 views. The main view is a plain UIViewController (it handles calculations), you are able to save your calculations by tapping a UIBarButtonItem that brings you to the second view where you enter in more specific attributes. Once you tap save, you are taken BACK to the main view, where you are able to tap a Show Saved button to access the UITableViewController containing saved objects.
I have included #imported the UITableViewController files into my main view's interface file, but when I run the program, I get an error on this line in my prepareForSegue method:
addShoeSizeTVC.managedObjectContext = self.managedObjectContext;
The error is "Property managedObjectContext not found on object of type 'SSFViewController*'" I understand the meaning of this error — I don't have any object called managedObjectContext in my SSFViewController class, but I figured that if I included my file that DOES contain managedObjectContext that it would still be recognized. I should add, that in the tutorial, the prepareForSegue method was contained in the list view for the segue to the add new object UITableViewController. I moved this method to my mainViewController.
I also get an error in my App Delegate in my ApplicationDidFinishLaunchingWithOptions method:
controller.managedObjectContext = self.managedObjectContext;
I understand that this stems from the same problem with the other error (it gives the same error message).
I do not understand how to pass data going from my viewA (mainView), to viewB (add object), back to viewA, then to viewC (view saved objects). I have heard about delegation and am using it in my prepareForSegue method in my SSFViewController main view:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"Add Object Segue"]) {
NSLog(#"Setting ObjectsTVC as a delegate of AddObjectTVC");
AddObjectTVC *addObjectTVC = segue.destinationViewController;
addObjectTVC.delegate = addObjectTVC.self;
addObjectTVC.managedObjectContext = addObjectTVC.self.managedObjectContext;
}
}
Also on the addObjectTVC.delegate = addObjectTVC.self; line I get a warning that says "Passing 'AddObjectTVC*' to parameter of incompatible type 'id'"
Do I have to set up an NSManagedContext or another delegation method in my main view? Or is it something that I must add to any of my Table views???
Thank you very much. I feel like this is a simple problem to solve, if provided with the right information. I am happy to post any other methods that I used if needed to solve the problem. I am a beginner, so it would be great if you could explain in a beginner-friendly way.
Thanks!
First of all, if you want data from ViewA to ViewB, insert a property in the ViewB and you can pass data from ViewA to this #property
Example
ViewB:
#property (nonatomic, strong) NSString *yourName;
(don't forget to call #synthesize yourName )
ViewA: (in prepareForSegue method)
"ViewB-Controller" *controller = segue.destinationViewController;
controller.yourName = self.name
--> name will be passed to ViewB
Second:
I prefer a delegate which send from ViewB to ViewA "Hey please save your data". It keeps your controller easy and smart, and you don't have to manage the save method from all view controllers. Delegate is an important chapter in iOS and it can be very frustrated for a beginner. (I was in the same situation 9 months before ;))
Search for a delegate example and try to understand how it works (learning by doing), if you have further question about delegate, I will friendly respond to your question.
It isn't the view controller that has the managedObjectContext property, but your UIManagedDocument.
The context is typically described as the 'scratch pad' in which your app will work with the data store.

Passing CoreData objects between views without managedObjectContext

Could you please help me with the following
I writing an app using CoreData and have two view controllers.
ControllerA is a table view and just lists the data
ControllerB is a view controller containing several UITextFields
To edit a row on ControllerA I set a property on ControllerB passing my CoreData entity model object
eg.
viewControllerB.item = [self.fetchedResultsController objectAtIndexPath:indexPath];
Once this is edited, the contents of item on ViewControllerB is passed back to ViewControllerA via delegate and saved.
This works fine, The bit i'm struggling with is the best way of adding a new item to CoreData without setting up the managedObjectContext on ViewControllerB.
I have tried passing a new object with
ItemData *item = [NSEntityDescription insertNewObjectForEntityForName:#"ItemData" inManagedObjectContext:self.managedObjectContext];
viewControllerB.item = item;
this works if I enter the form on ViewControllerB with data, but if I cancel the view and pop it off i'm left with a (null) object in CoreData.
I also tried instantiating the CoreData ItemData object using a standard init, but now understand this won't work because CoreData doesn't understand the context and also doesn't have getter/setters for the properties.
What is best practice for this situation?
1) Should I just pass managedObjectContext to viewControllerB?
2) Create a model class to hold the values from the form and pass that back and then deal with CoreData stuff?
3) or is there a correct way of doing what i'm trying to do?
I'm loosely following iOS Apprentice 2 to deal with Storyboards etc, but in this a model class is passed back and forth which I know works fine, its the CoreData side thats the problem.
I hope the above is clear and that the terminology i've used is correct.
Look forward to any help.
this works if I enter the form on ViewControllerB with data, but if I cancel the view and pop it off i'm left with a (null) object in CoreData.
If you close the view you need to delete the object if you don't want it to stay. So if someone hit's "abort" in on your view you should call
[self.managedObjectContext deleteObject:theManagedObject];
There are many possible ways of passing the managed object context between controllers. Your AppDelegate should have the managed object context as a property.
If controller a has a pointer to the context you could just pass this into an (nonatomic, weak) property of controller B.

iPhone: Which controller should handle the CRUD logic?

I'm building an app the works similar to the iPhone Notes app.
My app consist of two screens, first screen is a UITableView listing all the records. The second screen appears when you either click on one of the records or click an add button. This second view contains a UITextView where the user can add/edit the text for that record.
Both screens have a View Controller. The MyListViewController loads the records into the UITableView. When the user clicks on a record I create an instance of the MyEditViewController and push it using the pushViewController method of the Navigation Controller.
MyListViewController -> MyEditViewController
My question is which controller should handle the CRUD logic, should it be the parent controller (i.e. MyListViewController) or the edit controller (i.e. MyEditViewController)?
One thing to note is that you should be able to delete a record from MyListController by swiping a table cell and selecting delete.
You can should also be able to delete from the MyEditViewController by clicking on a delete icon.
I'm basically trying to duplicate the Notes app but am unsure what is best practice in terms of where the CRUD logic should go.
In the scenario that you describe, the best pattern to use, would be the delegate pattern.
Simply make a delegate for your MyEditViewController and make it's delegate your MyListViewController.
You define a delegate as a protocol. So in your MyEditViewController.h put in this:
#class MyEditViewController;
#protocol MyEditViewControllerDelegate <NSObject>
#required
- (void)myEditViewController:(MyEditViewController *)controller didSaveNote:(BOOL)save;
#end
and add this to your already existing MyEditViewController.h code.
#interface MyEditViewController : UIViewController
....
#property (nonatomic, retain) id <MyEditViewControllerDelegate> delegate;
#end
In your MyEditViewController.m code when you push weather the save or the cancel button you send the following message:
[self.delegate myEditViewController:self didSaveNote:YES]
or
[self.delegate myEditViewController:self didSaveNote:YES]
depending on you pushed save or cancel.
In your MyListViewController.h you adopt your newly created protocol like this:
#interface MyListViewController : UIViewController <MyEditViewControllerDelegate>
and in your MyListViewController.m you remember two things. First to implement the required delegate method:
#implementation MyListViewController
...
- (void)myEditViewController:(MyEditViewController *)controller didSaveNote:(BOOL)save
{
// Do business logic here depending on the value of save
}
and the last thing is setting your MyListViewController to the delegate of your MyEditViewController like this:
MyEditViewController *myEditViewController = [[MyEditViewController alloc] initWithNibNamed:#"MyEditViewController" bundle:nil];
[myEditViewController setDelegate:self];
That way you handle all CRUD logic in your MyListViewController and that way you can update the Table View accordingly.
I have recently developed an application that has very similar requirements. I think you should be very clear about your Model, Views & Controllers.
Model is the non-UI part of your application, the management of Notes in your case. I created a singleton object, say NotesManager, whose shared instance can be accessed from anywhere in my code. Something like [NotesManager sharedInstance]. In my application, the view controller does not read/enumerate the documents directory's contents (because thats not its job), the NotesManager does. The List view controller asks the notes manager for the notes to display. [[NotesManager sharedInstance] notesFromDocsDir];
Views are the UI part of your application. In this case, it would be the table view & the note's edit view.
Controllers are the ones that act as the link between your Views & Model. As you know, there is the ListViewController & the EditViewController.
Now, there are two types of interactions:
The first one originates from the UI & has to update the Model. For example, the user taps delete or save. In my application, i do something like [[NotesManager sharedInstance] deleteNote:Note]. You can do this from both the View controllers.
The second one originates from the Model end & updates your UI. For example, in my application, I have enabled iTunesSharing & hence a user can add/delete a note via iTunes. When such an event occurs, my UI has to update itself to reflect the current state of the documents directory. To accomplish this, the NotesManager dispatches an NSNotification. The Controller(s) registers for these notifications & updates the view.
Now for your original question, the CRUD methods reside in the NotesManager. They can be called by the Controllers or by NotesManager itself, when it detects something has changed.
HTH,
Akshay
Both. And neither.
You should use a model to store/provide the data.
ViewController's should control the views and pass instruction to the model to save changes etc.
I would do the business logic in the model - simply call the methods in the model from the viewcontrollers.
Seeing as your child viewcontroller is doing the editing it should be that which instructs the model in that instance.
The parent viewcontroller should instruct the model when you handle the deletion of the data.
I'd say that a good solution is to implement all these operations in your Model. Say, you have a class called Note that can handle CRUD operations. You will also need something like NoteCollection that will provide valid data for your table view.
MyEditViewController will always deal with a single note that should handle operations like save and delete. These should update its state in your note collection.

Accessing a view controller's function/variable from other view controllers

I'm currently making the initial menu view controller, which sets up the setting for main game view controller..
so in my menu game controller, I have
#import "MainGameViewController.h"
#implementation menuViewcontroller
......
-(void)setting{
NSMutableDictionary *regions =
[(MainGaimViewController *)self.delegate regions];
NSNumber *yesBool = [NSNumber numberWithBool:YES];
NSNumber *noBool = [NSNumber numberWithBool:NO];
[regions setValue:yesBool forKey:#"KM"];
[regions setValue:noBool forKey:#"KF"];
}
but this gives me the "Request for member 'delegate' in something not a structure or union
regions is a NSMutableDictionary in main game view controller.
so I think menuViewController is not being able to access the function/variable in main game view controller despite the import.
currently, I haven't declared "class MainGameViewController" in my menu implementation file. Could that be why? should I make an object of maingameviewcontroller and use it?
What could be wrong?
Please help me out..
I find it is much cleaner and clearer to put application-global data in separate singleton classes rather than wedge them in to the application delegate object and to try passing around pointers to root-level view controllers. View Controllers should focus just on the job of managing its view and responding to actions, and interacting with data models through the view. The data models themselves and app global data generally should sit outside of ViewControllers. The singleton pattern works nicely for data management, as the data is easily available to any piece of code in the app that needs it without having to worry about setting up delegate protocols, or whether a view controller owning data is valid any more.
You can see my answer to this question for how to set up a singleton data manager class:
Objective C: store variables accessible in all views

Using Interface Builder efficiently

I am new to iPhone and objective c. I have spent hours and hours and hours reading documents and trying to understand how things work. I have RTFM or at least am in the process.
My main problem is that I want to understand how to specify where an event gets passed to and the only way I have been able to do it is by specifying delegates but I am certain there is an easier/quicker way in IB.
So, an example.
Lets say I have 20 different views and view controllers and one MyAppDelegate.
I want to be able to build all of these different Xib files in IB and add however many buttons and text fields and whatever and then specify that they all produce some event in the MyAppDelegate object. To do this I added a MyAppDelegate object in each view controller in IB's list view. Then I created an IBAction method in MyAppDelegate in XCode and went back to IB and linked all of the events to the MyAppDelegate object in each Xib file.
However when I tried running it it just crashed with a bad read exception.
My guess is that each Xib file is putting a MyAppDelegate object pointer that has nothing to do with the eventual MyAppDelegate adress that will actually be created at runtime.
So my question is...how can I do this?!!!
If you create an instance of MyAppDelegate in each nib file then, yes, you do end up with a lot of different instances of the class when all the nibs load. The app delegate is not identified by class or even protocol but rather by being the object pointed to by the application instance's delegate property. To find the true app delegate, you have have to ask the application object itself for its delegate
You should have all your view controllers descend from a parent view controller class that has an appDelegate property. Implement something like this:
#import "MyAppDelegateClass.h"
#interface ViewControllerBaseClass :UIViewController {
MyAppDelegateClass *appDelegate;
}
#property(nonatomic, retain) *appDelegate;
#end
#implementation ViewControllerBaseClass
#synthesize appDelegate;
-(MyAppDelegateClass *) appDelegate{
self.appDelegate=(MyAppDelegateClass *)[[UIApplication sharedInstance] delegate];
return appDelegate;
}
#end
When the view controller needs the app delegate it just calls self.appDelegate. If you want to access an attribute of the app delegate use self.appDelegate.attributeName.
The important thing is that you ask the application for its specific delegate instance at runtime. You can't do that from a nib file.
I'm not entirely clear what exactly you're trying to do, but it's probably a bad idea. There should only be one app delegate per application, and it should deal with behavior for the whole application. Typically, the app delegate initializes the root view controller(s) and displays them, but not much else (other than handling things like opening and saving data sources).
The view controllers (subclasses of UIViewController) should interact with the XIBs. Having the view-specific behavior in the view controllers makes the app much easier to manage and maintain. Typically, there should be 0 or 1 XIBs per view controller (more than that is complicated). You set up the interaction with the views using the Target/Action pattern with IBOutlets and IBActions (see here for a complete guide). It's generally a bad idea to make view controllers or XIBs dependent on the app delegate (since reducing dependencies again makes the code easier to manage).
In general you should be making a view controller for each of the views you are building, and link events to those view controllers - not the app delegate. In fact usually no event ever is wired to the app delegate from any nib file, even in the sample projects you'll note that view controllers are created and held onto by the app delegate, but it does not receive events.