addSubview: re-writes the navigation stack? - objective-c

I'm working on a app which uses the controller containment pattern as described in the View Controller documentation in the iOS SDK.
I've written the controller container, and it works great. My controller is basically containing two sub views and it displays them both at the same time, sliding one over the other depending on what the user is doing. Works wonderful.
Now, I want to use this container controller in a navigation view. This is to get push segues to work. In effect, from my contained controllers, I want to be able to use the navigation stack, push a new controller on, and pop when the user is done.
However, I have noticed that if the navigation view is instantiated with my container controller as the root container, things fall apart.
In particular, I have noticed this:
In the iOS documentation, container controllers call addChildController: and then addSubview:. This seems to break the navigation stack, as the push segue does not work - it behaves like modal. I believe it does this because addSubview resets the navigation stack.
I confirmed this by replacing addChildController and addSubview with [self.navigationController pushViewController...]. I confirmed it is a problem with addSubview because I can reproduce the issue when I omit the call to addChildController.
When I do this, the navigation stack works properly. But of course, my container controller does not, as only the "most recently pushed" controller is visible.
I'm doing this because in my contained controllers, I want to push a new controller onto the stack, and when the user is done, I want to "pop" the stack, without reloading the "previous controller".
Using a modal segue reloads the previous controller; using a push controller does not.
I cannot find any documentation on the behavior of addSubview and it's effect on the navigation stack.
Thank you in advance for any light you guys can shed!

I'm having a bit of trouble completely understanding what you are doing, but I think that what you want to do is exactly what I'm doing.
I have a UINavigationController that has as its rootView a container UIViewController. That controller adds children per the normal methods. One of those children views pushes other views that may get popped.
One of those pushed views COULD message the appDelegate and make itself the rootViewController if it wanted to. In general, as long as you keep a strong reference to a view controller, you can remove it from whoever 'owns' it, and muck around with the navigationControllers viewControllers array to your hearts content.

Related

UISplitViewController - Multiple Detail View Controllers via storyboard segues

I'm trying to do a project for the iPad in which I'd like to utilized the split view controller. I'll be having different detail view controllers for each of the cells in the master view controller.
I saw one solution how to do this via storyboard segues in this site.
He basically linked each of his UITableViewCell to different detail view controllers. But I'd like to know if this is a "stable" or a "good" way of doing this. I mean, is it any better or as stable as doing it programmatically? What would be the consequences of doing his method, if there are any?
Here is the link to the solution I found
This is kind of a tricky one, even though it's an incredibly common use case.
1) One idea is to have an empty root view controller as your detail and it handles managing segues under the hood to quickly segue to the detail view you actually care about, utilizing the "replace" segue. This should "technically" fix having the "back" button at the top left and still allow you to pop to root and not have it show the empty controller. Haven't tested these though, so I'm not sure.
Edit: In Xcode 6, the "replace" segue is conveniently handled by a "show detail" segue which is used specifically for this type of view handling on Split View Controllers. I recommend using this method exclusively in new projects. See sample code.
2) The other idea is to have separate navigation controllers in your storyboard (one connected, the rest all stranded). One for each detail view type and tapping on the master menu will simply swap the navigation controller for the detail view to the one you care about.
Code similar to this in AppDelegate:
self.detailNavigationController = [self.masterNavigationController.storyboard instantiateViewControllerWithIdentifier:#"MyChosenNavigationControllerStoryboardId"];
self.splitViewController.viewControllers = #[self.splitViewController.viewControllers[0], self.detailNavigationController];
self.splitViewController.delegate = (id)self.detailNavigationController.topViewController;
The downside to this second way is that in memory tests, it doesn't appear that swapping a new nav controllers in frees up all of the memory that the old nav controller was using. So it's good to use for simple apps but not for anything crazy complex.

Present View Controller without calling presentViewController (or dismissViewController)

I am in the process of building a reasonably complex iPad app that will run on iOS7+. The app has a login screen that must be accessed once every user session, this is the starting view controller in my storyboard. The user can log out from any other screen in the app (there are about 60 other screens) by touching a button that is always available in every other ViewController. When the user logs out, a custom transition animation should be used.
It seems I can achieve this in one of two ways, either with a segue from every screen in the app to the login page, which makes the storyboard impossible to read, or presentViewController.
I've implemented this with presentViewController, by looking up the view controller by id from the storyboard (which creates a new instance, which is a desired behavior) and then presenting it from the current view controller.
Not surprisingly, this does not dismiss the original login view controller and essentially creates a stack of view controllers, eventually I run out of memory as each time a user logs out, a new login view controller is created and retained.
Is there a way to clear this "stack" of view controllers?
Is there a different way to present a view controller, with an animation, that does not involve presentViewController or segues? I've considered view controller containment, but that doesn't seem quite right when used with storyboards.
Have you considered replacing the root view controller? If the app delegate observes a logout notification and replaces the root view controller with the initial content of the storyboard that should get the app back to the initial screen.
I've seen that presented as a solution for login/logout issues elsewhere on the web, but I'm not sure if there's a transition you can animate there.
I think your approach is wrong. Correct me else wise.
Login mechanisms are supposed to be singleton instances. Hence you should define your view controllers, models, views all as singleton instances. Please look at my following code for example.
static id objectInstance;
+ (id) sharedInstanceID {
if (!objectInstance) {
objectInstance = [[YourClass alloc] init];
}
return objectInstance;
}
Let me know if this helps you.

UINavBarController connecting the same UIViewController to multiple navigation controllers

I have a storyboarded app with a chain of tableviews followed by a detail view. Kind of the classic iPhone app. There are 4 tabs and each one leads to a navigation controller.
The issue is I really want to avoid unnecessary glue code since the app is basically finished. If it was possible to connect the Search and Favorites (bottom two off the tab bar) controller as 'Root View Controllers' to the same UIViewController I would be done. However, this won't work since a view controller can only be the root view controller to one tab. So as you can see I've instituted two dummy UIViewControllers that forward you to the UIViewController in the middle. Now, unfortunately, I have to write code to make that central view controller a fake root view controller to disable the appearance of the back button, and prevent popping to the blank root when you double-tap the tab bar.
Has anyone got a more elegant solution?
This appears to be a flaw in Storyboards. One workaround would be to use simple view controllers for each navigation controller's rootViewController. Put a UIContainerView in each that points to the UIViewController you want to share.

Custom segue that 'finishes' early?

I'm looking to implement a custom segue that pushes to a UIViewController, but completes before the new UIViewController fully fills the screen, leaving some of the source view controller still in view and functional. (For example; new view controller covers half of the user interface).
I'm keen to use a segue rather than a view that is moved using CGRect, Quartz framework method, or similar, as constraints get messy really easily, unless a custom segue could utilise such methods(?)
Any pointers greatly welcomed! :)
For this task you would use a container view controller, which manages and displays the content of multiple other view controllers at a time while letting them interact with their views like normal. An example of this would be the UISplitViewController, which displays two view controllers' views at a time, one on each side of the screen. You can design segues that swap out one view controller of the multiple on display in a container view controller, similarly to the Replace Segue implemented by Apple to swap out a UISplitViewController's detail view controller (the one on the right hand side).

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.