Reuse UIViewController instances when using storyboard - objective-c

I decided to give the use of storyboards a go in my current iPhone app. I am facing a bit of a problem. I really need to reuse my UIViewController instances.
What do I mean by that? Well, for example I have a table view controller. When I tap a cell, another view controller is loaded from the storyboard and pushed onto the navigation controller stack. This all works well, but it takes about half a second to a second each time this view controller is loaded. Before I was using story boards I simply solved this problem by caching the created instance so the second time you tap a cell the view controller can be immediately shown.
By caching the created instance I mean something like this:
if (!cachedInstance) {
cachedInstance = [MyViewController new];
}
[self.navigationController pushViewController:cachedInstance];
Does anyone know how to accomplish this using the storyboard? Thanks in advance.

If you are using segues, you will need to create custom segues in order to use a cached view controller like you did before. Otherwise, the typical "push" segue will create a new instance of the view controller for segue.destinationViewController. If you write a custom UIStoryboardSegue class and use custom segues you can override initWithIdentifier:source:destination: and put your cached view controller in for the destinationViewController, and then override perform to use the classic pushViewController call.
That is how you handle the segue if you are really intent on using them. I would just skip it though, unless you really want the fancy arrows to lay everything out on your storyboard. If you skip it you can just instantiate the view controllers into the cache and then push them on just like you did before.
If your question is more about locating a view controller inside a storyboard then you can use:
UIViewController *vc = [[UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil] instantiateViewControllerWithIdentifier:#"Some View Controller"];
Then you can save that to your cache and push it like you did in your example code.
Hope that helps.

Related

Does dismissViewControllerAnimated remove the controller's instance

For my app, I want to have a few different instances of the same view controller. For now, I am just creating a new instance like this:
iSafeViewController *tab = [[iSafeViewController alloc] init];
[tab setModalPresentationStyle:UIModalPresentationFullScreen];
[tab setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[self presentViewController:tab animated:YES completion:nil];
Great. And since this is done in the iSafeViewController class anyway, I have another button that currently just dismisses the latest controller on the stack.
- (IBAction)closeTab:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
Okay, however, I really want to be able to go back to these instances. So, I have two questions.
Does dismissViewControllerAnimated remove that controller's instance from memory. If not, is there a way I can re-present it.
There is probably a better way to navigate through viewController instances then presentViewControllerAnimated. At the very least, is there a better way to create new instances of one's viewController and be able to navigate to each of them, hopefully not in a stack. In other words, if there are three viewController instances, is there a way I can go from the third to the main one?
Any ideas would be appreciated. Thanks.
"Does dismissViewControllerAnimated remove that controller's instance from memory? If not, is there a way I can re-present it."
Calling dismissViewControllerAnimated does not explicitly remove a view controller from memory, but if no other part of the code is storing a strong reference to the view controller, once the presenting view controller dismisses your VC, it may be deallocated as per the normal memory management system.
So if you ensure something in your code has a reference to your view controller (aside from the VC which is presenting it modally), it won't disappear after being dismissed, and yes this means you can re-use it.
As for "random access" to view controllers: you could use UINavigationController and use methods like popToViewController:animated: and multiple calls to pushViewController:animated: (without animation!) to create the effect of travelling to arbitrary view controllers. This feels like a bit of a hack.
Alternatively, and preferably, you could write your own custom container view controller. This is a view controller that deals with presenting other view controllers. See Apple docs.
Here's a good WWDC video on the subject: Implementing UIViewController Containment
Further reading:
Container View Controller Examples
http://subjective-objective-c.blogspot.co.uk/2011/08/writing-high-quality-view-controller.html
Custom container view controller

How do I Access an Already Existing View Controller

I want to move from one view controller to the next, using code. I have this:
[self.navigationController pushViewController:ViewController2 animated:YES];
This code is used in the first screen that launches. I want it to push (under a certain condition) to the NEXT view controller which is called ViewController2. ViewController2 already exists (its a storyboard project). But the current view controller doesn't know what ViewController2 is in the above code. How do I 'get' or 'access' ViewController2? It already exits, with .h and .m files, but how do I call to it!?
if you're using storyboards, you can just access it through its identifier like this:
ViewController2 *viewController2 = [self.storyboard instantiateViewControllerWithIdentifier:#"ViewController2"];
Make sure to properly set its identifier to "ViewController2" or whatever name makes sense.
This isn't the way that you normally push a view controller when you're using storyboards. You should have a push segue set up in IB and the use performSegueWithIndentifier:sender: to go to the next controller. You should also implement prepareForSegue:sender: where you can get access to both the source and destination controllers. This is the method where you typically provide any data needed for the second view controller to do its job.

How to Refer to the Current View Controller in the Code

I'm a new iOS developer with a simple question: I want to programmatically move from one view controller to the next, how do I write this code?
So far I have:
UINavigationController *navigationController;
navigationController = [[UINavigationController alloc] init];
[self.view addSubview:navigationController.view];
[navigationController pushViewController:viewController animated:NO];
I'm not even sure if this will work, ultimately, but my main question is "viewController" in line 4. The program doesn't know what that is. It is the name of my current view controller, but how do I set it up so that it knows what I mean by viewController?
As an aside, the above is part of an if/else statement that occurs and is connected to the NSUserDefaults class to make it such that the view controller I am referring to only loads if terms and conditions have not previously been accepted. Will that work? Thanks.
First the simple answer: pass self when you want to pass the "current" object.
The more important consideration is: is that nav controller on screen? It's likely that your code won't do much unless you use a navigation controller which is (probably) the window's root view controller.
This is fairly easy to setup in your storyboard ("embed in"->"navigation controller") and then you don't need to instantiate it in code, you simply use self.navigationController (usually).
Normally, you would instantiate viewController just before you push it, so the program will know what it is. And, sure, you can have an if statement, and push this new view controller based on how the if statement evaluates.
I'm not sure about the code you wrote -- whether that's right depends on the structure of your app, and where you're doing this. Often, the navigation controller is made the root view controller of the window, and you set the navigation controller with a root view controller of its own when you create it

UIViewController within a UIViewController

So I have a viewControllerA, and I want to add another View managed by viewControllerB to it. There is only one UISlider activating a simple action in viewControllerB. It won't crash if I don't touch this UISlider, it will once I use UISlider. I am using ARC. I am using:
[self.view addSubView: viewControllerB.view];
to add viewControllerB to viewControllerA. Am I missing something? Thanks.
OK. It looks like a really simple situation. I just added one view controller and one action. Here is the demo project code on github: https://github.com/randomor/Demo
The reason why I want this to work is because I have another app that will create a view controller on the spot and add it to anther view. And I don't want to do it modally, because I don't want the new view controller to cover the whole screen. Thanks.
SOLUTION: So I'm now just using the latest ViewController containment API:
[self addChildViewController:viewControllerB];
It works! as long as I added this line, the event will be passed to its own controller and it stopped crashing.
i recommend you, to use the following code
in ViewControllerA.h
#import "ViewControllerB.h"
in ViewControllerA.m (where you want to push the new controller)
ViewControllerB *newController = [[ViewControllerB alloc]init];
[self presentModalViewController:newController animated:YES];
in ViewControllerB.m you will need
[self.presentingViewController dismissModalViewControllerAnimated:YES];
to make it vanish again.
concerning multiple controllers for one open screen (Apple ViewController Programming Guide):
Each custom view controller object you create is responsible for managing exactly
one screen’s worth of content. The one-to-one correspondence between a view controller
and a screen is a very important consideration in the design of your application.
You should not use multiple custom view controllers to manage different portions
of the same screen. Similarly, you should not use a single custom view controller
object to manage multiple screens worth of content.
You should try and avoid the practice of nesting UIViewControllers. While it is technically supported in iOS5, it is ill-advised, for many reasons, including the type of problem that you're having (you have a dangling pointer to a UIViewController, which is why you are crashing).
http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/
Although this question is extremely vague, I imagine that you are not keeping a reference to View Controller B, and so when view B tries to interact with it, it causes EXC_BAD_ACCESS.
What's the object that is set as the target for the slider? If it's a EXC_BAD_ADDRESS, then you may not be retaining the target, most probably the view controller for the slider.

iOS 5 storyboard, programmatically determine path

I'm having trouble to achieve the following using a storyboard:
When setup is not done:
run app -> show settings view controller -> show main navigation controller
When setup is done:
run app -> show main navigation controller
So basically, I want the app to programmatically start with the settings view in certain cases, and otherwise skip right ahead to the main navigation controller.
I did manage to show the settings view with a modal style segue from the main navigation controller, but I don't know how to display it before the main navigation controller is displayed. Any ideas?
By default, the initial view controller from your main storyboard is instantiated and displayed automatically when your app starts up. To prevent this happening you need to remove the UIMainStoryboardFile setting from your info.plist file.
With no default view controller, you are now free to create one programmatically at app startup. See the UIStoryboard documentation. Use +storyboardWithName:bundle: to load the storyboard and then use –instantiateViewControllerWithIdentifier: to create the correct view controller. You will also need to create a main UIWindow and add the view controller's view to it just like you used to do with .nib based UI. Note that without the UIMainStoryboardFile setting a main window is not created for you - read the explanation.
I managed to do it a bit different:
Use a UINavigationController as the initial view controller.
Create a root view controller that will manage the decision of what to load.
Create a Storyboard Segues from the root view controller to the main view and to settings view, and give the segues proper identifiers.
Call the performSegueWithIdentifier with the proper identifier from your root view controller.
Just another solution, hope this helps.
I did something similar to amoshaviv, his advice is sound. I did it slightly different though, and I'll give some more info.
I created a custom MyInitialViewController class, derived from UIViewController, and made this the initial view controller.
In the storyboard file, I created modal segues with appropriate names to all (in my case three) possible 'real' first view controllers.
In the MyInitialViewController class, I implemented the
- (void)viewDidAppear:(BOOL)animated;
method, to first perform the check which view to switch to, and then do the correct
[self performSegueWithIdentifier:#"NameOfSegue" sender:self];
Effectively, this makes the MyInitialViewController nothing more than a switch performed when it's brought into view. I first tried doing this when loaded because I don't care for actually seeing this view, but that did not work, while viewDidAppear does.
To make this visually smooth, I tried the following. In the properties of the segues, I disabled animation. The view I left empty, and I gave it a background color matching to that of the startup image.