I´m completely new to core data programming. i just try to find out where the best place for implementing the core data code would be. i´ve done the apple tutorial Locations and it worked well. now i try to transfer that to my current project what is a bit more complex.
the Locations tutorial shows one RootViewController including a programmatically generated tableView. my project is based on a tabView template. it owns a MainWindow.xib including the TabBarController including three ViewController (MapView, ListView, SettingsView) where each view has it´s own navigationController and xib-file.
The first stumbling block was changing the code that it will run with a xib for the tableView instead of creating it programmatically. I´ve managed that but there is still one error. I can´t connect the managedObjectContext from the appDelegate to the listViewController. I´ve tried the examples and suggestions for that issue from this forum here. but it still doesn´t work.
after looking at the CoreDataBooks sample project i´ve seen that the core data code was implemented in the RootViewController as well. Seems that it would be the wrong way to implement it in the ListViewController. But i don´t have a RootViewController in my project. In the AppDelegate i directly pass the tabBarController as the rootViewController. therefore i don´t know how to reach the listViewController to set the context like it was done in the Locations sample.
As the MapView is the first view i can´t set the context in the appDelegate. And after struggling a long time with the managedObjectContext i wonder if it would be better to invent a RootViewController to be able to place additional code there. the model should be accessible by all three views and it seems that the RootViewController is the right place.
But how do i combine that with a tabBarController which includes three more viewControllers based on xib-files? Could somebody recommend me examples or tutrials including core data based on a tab bar app?
Please read the following article by Marcus Zarra: Passing around a NSManagedObjectContext on iOS. That should give you an idea how to solve your problem.
In general you should add a NSManagedObjectContext property to all of your ViewControllers and pass the context before adding them to the view stack via pushViewController:animated:. You should not take the context from your app delegate.
If you pass a single NSManagedObject to a ViewController, e.g. to present a kind of detail view, you can access the context from that object, as every NSManagedObject knows about the NSManagedObjectContext it is "living" in.
If you are a registered iOS developer, I'd also recommend the WWDC 2010 and 2011 videos. There are some sessions about mastering Core Data.
ok, now i have the correct solution. it took a while to understand but now it works with dependency injection from application delegate into the view controllers (listViewController).
my problem was that i didn´t know how to reference my view controllers as they are nested into dedicated navControllers and one tabBarController.
after reading a lot of postings here i understood i have to declare my view controllers in the appDelegate.h and synthesize them in appDelegate.m and after that connect them to the appropirate item in IB. that was done fast & easy after understanding :-)
there is no rootViewController needed.
MyAppDelegate.h:
#import <UIKit/UIKit.h>
#import "ListViewController.h"
#interface MyAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
UIWindow *window;
UITabBarController *tabBarController;
IBOutlet ListViewController *listViewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
#property (nonatomic, retain) IBOutlet ListViewController *listViewController;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;
#end
MyAppDelegate.m:
#import "MyAppDelegate.h"
#import "ListViewController.h"
#implementation MyAppDelegate
#synthesize window=_window;
#synthesize tabBarController=_tabBarController;
#synthesize managedObjectContext=__managedObjectContext;
#synthesize managedObjectModel=__managedObjectModel;
#synthesize persistentStoreCoordinator=__persistentStoreCoordinator;
#synthesize listViewController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
// Handle the error.
}
// Pass the managed object context to the view controller.
listViewController.managedObjectContext = context;
// Override point for customization after application launch.
// Add the tab bar controller's current view as a subview of the window
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleBlackOpaque animated:NO];
return YES;
}
...
ListViewController.h
#import <CoreLocation/CoreLocation.h>
#interface ListViewController : UITableViewController <CLLocationManagerDelegate> {
UINavigationController *navController;
NSManagedObjectContext *managedObjectContext;
}
#property (nonatomic, retain) IBOutlet UINavigationController *navController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
-(NSManagedObjectContext *)managedObjectContext;
#end
ListViewController.m
#import "MyAppDelegate.h"
#import "ListViewController.h"
#implementation ListViewController
#synthesize navController;
#synthesize managedObjectContext;
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"managedObjectContext: %#",[self managedObjectContext]);
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"error: %#",[self managedObjectContext]);
return;
}
...
I've coded an app like that some time ago. The way I've solved it is I made a singleton which had a persistentStoreCoordinator property like the one in Apple documentation to hold the access to the database (so I don't have to write it every time). Then in every tab bar view controller I've initiated its own NSManagedObjectContext.
NSPersistentStoreCoordinator *coordinator = [[Singleton sharedSingleton] persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator: coordinator];
}
That way every controller approaches the database with it's own context, if you understand what I mean.
Please note that if any of your view controllers has a detail view controller, take the standard approach of passing the managed object context to it like in sample code (Books, Locations, Recipes).
i just fixed the bug. i missed out some methods necessary in the appDelegate. it works now if i put following code into viewDidLoad of my ListViewController
if (managedObjectContext == nil) {
NSLog(#"managedObjectContext is nil");
managedObjectContext = [(IntraAppAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
is that OK concerning proper MVC pattern rules? in my case the ViewController takes the context from the appDelegate now.
trying to set the context in the appDelegate with something like that throws an error:
NSManagedObjectContext *context = [self managedObjectContext];
if (!context) {
// Handle the error.
}
// Pass the managed object context to the view controller.
self.tabBarController.listViewController.navController.managedObjectContext = context;
self.window.rootViewController = self.tabBarController;
how can i gather the reference of other viewControllers which are controlled by the tabBarController and are not the topView/superView after app start? the first view is the MapView. do i have to instantiate or declare the listViewController in appDelegate? how must it be coded that it referes to the listViewController controlled by the tabBarController?
Related
I am working on a simple core data app that uses a tableview and a detail view. I am getting the error message stating that the property managedObjectContext is not found in the object type ChildrenTVC. The problem is that it really is there. I have cleaned the project and deleted the derived data. There must be something else going on.
Here is the code for the object header:
#interface ChildrenTVC : CoreDataTableViewController <AddChildTVCDelegate>
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
#end
and the code for the implementation file:
#import "ChildrenTVC.h"
#implementation ChildrenTVC
#synthesize managedObjectContext = _managedObjectContext;
#synthesize fetchedResultsController = _fetchedResultsController;
Here is the app delegate file where the error registers:
#import "AppDelegate.h"
#import "ChildrenTVC.h"
#implementation AppDelegate
#synthesize window = _window;
#synthesize managedObjectContext = __managedObjectContext;
#synthesize managedObjectModel = __managedObjectModel;
#synthesize persistentStoreCoordinator = __persistentStoreCoordinator;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: ( NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
ChildrenTVC *controller = (ChildrenTVC *)navigationController.topViewController;
controller.managedObjectContext = self.managedObjectContext;
return YES;
}
The error is at the controller.managedObjectContext. A lot of this is boilerplate code so there is not much to it. I just can't figure out why it is not seeing the property for the managed object context. The property is in the code.
UPdate:
I ended up recreating the file entirely. I am not sure what references are still in place when a file is changed, but something was pointing to the wrong file. I had to empty the trash to get the new file work properly. It seems to work now, though. All the research I did ended up revealing that there are some things that are unexplained in Xcode.
I had solved this issue by entirely recreating the header file, but I never really understood why it happened. What I did not understand then were the effects of the changes that I had made to the app. The real problem was an issue in the sequence that the header files were imported. I had changed the #import in two implementation files and that caused the compiler to not read one of the header files. I could see that the code was there, but the compiler could not read the code because it was not importing it. That gave way to the error I was receiving. My solution simply reversed the change that I had made. It would more easily have been solved by simply removing the #import of the view controller header file on the app delegate. I recently tested this and it was the correct solution.
You are type casting the navigationController.topViewController but i assume it isn't one in reality. Try this
ChildrenTVC *controller = [[ChildrenTVC alloc] init];
NSArray *vcArray = NSArray *vcArray = [NSArray arrayWithObject:controller]:
[self.window.rootViewController setViewControllers:vcArray animated:NO];
What happens if you make following changes to ChildrenTVC.h
#interface ChildrenTVC : CoreDataTableViewController <AddChildTVCDelegate>
{
NSManagedObjectContext *managedObjectContext;
NSFetchedResultsController *fetchedResultsController;
}
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
#end
This is probably a noob question but can't get my head around it.
How do i make a connection between 2 viewcontrollers or a view controller and my appdelegate?
what i usually do is add the following to my app delegate "h" file
#class RootViewController;
#interface TabBarWithSplitViewAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
RootViewController *rootViewController;
}
#property (nonatomic, retain) IBOutlet RootViewController *rootViewController;
#end
and then create a connection in the Interface Builder. from my root view controller to the app delegate and automatically tells me thats the rootViewController that i added above.
and if you do this on the app delegate "m" file:
#import "RootViewController.h"
NSLOG(#"Controller %#",rootViewController);
it gives you a bunch of numbers indicating that there is a connection
But as you know with xcode 4 this changed since you usually no longer have the main.xib where you can create the connection, you do almost all those connections programatically.
i`ve tried everything from using the same code without the "IBOutlet" to adding:
rootViewController = [[RootViewController]alloc] init;
but nothing seems to work.
can anybody help out?
Thanks in advance
You will basically want to create an ivar of your view controller in your app delegate.
ViewController *myVC;
#property (nonatomic, retain) IBOutlet ViewController *myVC;
then synthesize it in the implementation file.
Then when the view controller loads, call something along the lines of this:
- (void)viewDidLoad {
AppDelegateClass *appDelegate = (AppDelegateClass *)[[UIApplication sharedApplication] delegate];
appDelegate.myVC = self;
}
At this point, you now have a direct connection to your view controller from the app delegate. Similarly, you could do the opposite to call app delegate methods from the view controller. In that case, you'd set up a delegate in the view controller's header.
id delegate;
#property (nonatomic, assign) id delegate;
again synthesizing it in the implementation file.
Now when you are in viewDidLoad, you'd call something like this:
- (void)viewDidLoad {
self.delegate = (AppDelegateClass *)[[UIApplication sharedApplication] delegate];
}
That should give you what you need to get going, so I hope that helps
You can do this with interface builder in XCode 4. I have made a short video on how to do it:
http://www.youtube.com/watch?v=6VOQMBoyqbA
Okay, I've tried to figure this out over and over again.
I know the best practice is to have the App Delegate pass the managed object context to the first view controller in an application, and then have each subsequent view controller pass the managed object context down. However, when I'm using a Tab Bar Controller in my application, I can seem to wrap my head around that extra layer.
The only way I've been able to figure out how to do it is have the root view controller of each tab "Reach Back" into the app delegate to grab the context, but as I understand it this is poor form.
You can use interface builder to achieve the same thing.
Here is a slightly modified (for some additional clarity) version of Rog's original suggestion - notice the IBOutlet's
#interface AppDelegate : NSObject <UIApplicationDelegate> {
ViewController1 *vc1;
ViewController2 *vc2;
ViewController3 *vc3;
}
#property (nonatomic, retain) IBOutlet ViewController1 *vc1;
#property (nonatomic, retain) IBOutlet ViewController2 *vc2;
#property (nonatomic, retain) IBOutlet ViewController3 *vc2;
Then on the implementation file:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
vc1.managedObjectContext = self.managedObjectContext;
vc2.managedObjectContext = self.managedObjectContext;
vc3.managedObjectContext = self.managedObjectContext;
// Continue with your implementation logic
}
Then from within Interface Builder ctrl drag from your App Delegate to the View Controller nested within the Tab Bar Controller and hook up the relevant View controller from the contextual menu that appears.
The key was, in the end, not to rely on interface builder to build the tab bar controller. By doing it manually in code I'm able to easily pass the managed object context to the view controller as I create them in applicatoinDidFinishLaunchingWithOptions:
I used this article as my basis: http://www.iphonelife.co.uk/creating-a-uitabbarcontroller-programmatically/
You can also just do something like this in your AppDelegate:
CoreDataUsingViewController *vc = (CoreDataUsingViewController *)[[tabBarController viewControllers] objectAtIndex:1];
vc.managedObjectContext = self.managedObjectContext;
I was adding coreData to an existing project with a few different build targets and didn't want to recreate all the different UITabBarControllers from scratch. It was pretty easy to do this way, though I'm not sure if it's the most artful way to do it or not.
See also
How to share a ManagedObjectContext when using UITabBarController
Not sure if I understand your issue correctly but why not simply pass the MOC to the other view controllers in the same manner? Here's an example:
#interface AppDelegate : NSObject <UIApplicationDelegate> {
ViewController1 *vc1;
ViewController2 *vc2;
ViewController3 *vc3;
}
// Declare properties as per normal
Then on the implementation file:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
vc1.managedObjectContext = self.managedObjectContext;
vc2.managedObjectContext = self.managedObjectContext;
vc3.managedObjectContext = self.managedObjectContext;
// Continue with your implementation logic
}
I hope it helps!
Rog
I am working on my first core data iPhone application. I am using a navigation controller, and the root view controller displays 4 rows. Clicking the first row takes me to a second table view controller. However, when I click the back button, repeat the row tap, click the back button again, and tap the row a third time, I get an error. I have been researching this for a week with no success.
I can reproduce the error easily:
Create a new Navigation-based Application, use Core Data for storage, call it MyTest which creates MyTestAppDelegate and RootViewController.
Add new UIViewController subclass, with UITableViewController and xib, call it ListViewController.
Copy code from RootViewController.h and .m to ListViewController.h and .m., changing the file names appropriately. To simplify the code, I removed the trailing “_” from all variables.
In RootViewController, I added #import ListViewController.h, set up an array to display 4 rows and navigate to ListViewController when clicking the first row.
In ListViewController.m, I added #import MyTestAppDelegate.h” and the following code:
- (void)viewDidLoad {
[super viewDidLoad];
if (managedObjectContext == nil) {
managedObjectContext = [(MyTestAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
..
}
The sequence that causes the error is tap row, return, tap row, return, tap row -> error. managedObjectContext is synthesized for the third time. I appreciate your patience and your help, as this makes no sense to me.
ADDENDUM: I may have a partial solution. http://www.iphonedevsdk.com/forum/iphone-sdk-development/41688-accessing-app-delegates-managed-object-context.html
If I do not release the managedObjectContext in the .m file, the error goes away. Is that ok or will that cause me issues?
- (void)dealloc {
[fetchedResultsController release];
// [managedObjectContext release];
[super dealloc];
}
ADDENDUM 2: See solution below. Sorry for the formatting issues - this was my first post.
I think I have the answer.
In the default Core Data Navigation Controller template, the AppDelegate does the following:
- (void)awakeFromNib {
RootViewController *rootViewController = (RootViewController *)[navigationController topViewController];
rootViewController.managedObjectContext = self.managedObjectContext;
}
and the RootViewController has the following code:
#interface PractitionerAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *navigationController;
#private
NSManagedObjectContext *managedObjectContext_;
NSManagedObjectModel *managedObjectModel_;
NSPersistentStoreCoordinator *persistentStoreCoordinator_;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (NSString *)applicationDocumentsDirectory;
#end
plus
- (void)dealloc {
[managedObjectContext_ release];
[managedObjectModel_ release];
[persistentStoreCoordinator_ release];
[navigationController release];
[window release];
[super dealloc];
}
In other words, when the managedObjectContext is set by code, either as above or in the tableView: didSelectRowAtIndexPath, then it needs to be deallocated.
On the other hand, if the managedObjectContext is not passed to the View Controller directly, and the following code is used to set the managedObjectContext...
if (managedObjectContext_ == nil) {
managedObjectContext_ = [(PractitionerAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
... then the managedObjectContext should not be released.
A much shorter answer is here. http://stackoverflow.com/questions/4028797/why-dont-i-have-to-release-managedobjectcontext-in-the-2nd-tableviewcontroller Apparently, even though the MOC is allocated in the View Controller, that has no effect since the MOC is owned by the AppDelegate.
I currently have an iPhone application with a tabbar and multiple viewcontrollers. All the views are designed in Interface Builder. I'd like to be able to get the currently selected index of the tabbar from the viewcontroller, but for some reason this property returns (null).
I called the following in the viewDidLoad function of my viewcontroller:
self.tabBarController.selectedIndex
What would be the correct way to do this?
Updated with the code of the AppDelegate class.
MyAppDelegate.h
#import <UIKit/UIKit.h>
#interface MyAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
UIWindow *window;
UITabBarController *tabBarController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
#end
MyAppDelegate.m:
#import "MyAppDelegate.h"
#implementation MyAppDelegate
#synthesize window, tabBarController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
[window addSubview:tabBarController.view];
}
- (void)dealloc {
[tabBarController release];
[window release];
[super dealloc];
}
#end
You should have a pointer to your tabbar in your appDelegate class. Your view has no tabbar, so you recieve nil from [self.tabBarController selectedIndex].
I think I've got it. Using the following returns the correct index:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"%d", appDelegate.tabBarController.selectedIndex);
The reason the application was crashing was the fact that I used %# instead of %d in the NSLog part. I could have sworn I tried %d before, strange...
The index is now returned, but only once. After you tap the tab section an index number is returned, but when you tap another section again no number is printed. Probably because the view has already been loaded once. Is there any way to work around this?