Passing a managedObjectContext through to a UITabBarController's views - iphone

I have an app which is based on the Utility template (where you flip over the view to see another). On the first view there is a login screen, then it flips over to reveal a UITabBar style interface.
I'm having trouble working out how to pass the managedObjectContext from the App Delegate (where it is created) all the way through to each of the Tab Bar's views.
App Delegate's managedObjectContext get passed to FrontLoginViewController which gets passed to BackViewTabBarViewController .. where next?
The BackViewTabBarViewController nib has a UITabBarController with a UINavigationController for each tab.

Sounds like the managedObjectContext is defined in your AppDelegate. If so, then...
From whatever viewController you want... just call
MyApplicationDelegate *appDelegate = (MyApplicationDelegate *)[[UIApplication sharedApplication] delegate];
Then use...
appDelegate.managedObjectContext
whenever you need the managedObjectContext. Change the MyApplicationDelegate to your AppDelegate and you should be good to go.

I've ran into this same problem, i'll share my solution.
First you need a reference to the Nav Controller in the Tab Bar in the nib file, make sure you connect it up.
IBOutlet UINavigationController *navigationController;
Then, get the Controller as recommended in the support docs and send it the managedObjectContext:
SavedTableViewController *saved = (SavedTableViewController *)[navigationController topViewController];
saved.managedObjectContext = self.managedObjectContext;
Alex (from another post) is right, "You should generally stay away from getting shared objects from the app delegate. It makes it behave too much like a global variable, and that has a whole mess of problems associated with it."

Related

iOS - Accessing viewControllers on storyboard from appDelegate

I've got an app using Core Data where I'm creating a managedObjectContext in the app delegate.
I want to pass that managedObjectContext to two view controllers on my storyboard so they are using the same managedObjectContext to save and fetch to and from.
I can access the first view controller with:
self.window.rootViewController
But the second view controller I want to access is then after a segue from the first and no reference is returned from it.
I tried:
instantiateViewControllerWithIdentifier:
But that creates a new instance of the view rather than allowing me to access the second view controller that appears after the segue.
So my question is, how can I access the second view controller?
Or (as I'm very new to this) is there a better way to be managing/passing the data between the view controllers?
Thanks in advance.
Or (as I'm very new to this) is there a better way to be managing/passing the data between the view controllers?
It depends on the data you're trying to pass around. In this case, you want to give your view controllers access to your Core Data managed object context. Because this is something you're going to need throughout the lifespan of your app it would be better to have your view controllers access it via your application delegate.
You can do this via [[UIApplication sharedApplication] delegate] - however, you may need to typecast it to avoid compiler warnings, or alternatively you might want to create a macro that returns the managed object context to save you time and make your code a little more readable.
If you told XCode you wanted to use Core Data when you created the project you should have the methods to retrieve your object context already in your app delegate. If not, you'll need to create them.
To create a macro to save you having to write out [[UIApplication sharedApplication] delegate] every time you need to access the managed object context, check out this answer: Short hand for [[UIApplication sharedApplication] delegate]?
You can go through this :-
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
MasterViewController *result;
//check to see if navbar "get" worked
if (navigationController.viewControllers) {
//look for the nav controller in tab bar views
for (UINavigationController *view in navigationController.viewControllers) {
//when found, do the same thing to find the MasterViewController under the nav controller
if ([view isKindOfClass:[UINavigationController class]])
for (UIViewController *view2 in view.viewControllers)
if ([view2 isKindOfClass:[MasterViewController class]])
result = (MasterViewController *) view2;
}
}

How to pass NSManagedObjectContext via Interface Builder's XIBs

I have a simple iOS application with one UIViewController beneath a UINavigationController. The UIViewController has an IBOutlet for an NSManagedObjectContext.
The AppDelegate has an IBOutlet for the nav controller - but not the view controller. The view controller is automatically instantiated ala the XIB process (as a child of the nav controller).
With this setup, how does one cleanly assign or pass the app delegate's NSManagedObjectContext to the view controller's IBOutlet property. There is a nav controller in the way :) and the app delegate doesn't have a direct property for the UIViewController.
It is a weird problem in that, I want to link a property from one XIB component to another component's property. Most of the XIB work I've done takes a property and points it to an object in the XIB which in turn - gets instantiated ala the normal process but in this case, the context is being created correctly in the app delegate, I just want to pass it on to the view controller when it instantiates it.
You don't need to pass it, just grab it from the app delegate as required:
#import "MyAppDleegate.h"
NSManagedObjectContext* moc = [(MyAppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext];
Apple's docs recommend that you pass references to your managed object context to the classes the require them instead of referencing it from your app delegate.
Here's what the application:didFinishLaunchingWithOptions: looks like in one of my Core Data projects.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
LocationsViewController *lvc = (LocationsViewController *)self.navigationController.topViewController;
lvc.managedObjectContext = self.managedObjectContext;
assert(lvc.managedObjectContext != nil);
[self.window addSubview:self.navigationController.view];
[self.window makeKeyAndVisible];
return YES;
}
You'll see that I also start with a UINavigationController with a single root view controller.
You've got the right idea, but the problem you're wrestling with seems to be entirely of your own creation. You say that your app delegate has an outlet for the navigation controller, but not for the nav controller's root view controller, because you've set up your nib such that the view controller is created when the nib is loaded. There's nothing wrong with that, but there's also no reason that the app delegate shouldn't have an outlet for that controller. Indeed, the entire reason for outlets is to get references to things that are loaded from a nib.
Add an outlet to your app delegate for your root view controller, and connect it. The app delegate can then give the controller a reference to the managed object context.
With respect to your question about multiple view controllers, I wonder what sort of real-world app might have view controller (A), which needs data, load another view controller (B) which doesn't need any data, followed by a third (C) which again needs data? A realistic example might help, if you have one.
Remember that you don't have to pass the entire managed object context to each successive view controller. You can instead pass just the part of the model that the controller will need to do its work by passing a managed object.

How to share a ManagedObjectContext when using UITabBarController with inner UINavigationControllers

I have an architectural question. My App uses a TabBarController right in the application window. The ApplicationDelegate creates the managedObjectContext, although it actually doesn't need it.
Each ViewController in the TabBarController is a NavigationViewController. The first view controller for each NavigationController are my custom views. All is createde an linked via Interface Builder.
Now, how do I pass the managedObjectContext around the right way? Actually I need my views to load the data as soon as possible so that when the user chooses a tab or navigates through the NavigationControllers, the data is already there.
So my questions are:
How to I pass the context properly?
When should I fetch my data, i.e. in which method? "viewDidLoad" or "viewDidAppear"?
Thanks for all ideas!
You should generally stay away from getting shared objects from the app delegate. It makes it behave too much like a global variable, and that has a whole mess of problems associated with it. And singletons are just fancy global variables, so they should be avoided unless really necessary, too.
I would add a managedObjectContext property to each of your view controllers and assign that when you're creating them. That way, your view controllers don't have a tight linkage with the app delegate.
As for when to fetch the data, you should do it lazily. Core Data is really fast, so I would wait until viewWillAppear: to do your fetching. If you wait until viewDidAppear:, the view is already on the screen and there will be a flicker when the data loads. Do be aware, though, that viewWillAppear: is called every time your view will become visible (e.g. when the user taps the back button on the navigation bar, or a modal view controller is dismissed) so you might want to track whether you've already loaded the data and skip the loading on subsequent calls.
I've ran into this same problem, i'll share my solution.
First you need a reference to the Nav Controller in the Tab Bar in the nib file, make sure you connect it up.
IBOutlet UINavigationController *navigationController;
Then, get the Controller as recommended in the support docs and send it the managedObjectContext:
SavedTableViewController *saved = (SavedTableViewController *)[navigationController topViewController];
saved.managedObjectContext = self.managedObjectContext;
Alex is right, "You should generally stay away from getting shared objects from the app delegate. It makes it behave too much like a global variable, and that has a whole mess of problems associated with it."
You can get it from the app delegate at any time like this:
myApp *d = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *managedObjectContext = d.managedObjectContext;
Or variations of the above.
Other than that you can add a property to all your viewcontrollers and pass it around or you can create a singleton and reference that globally.
Swift
You should not share a NSManagedObjectContext, but you can share the NSPersistentStoreCoordinator.
Thus, you can create a new managed object context for each view, each sharing the same store. It is the preferred method, and allows concurrent, multithreaded access. In the example below, I am assuming that your AppDelegate, *if created with a recent version of Xcode with Use Core Data checked*, has a property named persistentStoreCoordinator:
lazy var managedObjectContext:NSManagedObjectContext? = {
// This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate {
let coordinator = appDelegate.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}
}()

Switching to another View with UITabBar

I just started developing with the iPhone SDK and I have a problem with switching to another tab with the UITabBar.
This is my current code, and it works so far:
myAppAppDelegate *appDel = (myAppAppDelegate *)[[UIApplication sharedApplication] delegate]
[appDel.tabBar setSelectedViewController:[appDel.tabBar.viewControllers objectAtIndex:5]];
But if i go to the more tab and rearrange the tabbar items, the index of the viewControllers change too. Is there any possibility how I could solve this problem?
First of all, if you ever find yourself typing this:
(myAppAppDelegate *)[[UIApplication sharedApplication] delegate]
You can probably benefit from a better design. This code probably comes from a view controller, in which case you are calling out to the App delegate from a view controller, and dealing with stuff you shouldn't have knowledge of (the tab bar).
A better design is to delegate out to the app delegate, and the app delegate switches the tab for you. The app delegate should have references to the actual view controllers in the tab bar (you can hook these up via IB if not) so you can call setSelectedViewController: with the correct object, rather than indexing into the tab bar's array:
/* Somewhere in the app delegate */
- (void)selectFooBarController {
[self.tabBar setSelectedViewController:self.fooBarController];
}
Now if you don't want to bother with delegation you can just put a method on the app delegate (like the one above) and your original code becomes:
myAppAppDelegate *appDel = (myAppAppDelegate *)[[UIApplication sharedApplication] delegate]
[appDel selectFooBarController];
Again you will need to add IBOutlet properties to your app delegate which you connect to the fooBarController etc. in Interface Builder. This will let you directly reference them rather than grabbing them out of an array.
The most straight forward means I can think off relies on the fact that when you application first starts, unless you are doing something to save the re-ordering, you could save off the initial list of UIViewControllers:
initialOrdering = [[appDel.tabBar viewControllers] copy];
Where 'initialOrdering' is an NSArray* which you would then use instead of appDel.tabBar.viewControllers in the code you posted.

Navigation & View Controller questions

I'm experimenting with ViewControllers & NavigationControllers in Interface Builder trying to get a better grasp of what's tied to what and why... I'm struggling with a scenario that has confused me. Hopefully someone can set me straight...
Say I start with your typical iPhone template View-Based Application and I display a view which is handled by view controller (viewController). Then after a certain event I'd like to replace that view with a "typical" Navigation-Based View (rootVC). I'd like to create as much as possible in IB. My questions have to do with how to show rootVC and remove all traces of the previous viewController as user will never need to return and where/how to wire in the navController in IB. Currently when it's time to show the rootVC I do the following in my viewController:
RootVC *rvc = [[RootVC alloc] initWithNibName:#"RootVC" bundle:nil];
[rvc.view setFrame:[[UIScreen mainScreen] applicationFrame]];
ViewTestAppDelegate *appDelegate = (ViewTestAppDelegate *)[[UIApplication sharedApplication] delegate];
self.rootVC = rvc;
[rvc release];
[appDelegate.viewController.view removeFromSuperview];
[appDelegate.window addSubview:rootVC.view];
[appDelegate.viewController release];
rootVC displays except viewController still has a retain count of 1?!?
Also, where should rootVC's navigationController be instantiated? Having started with the View-Based template the MainWindow.xib contains an object for the viewController (which has its own ViewController.xib) an appDelegate and a UIWindow. My RootVC.xib contains a UITableView. Do I need yet another intermediary view controller that will have another ApplicationDelegate object that I wire up to a UIWindow object and a UINavigationController? The View Controller that comes along with IB's Navigation Controller object would then be set to my RootVC class?
Sorry for the verbosity. It's difficult for me to explain. Because some objects in IB are proxies and some are "real" it's sometimes confusing (to me) when trying "new" things out what's required, where & when. Basically I want to know to go about setting up one view leading to another with no way back to first view. 2nd view basically becomes the "main" root spawning off in many directions...
I would recommend using the navigation-based iPhone application template and presenting your one-time view as a modal view on top of the root view.
I was able to figure it out by putting a reference to the viewController in the MainWindow nib and then autoreleasing the viewController after I added the navigationController & rootVC to the UIWindow. Learned another thing or two about IB along the way. Pretty powerful...