What is the difference between a UIView outlet, and a ChildViewController? - ios

Lets say I have a container view in a parent UIView.
What is the difference between referencing it as an Outlet in my parent UIView, or accessing it this way :
categoryContainerViewController = self.childViewControllers[0] as! CategoriesControllerView

View and view controllers are two different things
A VIEW is an object that is drawn to the screen. It may also contain other views (subviews) that are inside it and move with it. Views can get touch events and change their visual state in response. Views are dumb, and do not know about the structure of your application, and are simply told to display themselves in some state.
A VIEW CONTROLLER is not drawable to the screen directly, it manages a group of view objects. View controllers usually have a single view with many subviews. The view controller manages the state of these views. A view controller is smart, and has knowledge of your application's inner workings. It tells the dumb view objects what to do and how to show themselves.
now you can get idea about View and a view controller.

A view and a view controller are two totally different things.
categoryContainerViewController = self.childViewControllers[0] as! CategoriesControllerView
In spite of the name, that is a view controller.
The outlet is to the view.

In layman terms : -
IBOutlet connects any view element from your interface builder to attached swift class file. So you can get reference to any subview of UIView(eg, UILabel, UIButton) from interface builder to your UIViewController or UIView Swift class
In your ex.
by using
categoryContainerViewController = self.childViewControllers[0] as! CategoriesControllerView
You are getting reference to your ChildViewController and not any view

Related

Pass & display the same view from VC1 to VC2

I have a view (in my case a MGLMapview : mapboxView) which is displayed in my first VC. I want my second VC to display the same view.
So from VC1 to VC2 I did :
self.VC2.mapView = self.mapView
self.navigationController?.pushViewController(self.VC2, animated: true)
Then in the viewDidLoad of VC2 :
view.addSubview(mapView)
My problem is that nothing appears.
However, when I print the size & height of the mapView in second VC, it is not zero.
Do I miss something ?
Thanks for helping
I've asked myself this question too. I don't know if it's possible (I never found a way to do it) but it's not recommended. Once you put a view into a view controller (via addSubview on its view), the view controller "owns" the view.
Here's the UIView documentation that explains why:
Views can be nested inside other views to create view hierarchies, which offer a convenient way to organize related content. Nesting a view creates a parent-child relationship between the child view being nested (known as the subview) and the parent (known as the superview). A parent view may contain any number of subviews but each subview has only one superview.
Instead, add the properties you need to set up a new map view in VC2 (for example center, span, any annotation details) and set up a new map view in the second view controller.

Getting a VC to appear inside another VC

I have 2 ViewControllers.
VC1 has a 2 views inside it.
I want to get MenuUIVC to appear in one of my views belonging(child) to VC1.
I tried this code inside of VC1 but it didn't work.
MenuUIVC * menuViewVC = [[MenuUIVC alloc] init];
menuUIView = menuViewVC.view;
I expected to be able to see the MenuUIVC inside the view (menuUIView) which is a child of VC1. I have the IBOutlets all hooked up on the storyboard.
You can use Container views to get View Controller inside a View Controller. And, you can get the reference to it through prepareForSegue() method.
Links for description:
iOS Container View
The Easy Way to Switch Container Views in iOS
If you are using UIView, then why don't you use
[menuUIView addSubview:menuViewVC.view];
Another option is to use childviewcontrollers.
See these links for example:-
adding view controller as child view in ios
Add child view controller to current view controller
Using container or child view controller, gives you full fledge option of using View Controller properties, which you will not get in UIView.
Say for example, you can you orientation delegate methods, which you can't using in UIView class directly.
Hope this can help you.
Cheers
Sanjay
you can go for container view if you want to do it using IBOutlets & storyborad.
but if you want to do it using code then follow the below steps :
initialise & create parent view controller.
then add required child view as a subview with negative frame.
now change the frame with UIView animation whenever required. & do add tap gesture recogniser on part of screen other than the child view to remove it back to the initial position.

How the view and viewcontroller hooked?

I cannot find where the view and viewcontroller get hooked? Is it in the xib file?
I learned that each viewcontroller can control several views, but where are those two get hooked?
I recommend you to read the whole ViewController Programming Guide if you have doubts like that:
ViewController Programming Guide
In case you want to jump right to your issue, check this section:
Resource Managment in ViewControllers
You can find a nice graph explaining where the views are created and linked in the ViewController:
A ViewController is just that, a class to manage the UIViews (there will be many) that it contains. The main view is automatically wired up for you and you are responsible for wiring up all the other views you add. Keep in mind that UIButtons, UILabels, UIViews, etc are all objects that inherit from UIView.
Like Antonio indicated, start with the Apple docs:
The view controller has its own view. Each child view (subview) view has a parent view (superview). You can nest views inside of views. In your case, the top view in the hierarchy is the view controller's view.
At design time, you can add a child view to any view in Interface Builder by simply dragging a new view onto the parent view. You can also adjust the view hierarchy from the Document Outline in Interface Builder.
When creating a view hierarchy in Interface Builder, the view hierarchy is stored in the .xib file.
At run time, your views are instantiated from the information in the .xib file, and each child view's superview property points to its parent view. Each view also has a subviews property that lists each of its child views.
You can add a view to any other view at run time by instantiating a new view and passing it to the parent view's addSubview method. Obviously, once instantiated, you can alter the view hierarchy by setting the superview and subviews properties and calling related methods.

iOS VIewController for UIScrollViews content

I have a UIScrollView in my app and I am adding some custom views from xib to it so you can horizontally scroll (tabbing) in ScrollView to change which one is shown. For now this works but I have a problem with connecting views to controllers.
I don't know how to choose structure of ViewControllers (how many controllers should I use, use nested controllers,...).
I have a rootView and its controller. In this rootView there is a ScrollView and this ScrollView contains some custom views (subviews) loaded from xib (using loadNibNamed method).
My question is should I use the same ViewController as for rootView also for these subviews in ScrollView? Problem is that the ViewControllers view property is already bind to the rootView (super view in rootView) so when I bind this view property also to subviews an error is occurred. Also if I create new controller for these subviews an error is occurred as well.
When I am loading subviews to the ScrollView with loadNibNamed method in ViewController of rootView, owner of these subviews is ViewController (owner argument of loadNibNamed method is set to self).
Can you tell me please, how should I solve this? What controller should I use for subviews, should I create new one or should I use existing one. Or should I use some nested controller? I am newbie in iOS development so I have a chaos in using ViewControllers right now...
If there isn't much code that is relative to controlling the sub views you could use just the root view controller. i.e A single controller for a single scene would be a good MVC approach.
If you are using it this way , don't change the view property of view controller as this messes it up for the root view - controller setup. If you just need a reference to this views you already have it with the return value of loadNibNamed. Also if you are setting the owner to self then create additional instance variable to hold the sub views(and not the view property) so that you can specify the owner from the xib itself and connect the references appropriately.
However if you have substantial business logic to be written regarding the sub views then its fine to create separate view-controllers(a single class would be fine if all the subviews behave the more or less same way if you are getting what i mean) for it. In the xib for the subviews, you can specify this class as the owner and when using loadNibNamed: you should create an object of the subviewcontroller class and specify this as the owner. This way you can modularize the whole thing.

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