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.
Related
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.
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?
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
My question is about the best way to implement passing of information between UIViewControllers.
If I have an application with 2 ViewControllers and for example a user selects an item in ViewControllerA which should then show the item and more details in ViewControllerB.
What would be the best way to implement this? via the appdelegate? or by passing a reference to ViewControllerA into ViewControllerB? Appreciate any help or examples of the best way to do this.
ViewControllerA (VCA) would maintain a reference to ViewControllerB (VCB). VCB would maintain a reference to the selected object as an ivar. When the user chooses an object in VCA, VCA instantiates VCB (if not already instantiated), sets VCB's selectedObject property to that object, and then pushes VCB. VCB reads from the object assigned to its selectedObject property to draw its information into the view.
In VCA, for every one of the "items" that the user can select, there needs to be an underlying object instance backing that item. For example, a UITableView might be backed by an NSArray of Vegetable objects if the user is selecting from a list of vegetables.
In general, try to keep data sharing between controllers to a minimum. Have them refer to model objects instead to get their data.
Try using the MVC design pattern. Put all shared state information into a Model object (M of MVC) created at a higher or the top level of your app. When creating your two view controllers, give them access to the model object (by setting a property in each view controller). Then the view controllers can store and access any shared state required, and you will have it nicely centralized for debugging, storing, extensibility, reuse, etc.
I have following problem:
I have built a tabbar application with 4 tabs. I want to pass a object/variable from the first tab controller to the third one and initialize this controller with the corresponding object.
I've already done some research. The best way, corresponding to a clean model approach, would be to call some initWithObject: method on the called viewcontroller.
How can I achieve this? How can I call the init method of the receivercontroller within the callercontroller? Can you give me some code example?
Edit:
To pass data between several views/classes etc simply create some Kind of data class which holds the data beeing shared between several classes. For more information follow the link:
Singleton
You need a data model object that stores the data for application.
A data model is a customized, standalone object accessible from anywhere in the application. The data model object knows nothing about any views or view controllers. It just stores data and the logical relationships between that data.
When different parts of the app need to write or read data, they write and read to the data model. In your case, view1 would save its data to the data model when it unloads and then view2 would read that data from the data model when it loads (or vice versa.)
In a properly designed app, no two view controllers should have access to the internal data of another controller. (The only reason a view controllers needs to know of the existence of another controller is if it has to trigger the loading of that other controller.)
The quick and dirty way to create a data model is to add attributes to the app delegate and then call the app delegate from the view controllers using:
YourAppDelegateClass *appDelegate = [[UIApplication sharedApplication] delegate];
myLocalProperty = appDelegate.someDataModelProperty;
This will work for small project but as your data grows complex, you should create a dedicated class for your data model.
Edit:
To clarify for your specific case, you would add the call to the data model when the receiver viewController becomes active.
Placing the data in an init method or a viewDidLoad won't work because in a UITabBar the users can switch back and forth without unloading the view or reinitializing the view controller.
The best place to retrieve changing data is in the viewWillAppear controller method. That way the data will be updated every time the user switches to that tab.
You might want to consider NSNotificationCenter (Reference); you register the one viewcontroller with the application notification center, and send a notification when a selection is made. When the notification is received, the other viewcontroller updates itself accordingly.
I don't think this is best practice (also check syntax) however I have got away with:
in the .h
otherclassref *otherclassname
#property (assign) otherclassname otherclassref;
and in the .m
#synthesize otherclassref;
then I just assign the reference from somewhere convenient e.g. the app delegate or wherever you are instantiating your viewcontrollers.
then the view controller can get a reference to the other view controller.
I add #class secondviewcontroller to the .h file for the firstviewcontroller and put put the #imports "secondviewcontroller.h" in the .m file of the first view controller. These are called forward references and prevent compiler errors resulting from having .h files referencing each other.