I've connected a bunch of navigation controllers to my UISplitViewController to replace the detail view, however when I do I get this error
'Could not find a split view controller ancestor for '<UISplitViewController: 0x6a52f50>',
while performing a split view controller replace segue (identifier 'Queue') with
destination '<UINavigationController: 0x6870ff0>''
And I can usually guess what's going on from these internal inconsistencies but this one I literally have no idea -- any help is much appreciated!
Thanks!
Examine the sequence of segues leading into the source scene. If it doesn't contain a split view controller in the mix, you've found your problem. Replace segues can only be used in scenes that are presented in the context of a split view controller.
I ran into this issue and all my segues were fine, no code was interfering. Turns out the problem was in the master view controller:
#property (strong, nonatomic) UISplitViewController *splitViewController;
The property had become unused and something was trying to use it while it was nil. Simply removing it did the trick.
Related
I know there is the common practice in iOS development of having one UIViewController presented on the screen whose view is loaded from a XIB which will contain all the UIView subclasses in it.
While I was working on a pretty complex widget for an app, I decided to make the widget subclass a UIViewController instead of a UIView. This was because I thought a UIViewController was essentially a UIView with some helper methods around it. Then I could create a XIB for it (I know UIViews can have their own XIBs too), load the views it contains, place ITS view in the presented parent VC's view, and lay it out.
This works fine so far, but I'm wondering if this is bad practice and if I should just subclass a UIView instead and give it a normal NSObject controller. I am seeing some problems with this and I was wondering if anybody could address concerns I have with this approach?
EDIT NOTE: The widget VC does NOT relate to the VC view it is in and is reusable on ANY screen. So the answer is not subclassing the widget VC with the parent VC. The widget is INSIDE the parent VC, but it is NOT a parent VC.
EDIT NOTE 2: I am NOT using Storyboard. Only Autolayout, XIBs, and ARC.
Why can't we have VC's in VC's?
1) Can VC's be simply dropped into ANOTHER VC's XIB and be loaded easily as a subview?
2) I read here: When to use a UIView vs. a UIViewController on the iPhone?
The top answer explains how the VC controls rotation of the screen and laying out the subviews again, so if you add another VC, then the system will automatically think that THAT is the main VC and will attempt to rotate it instead, causing problems. Is this true? Or is he just talking about if you somehow got into a state where 2 VC's were "presented"? I wasn't sure if his answer applied to VC views that were SUBVIEWS of other VC views.
3) In general is this good practice? It certainly seemed more reasonable as it made loading the subview VC's view much easier.
Thanks!
It's absolutely fine. The answer to your problem is ContainerView.
Container View defines a region within a view controller's view subgraph that can include a child view controller. Create an embed segue from the container view to the child view controller in the storyboard.
You almost got it right. Yes, it's good to make a view controller for what you needed. But you shouldn't just add it's view to the parent view, you should also add the view controller as a child view controller of the first view.
You can add many views controllers as child view controllers of a view controller.
You can learn more about this here.
So suppose you need some functionality that requires next storyboard. For example you need to upload different content to view depending on what tab is clicked.
But the problem comes out when you try to use this storyboard. When you switch tabs you getting this behaviour.
But in first tab everything fine. So looks like it doesn't load view second time. Can somebody explain or give a link to the behaviour of navigation controller in this case, because I can't find anything useful in reference. Or how should I correct this behaviour in IB or programatically? Thanks.
a simple work-around is to put a "fake-viewcontroller" as root for the second navigation. On this "fake" controller execute in viewDidLoad a [self performSegueWithIdentifier: #"goToTheControllerHereWeGo" sender: self];
So, as I mentioned in my comment I do think this is a bug but we'll see how Apple responds. But yeah, segues have no love for view controllers that are the root view controllers of multiple navigation controllers. There are a number of workarounds depending on the context under which it comes up.
Best workaround : Share the navigation controllers, not their root view controllers
So for the simple example given above you could do this and everything would be fine:
Other workaround : This one is more applicable to complex storyboards that may have different custom navigation controllers so that sharing the nav controller isn't possible. One hilarious aspect of this issue is that when a view controller has two parent nav controllers in the storyboard, you won't know until runtime which one gets it! And further, on different runs they can switch :P (another reason I think this is a bug).
Sooooo from within prepareForSegue you can check if your navigation controller got unpacked with a rootViewController and, if it didn't, force it in there yourself:
UINavigationController* nc = segue.destinationViewController ;
if (nc.viewControllers.count == 0) {
nc.viewControllers = #[[self.storyboard instantiateViewControllerWithIdentifier:#"MyDetailVC"]];
}
Just provide more explanation beside the comments 'You cannot make a UIViewController as the root view controller of two different Navigation controller'. Suppose you can do so, then the view of the controller will be child view of the two navigation controller's view. It cannot happen as "it" cannot be a child of A but simultaneously a child of B.
On what condition will the tabview items switch , also trigger one of the two separate view controllers? what is the logic? when is it implemented? and no matter whatever the logic maybe, why does a single view controller (let us assume it is being filled up with different data according to the root) has 2 separate roots? we cannot add anything separately from the navigation controller itself,
Navigation controller is the flow that sets the storyboard in motion, but putting a VC as a subview of two different NC just has no point.
Think it of like this,
No additional information is provided by the Navigation controller itself, it just sets things in motion. So why would you want to put a VC as the child of 2root NC.
More easily think it as multiple inheritance, in objc, java its not possible because of the
Diamond problem. look it up and i hope it helps u understand
Update: I have decided to go a different route with my problem.
Instead of trying to reuse the same UIViewController directly, I use two vanilla UIViewControllers that are set as rootViewControllers. In their loadView methods, they make a call to [storyboard instantiateViewControllerWithIdentifier:] to get the former UIViewController and set their views to the controller's view. This is probably the preferred approach anyway, since I need to set several variables and delegates.
I have a UIStoryBoard with a UITabBarController as the entry point connected with two UINavigationControllers. Each of those share a common UIViewController as their root view controller. When the app starts, the first UITabBarItem is selected and the view loads as-expected. However, when I select the second UITabBarItem, the same view is not visible. I see the UINavigationBar with a black background. Am I doing something incorrect with the Storyboard interface, or do I need to manually instantiate the UIViewController via each UINavigationController's method--loadView for instance?
Strangely this is a question that no one else is asking. As far as I know it is not possible to share the rootViewController which I think is without a doubt a bug since when you inspect the connection on the storyboard you can see that the view controller is connected to both navigation controllers. I consider this a flaw in storyboarding because duplicating viewControllers and reapplying all of their connections is quite error prone and makes the storyboards overly complex.
I see your solution to the problem. Workarounds like this make me question if the current storyboard functionality in iOS is ready for creating apps. I think that there is a conceptual problem with the storyboards, Apple needs to decide if a viewController on a storyboard represents an instance or if it represents just the class, right now it is not consistent as you can see that multiple segues can actually point to the same viewController but in reality each segue has its own instance, why this is not also followed for rootViewController connections?, I don't know.
Just as a note, with your solution take into account the following from Apple's documentation:
"Important A view controller is the sole owner of its view and any subviews it creates. It is responsible for creating those views and for relinquishing ownership of them at the appropriate times, including during low-memory conditions and when the view controller itself is released. If you use a storyboard or a nib file to store your view objects, each view controller object automatically gets its own copy of these views when the view controller asks for them. However, if you create your views manually, you should never use the same view objects with multiple view controllers."
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html
Definitely a bug in Storyboards. Another way to do this is to create basic UIViewControllers for each UINavigationController, then have a UIContainerView that points to the same UIViewController in each of the basic view controllers.
I think the easiest solution is to set no root view controller for your nav controller in the storyboard and then do something like this with your nav controller:
- (void)viewDidLoad
{
[super viewDidLoad];
UIViewController *topVC = [[UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil] instantiateViewControllerWithIdentifier:#"myTopVC"];
[self pushViewController:topVC animated:NO];
}
The first-pushed VC becomes the root.
Can you associate child view controllers to a custom container view controller in Storyboard?
I can link child view controllers to a tab view controller, and I can link one view controller to a navigation controller.
What must I do to the container VC to accept child VCs?
As something of a combo of Caleb and Matt's answers, I did:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"cpdc_check_embed"]) {
self.checkVC = segue.destinationViewController;
}
}
...where checkVC is a property on the container controller:
#property (weak,nonatomic) PXPCheckViewController * checkVC;
You just have to set your embed segue's Storyboard ID to whatever you want (in this case, cpdc_check_embed):
...and then check the identifier in -prepareForSegue:sender:.
Still not an outlet, but cleaner than Matt's (IMHO) and more specific than Caleb's, and you still get a nice-looking storyboard:
The storyboard deals with built-in container view controllers very nicely, displaying segues to child/root view controllers so that relationships are clearly shown. It is also nice how the children and parent view controllers are separated into different scenes.
If you want to achieve this effect in your own project, then there is a trick that is not perfect but very straightforward. In my example, suppose I have a container view controller that acts like a tab bar controller with only two tabs, 'left' and 'right'. I want to have a scene represent the parent view controller, and two separate scenes represent both the 'left' child view controller and the 'right' child view controller.
Even though it is impossible, it would be nice if I could create IBOutlets from the container view controller to its children in different scenes, and then when my container view controller is displayed set up the parent/child relationships according to the rules described the UIViewController documentation. If we had references to our 'left' and 'right' child view controllers, then we could set up the relationships no problem.
The standard solution to this referencing problem is to create references to child view controllers by dragging in Object outlets into the container view controller's scene, and then specifying their class type as being instances of the child view controller classes.
In order to keep children separated in different scenes like Apple's built-in containers, however, we will use a different trick. First, suppose we have the following properties declared in our container class, ContainerViewController:
#property (nonatomic, strong, readwrite) UIViewController *leftViewController;
#property (nonatomic, strong, readwrite) UIViewController *rightViewController;
In our storyboard, select the scene representing the 'left' view controller. In the attributes inspector, set the view controller's identifier property to "cvc_leftViewController" ("cvc_" refers to ContainerViewController, but really the identifier can be anything you want). Do the same for the right view controller's scene, setting it's identifier to "cvc_rightViewController".
Now insert the following code into ContainerViewController's viewDidLoad method:
if (self.storyboard) {
_leftViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"cvc_leftViewController"];
_rightViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"cvc_rightViewController"];
}
When ContainerViewController is loaded from the storyboard, it will go grab the 'left' and 'right' view controllers from their respective scenes and set references to them via its properties. Now that you have control of the child view controller instances, you can set up the parent/child relationships however you like. To learn how to do that properly refer to the UIViewController documentation.
This trick is not perfect, and has many caveats, but if you are careful you can make it work nicely for your project.
Edit: Although this is completely unnecessary and doesn't mean anything, if you really really want to have the storyboard display connections from your container to your child view controllers just like Apple's built-in containers, just use my method above and then set up segues directly between the container scene to the child scenes, and simply never perform those segues. Now everything will work correctly and look pretty too.
Can you associate child view controllers to a custom container view
controller in Storyboard?
I think what you're asking here is how to connect a view controller in one scene to an outlet of a view controller in a different scene. I don't believe that's possible, perhaps because the storyboard machinery may not have all the scenes in a storyboard loaded at the same time.
You're probably asking this because you want to pass some information from one view controller to another as you segue from one scene to the next. The way to do this when you're working with storyboards is to override -prepareForSegue:sender: in one or both view controllers affected by the segue. The UIStoryboardSegue object provided in the segue parameter has sourceViewController and destinationViewController properties, and also an identifier property. You can use these properties to identify the segue that's about to transfer data between the view controllers.
Ray Wenderlich's blog has a nice two-part tutorial on using storyboards that may help you:
Part 1 covers setting up a storyboard project, adding scenes, and creating segues.
Part 2 deals with using segues to transition between scenes, including the prepareForSeque method mentioned above.
iOS 5 allows multiple view controllers to be active in the same scene (although one should still be in charge), so a single scene in your storyboard might have several controllers. You can use outlets to connect these controllers to each other, and you can configure those connections the same way you did in IB: control-drag from one controller to another in the same scene. The usual outlet list will pop open to let you choose which outlet to connect.
The key to using multiple controllers in one scene (what I believe you are after here) is using the mysterious Object from the Objects list in IB to represent the other view controller and hooking up its outlets.
This answer How to create custom view controller container using storyboard in iOS 5 should help I hope. The answer also provides a working example app which is very helpful.
The problem with #Ben's (otherwise reasonable) answer is that it only works at one level of nesting. Beyond that, it would required that every subsequent VC is customized to save the nesting view controller in prepareForSegue.
To solve this, I spent too much time exploring an NSObject based index that that you could add to the Storyboard, bind to a scene, and which would then register it's parent VC in a global index, based on type and restorationId. That works / can work, but is too much effort in the end, and still requires the two step process of visually binding, and programmatically looking up.
For me, the simplest and most general solution is to lazily descend the view controller hierarchy
In my simple test project, I added the following lines to viewDidLoad:
self.left.data = [
"Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro.",
"De carne lumbering animata corpora quaeritis." ]
where left is defined as:
lazy var left:CollectionViewController = { [unowned self] in
return self.childViewControllerWithId("Left") as! CollectionViewController }()
and childViewControllerWithId is defined as:
extension UIViewController {
func childViewControllerWithId(rid:String) -> UIViewController? {
// check immediate child controllers
for vc in self.childViewControllers as! [UIViewController] {
if vc.restorationIdentifier == rid { return vc }
}
// check nested controllers
for vc in self.childViewControllers as! [UIViewController] {
if let vc = vc.childViewControllerWithId(rid) {
return vc
}
}
assert(false, "check your assumptions")
return nil
}
}
Note that you could do other find variants based on type, if need be. Also note that the above requires that you define the restoration id in the Storyboard file. If you did not have repeated instances of the same view controller, then using type would be easier.
And to state what is hopefully obvious, you don't need to implement prepareForSegue, nor do you have to use the lazy loading, you just have to call find(...).
Is it possible to create a storyboard segue from a view controller to itself? I have a bunch of Entities that have Related Entities. I'd like to be able to display a Related Entity using the same view controller that's displaying the Entity. But I can't seem to create a segue that will display a new instance of the origin view controller.
Is it just not allowed? Thanks!
Well here's a solution that isn't quite the same but gets me what I want. I found it as an answer to this question.
The reason I thought I had to use a segue rather than the good old programmatic push of a view controller onto the navigation controller's stack is that I had set up the view controller's IBOutlets in the storyboard. I didn't realize that you could create a copy of the view controller as laid out in the storyboard without using a storyboard segue. You can! To see how to do it, check out that other question and up vote the answerer!
You can ctrl-click-drag (or right-click-drag) from an element (UIButton, etc.) to the containing view controller.
(Did you try this? I'm doing it right now; I have one stock UIViewController that just keeps adding itself indefinitely to the containing UINavigationController stack via a normal push segue.)
Yeah, it's annoying I can't do a 'manual' segue to itself.
What I did was added a UIButton to my view and gave it an action of push to the same view controller, and then made this button hidden. Then I can name the segue and reference it in the code.
Hacky, but works.