Downcast/Upcast a View Controller From Storyboard - ios

If I have a storyboard that contains a view controller named "ABCViewController"
"A_ViewController" is a subclass of "ABCViewController"
is there a way to initiate the view controller "ABCViewController" from the storyboard as "A_ViewController" ?

Here is what I do. Hope it helps.
let abcController = storyboard.instantiateViewControllerWithIdentifier("ABCViewController") as ABCViewController
object_setClass(abcController, A_ViewController.self)
now you can cast abcController to specific child view controller if needed.

You can't do this with storyboard. instantiateViewControllerWithIdentifier will return instance of ABCViewController, you can cast it to superclass (i.e.UIViewController) but casting to subclass (A_ViewController) won't work. You can read why you can't do this here.
If you want to have two view controllers with same layout, but different classes you should use xib

I do not think it is possible to achieve what you are trying to do, reason being:
In Storyboard you give specify the concrete class name in IB for a particular UIViewCotnroller UI. This means when you you instantiate this UIViewController that specific class will be created.
In your case Base class is specified in IB and you are trying to downcast it a derived class which is bound to fail.

With a reference to the storyboard, you should be able to do a regular cast to a base type:
let myController = storyboard.instantiateViewControllerWithIdentifier("MyViewController") as UIViewController
Remember to set the identifier for the view controller in the storyboard too!

Not sure what you are asking, presuming one of the following :
1) You want to link the storyboard viewcontroller to the A_ViewController subclass of ABC_ViewController. You can simply do this by selecting Class from the identity inspector :
2) You already have the ViewController linked to ABCViewController from the storyboard IndentityInspector and you want to programatically fetch it as A_ViewController type (subclass). You cannot downcast (from the parent to the child), so you cannot do something like (presume that sbID1 is the StoryBoard ID corresponding to your view controller) :
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewControllerWithIdentifier("sbID1") as A_ViewController
The reason is that, generally speaking from an inheritance POV, you cannot pass a parent type as a child type (only the other way around). You can downcast from Parent to Child only when you know that you have initially instantiated the object as a Child.

I have tried different ways but not able to solve it. I used following way to tackle it :
In storyboard, create copy of VC with child 2
Bind VC identifier to storyboard
Implement all IBOutlet and action in Base only
Instantiate Child 1 and child 2 from any class as required.
Benefits:
This way we can keep the sam UI for 2 different VC.
we can implement all UI related functionality in Base
We can reuse common code use BaseVC
We can implement logic in respective VC i.e. child 1 and child 2

Related

What is the relationship between the view controller on the storyboard and view controller I define through code?

When I create a view controller in the Interface Builder, I associate it with my code version of the class through selecting the appropriate name from the Identity Inspector. Is the view controller from IB a subclass of the class I coded?
As far as I can tell the view controller in IB is not an instance because you still have to instantiate it:
if let vc = storyboard?.instantiateViewController(identifier: "Detail") as? DetailViewController {
// use vc
}
I don't think it's a property of the code version of the view controller, DetailViewController in the above example. It's being instantiated through storyboard, which in turn is from UIStoryBoard.
Think of the storyboard as a resource file. instantiateViewController just reads that file and creates a certain UIViewController subclass, by calling its init(coder:) initialiser. After that, it creates all the views found on the storyboard, and adds it into the VC's view. How does instantiateViewController know which UIViewController subclass to create? The subclass's name is actually stored in the storyboard, exactly when you type "DetailViewController" in the identity inspector!
The View Controller you see in IB is only as much of an instance as this JSON...
{
"username": "Sweeper",
"id": 5133585
}
is an instance of this struct:
struct User {
let username: String
let id: Int
}
It's not a subclass of DetailViewController either. It's just data in a resource file.
A storyboard is a collection of scenes / vcs related to each others by a segue if exists , when you create a vc you have the option to create it completely programmatically in terms of it's layout or create it's layout inside a storyboard and then assign the vc name in identity inspector so that you use it to create instance of that vc with instantiateViewController which is linked to the layout specified in storyboard there is no super-subclass relation. the code in vc acts as the series of the vc's life cycle . think of the sotyboard part as an easy way to add the layout components like label / button with constraints to the vc's view instead of creating them programmatically that is the main difference
A view controller in the Interface Builder (IB) is not a subclass of the class you define in your .swift file. The storyboard really just helps you visually define the layout and constraints of the subviews that a UIViewController controls.
Basically what your code snippet is doing is "find the storyboard object with 'Detail' as its identifier, make sure its companion class is of type DetailViewController, and then create an instance of DetailViewController with the layout and constraints that are defined in that storyboard object".

Subclass from the subclass of UIViewController with Storyboard doesn't work as except

I have two view controllers, one subclass UIViewController
class MGStatusViewController: UIViewController
with storyboard identifier "MGStatusViewController".(With some UIButtons)
And the other subclass **MGStatusViewController**
class MGStatusDetailViewController: MGStatusViewController
with storyboard identifier MGStatusDetailViewController
When i use it like this:
self.storyboard?.instantiateViewControllerWithIdentifier("MGStatusDetailViewController") as? MGStatusDetailViewController
There's no controllers(the UIButtons) from MGStatusViewController's storyboad file.
How should i achieve this? Thanks!
When you inherit your custom view controller - in this example MGStatusViewController you inherit code behind and logic. You can connect your outlets for MGStatusDetailViewController with MGStatusViewController. And that is it. You can't inherit UIView controls from the storyboard and expect them to show.
What you can do is to create MGStatusDetailViewController take the view from it and add it to the MGStatusViewController. Or, you can create embed control and embed MGStatusDetailViewController in MGStatusViewController. This way you will have all the controls from storyboard and will present your MGStatusDetailViewController
In short, inheritance does not take Views from storyboard, only code from that view controller.
Furthermore, when you inherit from view controllers, you use them as an abstract. You create some logic for that will be used on others view controllers, but you don't create actual UI.
Why you use "MGStatusDetailViewController" story board identifier if you have set identifier as "MGStatusViewController"
Storybord ID and class are diferent thing.
You may try with this -
self.storyboard?.instantiateViewControllerWithIdentifier("MGStatusViewController") as? MGStatusDetailViewController

Connecting Objects with two View controllers in Swift

In my project I have two view controllers, and I am having trouble connecting objects such as an UIImageView to the view controller. When I try to create the IBOutlet, it tells me that "Could not insert new outlet collection: could not find any information for the class named UIViewController". I believe this problem stems from the fact that my original declaration of my class is as follows:
class UIViewController: UIViewController {
when in fact the view controller is named mainScene instead. However, when I change the first UIViewController to what I think it should be (mainScene), it doesn't even show me the option of connecting an IBOutlet...
class mainScene: UIViewController {
So, I have two questions.
Do I need to have a whole separate class for the second UIViewController and would that solve my issues?
Is there a better way to link objects to the UIViewController or am I just doing something horribly wrong (the likely scenario)?
Thanks so much
Short answer: 1. Yes, and yes. 2. There's no better way, and you're not doing something horribly wrong. (You probably just missed a step.)
You have two view controllers. Assuming they are different, you would subclass each one from UIViewController with a different name. E.g., mainSceneViewController and otherSceneViewController.
Your mainSceneViewController and otherSceneViewController would each have their own properties and IBOutlets.
Where you're probably stuck, is needing to change the class of your viewController within Interface Builder to match the class name in its .swift file, so IB knows what outlets it can connect for that view controller.
Each scene in your storyboard corresponds to a view controller. When the segue is performed, iOS instantiates your view controller from the storyboard.
While it is possible to only have one view controller subclass, and use the same subclass for different views, it doesn't happen too often.
Update:
Subclassing lets you add properties and methods to a class, and override their superclass.
In your comment, UIViewController is the class, and mainSceneViewController is subclassed from UIViewController. For your second view controller, it would likely be class otherSceneViewController: UIViewController {, as your other scene would likely require a different properties and methods from your main scene view controller.
You can read more about Inheritance in the Swift Programming Language guide.

Segue to a subclass of some view controller

I'm using a storyboard. Let's say I have a view controller that's named MYviewController.
In - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; I would like to substitute the view controller that I'm segueing to, by one of its child, for example: MYviewControllerChild1 OR MYviewControllerChild2. The child that's segued to depends on the sender parameter.
These view controllers have the same scene (in the storyboard). Only their behaviour is slightly different.
I have a tableView that shows the user the settings of the application. When he clicks a cell, it segues to a viewController where he can modify the value of some setting. Some of theses are alphanumeric, others are numeric. Depending on which cell is clicked, I'd like the input viewController to format the value accordingly (if it's a decimal value I'll use a NSNumberFormatter for example).
Is that possible?
As mentioned in comments to your OP, I believe you should handle this kind of scenario in one viewcontroller.
However, if you insist on using separate controllers, maybe because you think the functionality will be expanded later down the line and therefore add more diversity, you need to handle this by creating multiple storyboard scenes - one for each child controller.
The destination view controller in prepareForSegue is imposed by the viewcontroller at the end of the segue in the storyboard. I don't think there is any way to override that.
As described, your problem isn't really a good candidate for a storyboard. If you use a storyboard you will have to create and sync multiple scenes. Several possible solutions::
Create multiple storyboard scenes and invoke them manually via performSegueWithIdentifier.
Use a nib file instead of a storyboard for this scene. You can use a single nib file since the view controller is created outside the storyboard with [[VCClass alloc] initWithNibFile: bundle: You can create the appropriate view controller class and pass the same nib file to all instances.
Use a single storyboard scene and view controller and pass in typing information in your prepareForSegue.

Linking child view controllers to a parent view controller within storyboard

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

Resources