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
Related
I am quite new to iOS programming so please be nice :) I am trying to google out this for hours now with no success. I have setup an iOS master detail project.
What i need to do. is to change a label in the detailViewController when the app calls applicationDidEnterBackground
This is my faulty code in the appdelegate applicationDidEnterBackground method
UIViewController *temp = [self.navigationController visibleViewController];
NSLog(#"%#",[temp nibName]);
if ([temp nibName] == #"DetailViewController") {
temp._lblBrewingTime = #"";
}
This doesnt work. semantic issue: lblbrewingtime not found on object of type UIViewController.
If I add a breakpoint and check the structure of the temp pointer. I can see the _lblBrewingTime type.
Can you please point me how to get the properties of whatever view is currently loaded in the app delegate?
thank you very much,
Greets,
Nick
You have to explicitly cast it to DetailViewController, once you are sure that the visibleViewController is DetailViewController actually.
So here's the fix:-
UIViewController *temp = [self.navigationController visibleViewController];
NSLog(#"%#",[temp nibName]);
if ([temp nibName] == #"DetailViewController") {
DetailViewController* tempDVCObj = (DetailViewController*)temp;
//temp._lblBrewingTime = #"";
tempDVCObj._lblBrewingTime = #"";
}
And it says absolutely correct that your property _lblBrewingTime is not the property of UIViewController, it's the property of DetailViewController i.e. a subclass of UIViewController.
Some things here:
You should keep a reference to your main controller in the AppDelegate and access the view through this reference - the visible view controller in the navigation controller may not be your view controller class, e.g. because you navigated to another view.
You access the view controller via the UIViewController interface. The UIViewController class does not know about your child view controller's properties, so it cannot access the _lblBrewingType. You have to use your view controller's class name to access its properties, e.g. MyViewController * myVc = (MyViewController*)viewController.
_lblBrewingType looks like an internal variable of your view controller. To access it from the outside, you must provide it as a property:
// MyViewController.h
#interface MyViewController : UIViewController
{
UILabel* _lblBrewingType;
}
#property (strong, nonatomic) IBOutlet UILabel *lblBrewingType;
And the implementation:
// MyViewController.m
#implementation MyViewController
#synthesize lblBrewingType;
#end
I have two tabs. Tab 1 and Tab2.
In tab2 I have a table view controller displaying a table. The table view controller is inside a navigationcontroller. I am using storyboard.
I need to pass my managedObjectContext to the second tab so that I can display the data in the table.
This is what I have so far but it seems quite rigid. How do I pass the context without getting it from the delegate? So far I have this but if I understand correctly, I need to pass the context to svc from fcv and not directly in the delegate.
FirstViewController *fvc = (FirstViewController *)[tabBarController.viewControllers objectAtIndex:0];
SecondViewController *svc = (SecondViewController *)[tabBarController.viewControllers objectAtIndex:1];
fvc.managedObjectContext = self.managedObjectContext;
svc.managedObjectContext = self.managedObjectContext;
Okay
expose the pertinent core data objects in your app delegate
#property(nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property(nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property(nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
In both your FirstViewController and SecondViewController you then access the moc (and the other stuff if you need to) as above (Marko's answer) except mod is shown....
[(MyAppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
Try the other way round: If the managedObjectController is maintained in the app delegate, you can get it from anywhere in your app with the following call (replace "MyAppDelegate" with whatever your delegate is called):
[(MyAppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectController];
I think you are on the right track. If you want to make it a little easier on yourself you could make sure you are consistant with your naming (which you currently are) and use something like
for (id controller in tabBarController.viewControllers) {
if ([controller respondsToSelector:#selector(setManagedObjectContext:)]) {
[controller performSelector:#selector(setManagedObjectContext:) withObject:self.managedObjectContext];
}
}
This would allow you to add/remove and reorder the tabs without things breaking on you. This is taking advantage of convention over configuration.
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?
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
Basically I have a viewController that loads at app startup. In that VC, depending on whether there is userdata, I serve up a ModalView with either a login. After the user logs in, I can then dismiss the modalView, but I would like to call a method on the opener that will then populate a table with data.
I thought from the modalView I could do something like
[self.parentViewController loadInitialData];
[self.dismissModalViewControllerAnimated:YES];
but that does not work..
any suggestions?
The problem is because self.parentViewController is of type "UIViewController" and your -loadInitialData method doesn't exist in UIViewController. There are a couple of common ways to solve this problem... from easiest and least "correct" to most complicated and most "correct":
1) First you need to cast your self.parentViewController to the type of your parent view controller. Something like:
MyParentViewController *parentVC = (MyParentViewController*)self.parentViewController;
[parentVC loadInitialData];
2) You can add a property to your modal view controller that explicitly keeps a reference to your parent view controller and then call loadInitialData doing that.
#interface MyModalViewController : UIViewController {
MyParentViewController *myParentViewController;
}
#property (nonatomic, retain) MyParentViewController *myParentViewController;
Then you can call:
[self.myParentViewController loadInitialData];
3) The most complicated, but most correct way to do it is to create a Delegate protocol, have your ParentViewController implement the protocol and have your modal view controller keep a reference to the delegate and call that way. Something like:
#protocol ManageDataDelegate
- (void) loadInitialData;
#end
#interface MyParentViewController : UIViewController <ManageDataDelegate> { ...
#interface MyModalViewController : UIViewController {
id<ManageDataDelegate> delegate;
}
#property (nonatomic, assign) id<ManageDataDelegate> delegate;
When you present your modal view controller, just set the delegate. In your MyParentViewController:
MyModalViewController *vc = [[MyModalViewController alloc] init];
vc.delegate = self;
[self presentModalViewController:vc];
Then in your modal view controller you can call back like so:
[self.delegate loadInitialData];