Why do I need to call addSubview if i'm immediately following it with addChildViewController [duplicate] - ios

This question already has answers here:
What does addChildViewController actually do?
(4 answers)
Closed 7 years ago.
I'm pretty new to Objective-C and iOS.
I've noticed a familiar pattern in several apps, and I don't understand why it's necessary...
[someParentView addSubview:aChildViewController.view]
[self addChildViewController:aChildViewController]
From what I've read, you're not supposed to mess with a view controllers view directly, but rather just add the child view controller and the parent view controller will decide how and when to consult the childViewController's view.
In other words, when you've added a child viewcontroller to a parent viewcontroller, well obviously the parent viewcontroller has access to the child viewcontroller's view, hence calling addSubview seems not only redundant, but from what I've read, not recommended.
EDIT: Per a question below about more detail of what I'm doing...
I have a main storyboard view that covers the whole screen, and then I'm adding a view that takes only the left half of a vertical oriented screen. Users can tap on either the half view I added, or the remaining exposed half on the underlying full screen main storyboard view

Your main concern as I see is calling addSubview seems redundant?
I would say no it is not redundant, coz addChildViewController just Adds the given view controller as a child but it will not load/add the view.
This makes it essential to call addSubview
addSubview Add the child controller’s view as a subview.
For more info see:
Creating Custom Content View Controllers
Implementing a Custom Container View Controller

Refer the page, it will help you
http://www.objc.io/issue-1/containment-view-controller.html

Related

Is it bad practice to put UIViewControllers in other UIViewControllers?

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.

Why does presenting a modal UIViewController cause setNeedsLayout on the presenting controller?

It appears that presenting and dismissing a view controller both prompt the presenting view to layout its subviews and/or update its constraints. With a heavy view hierarchy, this is introducing performance issues. Again - this is the existing, currently displayed view. The modal being created and displayed is very light.
This occurs whether I use autolayout (as in my example project) or not.
I have built a demo project that approximates an app I am working on. There is a main parent controller with a horizontally scrolling UIScrollView. Multiple child controllers are added to the parent controller, and their views are added to the scrollview and arranged using NSLayoutConstraints. Each child view has one subview itself, a simple UIView, also arranged with a constraint.
In the navigation bar, there is a button to launch a modal. When presented, the parent controller makes a call to setNeedsLayout on each child view, multiple times. In my demo project, I am overriding setNeedsLayout to log when it is accessed. The same occurs when closing the modal. Open and close the modal a few times and observe the console.
I can see no reason why a new layout is needed, and with more complex views I am finding that hundreds of these calls are firing, with a noticeable performance impact.
Note that when the layout code from ChildView is omitted, setNeedsLayout is not called. I encourage you to comment out the constraints and see the difference in the logging.
Why is this happening? How can I prevent an unnecessary layout pass when presenting and dismissing a modal?
First of all, you are logging setNeedsLayout, which is just a flagging mechanism and does not really incur any work yet. Multiple calls to setNeedsLayout may only trigger a single layout. You should be logging -[UIView layoutSubviews] or -[UIViewController viewDidLayoutSubviews] instead, because these are where the actual heavy-lifting happen.
Second, layout-related methods are meant to be called repeatedly and rapidly during presentations because:
The window needs to rotate all its subviews to respect the presented view controller's preferred interface orientation.
Animations will need to know the initial and final states of your views.
When layouts happen on parent views for whatever reason, all their subviews (which may include views of your view controllers) will of course need to update their layouts too.
If you want to minimize the number of layout passes, you can try give up using presentViewController:animated: and instead use addChildViewController: and animate just the necessary views manually. But even then, you may still trigger the parent controller's layout anyway.
You are doing a very, very, very odd thing: you're maintaining a custom parent view controller with 10 child view controllers all of whose views are in the interface simultaneously. View controllers are not designed for that sort of thing. It is this that is triggering the multiple layoutSubviews calls that you are seeing. It is fine to have multiple child view controllers, but their views should not all be in the hierarchy - especially in your case, where only one such child view is actually visible.
In fact, the interface that you've constructed - a paging scroll view, each of whose "pages" is a view managed by a view controller - is already implemented for you by UIPageViewController, which is far more efficient, as it only actually maintains at most three view controllers at a time: the view controller managing the visible view within the scroll view, and the view controllers managing the views to its right and left. It is also wonderfully convenient and easy to use.
So either:
You should use UIPageViewController, or
You should imitate what UIPageViewController does, removing view controllers' views (and perhaps even releasing the view controllers) when they have scrolled out of sight - as we had to do in the days before UIPageViewController existed - see the Advanced Scroll View Techniques video from WWDC 2011. My Latin "flashcard" app worked this way before UIPageViewController came along; it has thousands of vocabulary cards, each of which is managed by a view controller, but only a maximum of three card view controllers ever exist at any one moment.
(By the way, you should also not be using your own self.childControllers mutable array, as this list is already maintained for you as self.childViewControllers.)
I think layoutSubviews is getting called because the presenting controller's view changes superviews while animating out of the screen once hidden by the presented view.
If you want to avid skip layoutSubviews when the frame hasn't changed, just save a reference to the last frame and if equal return without doing anything. Also there is no need to call setNeedslayout on subviews as the system will trigger it automatically if you resize them.
Anyway, your main problem is your approach:
Only use view controllers if you're going to use them as such (inside a tab bar controller, pushed to a navigation controller, as a window's rootController, presented modally, etc.). If you want to manually add views do not use view controllers and just use custom views! This is a very common error and you can see more details here.
Load views and objects lazily and reuse them. For instance you should only load 1~3 pages of contents and load new ones only when the user scrolls to them. When loading a new one remove one of the old views, or better yet reuse it.
You can separate the logic not only with controllers but also with custom views. Some reasons why you should not use controllers in your particular case:
Controllers won't get retained by a container controller or window as you're manually adding their views.
Controllers won't get orientation, memory, viewDidAppear, etc., events. Again because you're not using them as proper view controllers.
If you properly implemented a custom container controller (which is a lot of work to do properly), then you could use controllers. Otherwise stick to custom views.

Why can't I assign a UIView to self.view?

This might be a stupid question, but I'll shoot.
I made a little test project to test out a concept I had for a sliding view controller type of thing. I naively assumed I could create a UIView (let's call it peekView) with an outlet in a controller, and call something like [slidingControllerSlideFrom:self.view] from any visible view controller, the implementation of such being:
- (void)slidingControllerSlideFrom(UIView*)controllersMainView
{
// push side controller to top of navigation stack
self.peekView = controllersMainView;
// sliding animation
}
But there is no effect. No crash, no warning, no change of view in the pushed controller.
Of course, the pushed controller crashes when trying to add self's view as a subview, but assigning it to a predefined UIView just results in nothing.
So, why? And if a mere 'why' is not enough of a question- what happens when I try to assign one controller's view another controller's subview, and what was the reason for designing UIKit where you cannot set views from self.view?
To do that you have two options:
1 - If the controller in the peekView is always the same one in a given scene, use a "Container View". Those are explained here. Basically, they allow you to add a view in your scene that is managed by another controller.
2 - If the controller in the peekView depends on different conditions, you will have to create something similar to a custom tabbarcontroller. That means that you instantiate the controller that you need, add it's view as a subview of peekView (not assign the controller's view to the peekView itself) and then use didmovetoparentviewcontroller to notify the child controller. This question might help.
UPDATE:
Just saw your comment, so let me answer what you actually asked: The peekview property is actually just a reference to the real UIView you placed in the screen. When you do this:
self.peekView = controllersMainView;
You are changing the reference, but no the view object itself. That's why you are not seeing any changes. There are ways of adding a new view to the controller from code, but it is much simpler to simply use addSubview to add your controllers view to a UIView that is already in the controller.
Check out the discussion here: subView Slide up from bottom of the screen
and here: SubView slide in animation, iphone
Hopefully that gives you a bit of framework on how to approach this task!

Can a view be added to multiple other views safely, if so what does removeFromSuperView: do in that case?

I am wondering what happens if a single instance of a UIView object gets added as a subview of multiple other views simultaneously.
If UIView:removeFromSubview: gets called then does it get removed from all superviews or just the currently displayed one?
For background:
I have a status-bar like view object that needs to be displayed within several different other views (each other view is managed by its own view controller).
[i.e. a) the user is in one view, b) something happens to make the status-bar-like view appear, c) the user switches to another view d)the status bar is still visible in the new view e) the status bar expires after a time and disappears from site. And so on]
Initially I implemented this by adding/removing it as required as a subview of the window, and this was managed by a singleton.
However due to some complications with some animations I have instead added it as a subview of each of the main view's for each of the view controllers.
Note that there are not multiple copies
When the view needs to be removed I am calling its removeFromSuperview:, and everything is all working perfectly.
However I am wondering what the situation is regarding the removal of the view, is it being fully removed or is there something else I need to do?
For example the view might get added to N view controller's views by calling addSubview as required (it will only get added to each view controller if that view controller actually launches)
However when it is being removed I am only calling removeFromSuperview: for the view of currently loaded view controller, not all view controllers it might have been added to.
Next time I navigate to one of these other view controllers it displays fine without the view being there, even though I didn't explicitly call removeFromSuperView.
As I said everything is working as it is, however at the back of my mind I feel there might be something missing?
Hope this was understandable.
You can only have it added to one view. Documentation is your friend!
(void)addSubview:(UIView *)view:
Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous superview before making the receiver its new superview.
From my point of view, having to add a same view to different parent views (and more important, from different view controllers) is an indication that something is wrong on the design...
However, if you really (really) need so, I had always thought that a view instance could have one and only one parent view... Moreover, you can access it by [myView superview] message, which gives you a UIView instance instead of an array... It may auto remove from its old parent before adding to a new superview?
About the design, what about creating it each time you need a new one and have a singleton to manage their status/logic?
Good luck with that!

iOS container view controller - Why does transitionFromViewController need the fromViewController?

When using custom container view controller, I don't quite understand why the presenting view controller needs to specify the from, because being the container class, it should already know what's in the view hierarchy, no?
transitionFromViewController:toViewController:duration:options:animations:completion:
Container view controllers sometimes need to put the views of the contained controllers into specific subviews of their own view. (For example, a SplitViewController reimplementation might have left and right positioning views holding the master and detail controller views respectively.) Providing the fromViewController tells UIViewController where in the view hierarchy the new controller's view should be inserted, and also which specific view should be removed after the animation.
(contrary to another answer, the frames of the views aren't set for you at all. You do that, before the call, and in the animation block. The "Creating Custom Container View Controllers" system guide in the docs has a pretty good example.)
As it happens, actually using transitionFromViewController:... appears to be optional. You can manage your view hierarchy manually, with or without animations, and it works fine. I'm still forming my opinions, but I think I prefer to do it manually, to more easily handle cases where one of the VCs is nil.
This is done this way to allow you to have a view controller that has views with viewControllers in it. The from defines the originating view controller and gives the system the ability to position the animations appropriately.
Imaging you had a view with 4 views in it like tiles. the main view controller can consecutively call this on its "child" view controllers and with the from -> to specification, it won't make the assumption that the caller is the from viewController.

Resources