How to pass NSManagedObjectContext via Interface Builder's XIBs - iphone

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.

Related

viewDidLoad is in fact called every time there is a segue transition

I have seen a lot of posts on stack overflow stating that the viewDidLoad method of controllers is only called the first time the controller is accessed and not necessarily every time but always at least once.
This is not what I am seeing at all! I put together a simple test to highlight this:
https://github.com/imuz/ViewDidLoadTest
It seems for navigation controller segues and modal views viewDidLoad is always called. The only time it is not called is when switching between tabs.
Every explanation of viewDidLoad I can find contradicts this:
When is viewDidLoad called?
UIViewController viewDidLoad vs. viewWillAppear: What is the proper division of labor?
http://www.manning-sandbox.com/thread.jspa?threadID=41506
And apples own documentation indicate that a view is only unloaded when memory is low.
I am currently doing initialisation in viewDidLoad making the assumption that it is called with every segue transition.
Am I missing something here?
Phillip Mills' answer is correct. This is just an enhancement of it.
The system is working as documented.
You are seeing viewDidLoad because the view controller being pushed onto the navigation controller is a new instance. It must call viewDidLoad.
If you investigate a little further, you would see that each of those view controllers are deallocated when they are popped (just put a breakpoint or NSLog in dealloc). This deallocation has nothing to do with the view controller container... it does not control the life of the controller it uses... it is just holding a strong reference to it.
When the controller is popped off the navigation controller stack, the nav controller releases its reference, and since there are no other references to it, the view controller will dealloc.
The navigation controller only holds strong references to view controllers that are in its active stack.
If you want to reuse the same controller, you are responsible for reusing it. When you use storyboard segues, you relinquish that control (to a large extent).
Let's say you have a push segue to view controller Foo as the result of tapping some button. When that button is tapped, "the system" will create an instance of Foo (the destination view controller), and then perform the segue. The controller container now holds the only strong reference to that view controller. Once it's done with it, the VC will dealloc.
Since it creates a new controller each time, viewDidLoad will be called each time that controller is presented.
Now, if you want to change this behavior, and cache the view controller for later reuse, you have to do that specifically. If you don't use storyboard segues, it's easy since you are actually pushing/popping the VC to the nav controller.
If, however, you use storyboard segues, it's a bit more trouble.
There are a number of ways to do it, but all require some form of hacking. The storyboard itself is in charge of instantiating new view controllers. One way is to override instantiateViewControllerWithIdentifier. That is the method that gets called when a segue needs to create a view controller. It's called even for controllers that you don't give an identifier to (the system provides a made-up unique identifier if you don't assign one).
Note, I hope this is mostly for educational purposes. I'm certainly not suggesting this as the best way to resolve your problems, whatever they may be.
Something like...
#interface MyStoryboard : UIStoryboard
#property BOOL shouldUseCache;
- (void)evict:(NSString*)identifier;
- (void)purge;
#end
#implementation MyStoryboard
- (NSMutableDictionary*)cache {
static char const kCacheKey[1];
NSMutableDictionary *cache = objc_getAssociatedObject(self, kCacheKey);
if (nil == cache) {
cache = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, kCacheKey, cache, OBJC_ASSOCIATION_RETAIN);
}
return cache;
}
- (void)evict:(NSString *)identifier {
[[self cache] removeObjectForKey:identifier];
}
- (void)purge {
[[self cache] removeAllObjects];
}
- (id)instantiateViewControllerWithIdentifier:(NSString *)identifier {
if (!self.shouldUseCache) {
return [super instantiateViewControllerWithIdentifier:identifier];
}
NSMutableDictionary *cache = [self cache];
id result = [cache objectForKey:identifier];
if (result) return result;
result = [super instantiateViewControllerWithIdentifier:identifier];
[cache setObject:result forKey:identifier];
return result;
}
#end
Now, you have to use this storyboard. Unfortunately, while UIApplication holds onto the main storyboard, it does not expose an API to get it. However, each view controller has a method, storyboard to get the storyboard it was created from.
If you are loading your own storyboards, then just instantiate MyStoryboard. If you are using the default storyboard, then you need to force the system to use your special one. Again, there are lots of ways to do this. One simple way is to override the storyboard accessor method in the view controller.
You can make MyStoryboard be a proxy class that forwards everything to UIStoryboard, or you can isa-swizzle the main storyboard, or you can just have your local controller return one from its storyboard method.
Now, remember, there is a problem here. What if you push the same view controller on the stack more than once? With a cache, the exact same view controller object will be used multiple times. Is that really what you want?
If not, then you now need to manage interaction with the controller containers themselves so they can check to see if this controller is already known by them, in which case a new instance is necessary.
So, there is a way to get cached controllers while using default storyboard segues (actually there are quite a few ways)... but that is not necessarily a good thing, and certainly not what you get by default.
I believe the Apple documentation is describing a situation where the view controller is not being deallocated. If you use a segue, then you are causing the instantiation of a new destination controller and, being a new object, it needs to load a view.
In xib-based apps, I have sometimes cached a controller object that I knew I might re-use frequently. In those cases, they behaved in keeping with the documentation in terms of when a view had to be loaded.
Edit:
On reading the links you included, I don't see any contradiction in them. They, too, are talking about things that happen during the lifespan of a view controller object.
It is called every time the controller's view is loaded from scratch (i.e. requested but not yet available). If you deallocate the controller and the view goes along with it, then it will be called again the next time you instantiate the controller (for example when you create the controller to push it modally or via segue). View controllers in tabs are not deallocated because the tab controller keeps them around.

On iOS, if a view controller has no view yet, why does NSLog(#"self.view is %p", self.view) crash?

If a new iOS project is created with an Empty App template in Xcode 4.3.2, and in AppDelegate.m:
self.window.rootViewController = [[FooViewController alloc] init];
and in FooViewController's viewDidLoad, the following:
NSLog(#"self.view is %p", self.view);
NSLog(#"self.view is %#", self.view);
will print out the view, so it looks like the default loadView will instantiate a view and assign it to self.view.
So if I override loadView with an all empty method, and comment out the second NSLog statement above, I expect the first NSLog statement to print out 0x0, but instead the app crashed due to bad memory access right at that NSLog line. Why would that be?
Okay, after a knee-jerk and obviously wrong answer, I tried this. The Empty App template would not have a rootViewController, so I used a single screen template. After running, I see that you are getting a stack overflow. In trying to access self.view, you are calling the view property on the superclass, which is then trying to load the view in order to return it, which is calling viewDidLoad, etc., as far as I can see. The other NSLog statement does the same.
The documentation for the view property in UIViewController states:
Because accessing this property can cause the view to be loaded automatically, you can use the isViewLoaded method to determine if the view is currently in memory.
It also has a link to The View Controller Life Cycle, which states:
The steps that occur during the load cycle are as follows:
The load cycle is triggered when the view controller's view property is accessed and the view is not currently in memory.
The view controller calls its loadView method. The default implementation of the loadView method does one of two things:
If the view controller is associated with a storyboard, it loads the views from the storyboard.
If the view controller is not associated with a storyboard, an empty UIView object is created and assigned to the view property.
The view controller calls its viewDidLoad method to allow your subclass to perform any additional load-time tasks.
So when you say:
So if I override loadView with an all empty method
You're deliberately breaking the life cycle, because when your overridden version of loadView finishes, it should have loaded a view. Because it didn't, you get a crash.

Programmatically navigating in iOS

I'm in the process of porting an existing Android app to iOS, and I'm pretty inexperienced in this environment. The examples I've read so far for navigating multiple views all use some kind of visual user control for triggering the loading and unloading of views (tab bar, nav bar). This application's navigation needs to be pretty strict and not allow the user to freely move around between the three views.
The app needs to have a full screen splash view, a main view that the user interacts with, and a third view for data collection. The splash screen should appear, and the user should be navigated to the main view when tapping on the splash image. There will be custom logic in the main view controller for determining if data is required at which point it should navigate to the data collection view. Once valid data is entered and the user clicks OK, it should navigate back to the main view.
What I've read so far is that all Views should have an associated UIViewController class, and the easy way to do this is to create the XIB and UIViewController class in one shot and link them together (I have plenty examples/books/tutorials that I can reference for that part). I believe what I've read is that the app should have a root UIViewController that handles loading the others and navigating between them.
My questions are:
What class should I derive from for my main view controller that I use to load the others?
How do I wire that up to the app so that it knows to load this as the main controller?
What is the accepted standard way of having a navigation controller in the app and allowing the other views to obtain a reference to it? Should my UIViewControllers hold a reference to their parent controller, or should they ask the UIApplication for a reference to it when needed? How do I make sure I don't instantiate extra copies of the views and their controllers as the user navigates?
What class should I derive from for my
main view controller that I use to
load the others?
UIViewController
How do I wire that up to the app so
that it knows to load this as the main
controller?
Read the "Defining Your Subclass" section of View Controller Programming Guide for iOS. Scratch that -- read the whole thing. It's all important, you might as well start to learn it now. Also read App Programming Guide for iOS. Again, read the whole thing, but the application lifecycle part is the most relevant to your question.
What is the accepted standard way of
having a navigation controller in the
app and allowing the other views to
obtain a reference to it?
Again, this is explained well in View Controller Programming Guide. Views should never care about the navigation controller, but any view controllers that are part of a navigation stack have direct access to the nav controller via their respective navigationController properties.
Should my UIViewControllers hold a
reference to their parent controller,
or should they ask the UIApplication
for a reference to it when needed?
A view controller already has a reference to its parent controller in its (surprise!) parentController property. It's best for a controller to avoid assuming too much about its
parent, though. If the controller expects its parent to be a certain type or respond to certain messages, it becomes more difficult to reuse that controller or reorganize your application. Try to give the controller what it needs to do its thing when you create it. If the controller will need to ask for additional data or something like that, delegation is a good way to go.
How do I make sure I don't instantiate
extra copies of the views and their
controllers as the user navigates?
Exercise caution. There's not much danger of creating extra copies of views in a properly structured application because each view controller should take care of its own views. If you find yourself loading or otherwise creating views outside the context of the view controller that owns them, stop that.
It sounds like you can accomplish what you need with a couple of basic calls. To programmatically call a view controller:
- (void)showController {
MyViewController *myController = [[MyViewController alloc] initWithNibName:#"MyViewControllerXIB" bundle:nil];
[self.navigationController pushViewController:myController animated:YES];
[myController release];
}
To return to the previous view just call from any view controller:
- (void)goBack {
[self.navigationController popViewControllerAnimated:YES];
}
Read up on the documentation for the UINavigationController for more ways to move through views. This method is just one of many ways to do this and may not be suitable for all situations.
Not quite right — each UIViewController should know how to trigger its children. Apple's preferred navigation path through views is a branching tree, with the caveat of tab bars that collapse multiple view controllers into a single node on the tree.
You don't explicitly handle loading. Normally you have a sufficient relationship between your NIBs that the container classes are loaded automatically. Cocoa will then load the views whenever they're needed but not yet loaded (which is the purpose of loadView and viewDidLoad), and keep them unless and until a low memory warning requires them to be purged (leading to viewDidUnload). It's relatively rare that you explicitly load a NIB yourself (though table view cells are an obvious example where programmatically loading a NIB is quite common).
So you'd probably have:
a splash screen or preview of the first view controller, as the Default.png
a view controller that probably displays Default.png, and has two outlets going to the data collection controller and the main controller
when the user taps the button on the main screen, ask the model whether data collection is necessary. If so then navigate to the data collection controller, otherwise navigate to the main controller
give the data collection controller an outlet to the main controller and let it perform a navigation there at the appropriate moment
You get a MainWindow.xib for free when creating a new view based project. Probably the easiest thing to do is to put references to the three UIViewController subclasses in there, but set each of them to load from other files. Set the links between them in MainWindow.xib, set the links to things within the relevant views within the relevant XIBs.
That will prevent you from keeping multiple instances of any controllers about, and the built-in Cocoa loading mechanisms will ensure that the stuff that occupies significant amounts of memory — the views — is loaded only on demand and kept for no longer than space allows.
There's no need to link to parent view controllers. Every view controller already knows who presented it, via the parentViewController property. So if a view controller wants to dismiss itself and return to whoever presented it, you can just issue:
[self.parentViewController dismissModalViewControllerAnimated:YES];
Because the model is ideally a separate sovereign thing, all controllers really need to know is which other controllers they can present, how to populate themselves from the model and how to push data back to the model. You rarely end up with particularly complicated links between view controllers.
I think you should have the view loading/unloading in the application delegate and then each view should send notifications to the application delegate.
Here is the official introduction from Apple:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Notifications/Introduction/introNotifications.html
You can set before which ViewController to load first
If the application is navigation based use the following code:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MainViewController *mainViewController = [[MainViewController alloc] initWithNibName:#"MainViewController" bundle:nil];
self.nav = [[UINavigationController alloc] initWithRootViewController:mainViewController];
[_window addSubview:nav.view];
[_window makeKeyAndVisible];
}
If the application is View based use the following code:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}

How to access one UIViewControllers properties from another UIViewController?

I have one single MainViewController which has of course it's one main UIView. I have this main view comprised of many different subviews.
Some of these subviews have their very own ViewController.
Lets say the MAIN view (whose delegate is primarily MainViewController) has a container which loads another UIView that uses a separate UIViewController- SecondaryViewController as the delegate for most it's actions.
This container view is of course loaded in MainViewController via
MyContainerViewController *myContainerController =
[[MyContainerViewController alloc] ...];
[self addSubView: myContainerController.view];
the controller for myContainerController.view though is MyContainerViewController. How inside this controller do I access MainViewController properties? Specifically I need to access MainViewController's - self.navigationController property to push a new ViewController? :)
Does this make any sense? I assume there's going to be casting of some sort involved since it seems I need to somehow retain a reference to MainViewController inside SecondaryViewController?
It doesn't make sense to push a new ViewController from the SecondaryViewController in the MainViewController.
This screws up the design of the code. A child object will access its parents method to call a method. By other words: bad code.
Make a delegate call from the SecondaryViewController to the MainViewController that it state has changed. Then the MainViewController can decide to do with the call and the SecondaryViewController will not know anything about the implementation of the MainViewController.
So:
Make a protocol in SecondaryViewController.
Let the MainViewController be SecondaryViewController's delegate.
Implement the delegate method in MainViewController which pushes the new ViewController.
Expose the desired sub-view controllers as properties of the view controller that contains them.
Expose your root view controller(s) as properties of your app delegate, and synthesize them also.
When you want to access a different UIViewController, first obtain a reference to your appDelegate:
MyAppDelegate* myAppDelegate = [[UIApplication sharedApplication] delegate];
Then just use your chain of properties to get access to your desired view controller.
SubSubViewController* ssvc = myAppDelegate.rootViewController.subViewController.subSubViewController;
Some folks frown upon the use of the sharedApplication class method to obtain the reference to a delegate, but I've used it in multiple apps and not suffered for it (yet). The alternative is to pipe your appDelegate through your hierarchy of viewControllers.

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...