What methods are called when stack view automatically updates - swift

When a UIStackView recalculates its fill proportionally distribution of UI elements upon a change in Stack view size, I'd like to make adjustments to those elements right before they appear. It seems as though there must be a method called between the ViewWillAppear and ViewDidAppear.
Any Idea what happens between these two when the stack recalculates?

viewWillAppear and viewDidAppear have to do with the view controller life cycle and coming on and off screen, but not with layout. You want either UIViewcontroller's viewDidLayoutSubviews or UIView's layoutSubviews method. When these methods are called, auto layout is done with he view controller's subviews or with the UIView's frame, respectively. You can tweak the frames and update things like sublayers that are not affected by auto layout at this point.

Related

UIScrollView setContentSize breaks view with Auto Layout

I am using Auto Layout in my iPhone app and have a UIScrollView. I need to change the content size of my scroll view at several points while my apps running (so setting the content size in viewWillAppear is useless as I have seen this suggested in other places).
When I change it, my subviews jump about, presumably because it breaks the auto layout constraints.
So how can I approach changing my scrollview content size with auto layout enabled?
Thanks.
I was having this same issue, and I know this can't be the final solution, but for now, rather than calling this in viewWillLayoutSubviews, by putting the code inside viewDidLayoutSubviews it allowed me to set the contentSize AFTER the viewController did it's default business :)
Hope that helps.
Test your code in viewWillLayoutSubviews. Apple say:
When a view’s bounds change, the view adjusts the position of its
subviews. Your view controller can override this method to make
changes before the view lays out its subviews. The default
implementation of this method does nothing.
UIViewController Class Reference

Why does animating a subview cause its parent view's layoutSubviews method to be called?

I'm doing UIView animation on individual subviews that have a number of sibling views that I don't want affected by the single-view animation. However layoutSubviews is being called on the containing superview when I do the animation, causing the other siblings to be rearranged as well.
(I should explain that I'm doing initial subview layout in the parent view's layoutSubviews method; I only want it to be called the first time I'm setting up the subviews, not when I'm animating them individually later on.)
Why is the parent view's layoutSubviews method being called when animating its subviews?
I can imagine "sorting a grid of icons", you'll just have to animate one icon and the rest works automatically.
On the other side: What autoresizing masks do you have set for the view you're animating? Perhaps it has to do with that. What type of UIView are you animating? Perhaps it changes shape and thus calls [self.superview setNeedsLayout] to tell the superview that it changed shape.
Other idea: Has your superview "autoresizedSubviews" set?
I've found that even setting transformations on the layers of a view can trigger the view's setLayoutSubviews. It took me by surprise too, but maybe just because I don't need the behavior right now. It might sometimes become handy, I guess…

What's the point of having to set the frame of a view of an UIViewController when it adjusts it anyways?

From the docs:
Create a root view object that is
sized to fit the screen. The root view
acts as the container for all other
views associated with your view
controller. You typically define the
frame for this view to match the size
of the application window, which
itself should fill the screen.
However, the view controller also
adjusts the frame size as needed to
accommodate the presence of assorted
views, such as the system status bar,
a navigation bar, or a tab bar.
Why should I bother then to set the view's frame to the application window size? I mean... if the view controller adjusts it anyways, then why should I? No...wait... I get it... it makes sense to set it, because subviews created right after that in -loadView might want to know the frame. But doesn't make a lot of sense since that should be done in -viewDidLoad, right?
The designated initializers for UIView both require a frame be provided either from nib or programatically. This is most likely because all subsequent drawing of any kind depends on the frame. The frame also defines where in the window/super-view the view will appear and even if it should be drawn at all.
In other words, having no frame attribute will break a lot of the class' default methods. even if the frames gets changed later, at any point in time the instance must have a frame attribute.

Laying out & sizing of subviews in a UIViewController

I have an app with with a UITabController and each tab is a UINavigationController. The root of one of my UINavigationControllers is a UIViewController.
Inside that view controller's view, I want to layout some subviews, but I'm confused as to where & how to lay them out in a way that will be resolution independent (i.e. not hardcode values such as 320px, 480px, 44px, etc.).
When the view is fully loaded and presented on a vertical iPhone, it's height will be 367px = 480 - 20 (status bar) - 44 (nav bar) - 49 (tab bar).
Inside the view controller, I currently create all my subviews within the viewDidLoad method. However, it appears that within this method, the view's current height is 460px (self.view.bounds.size.height). So when setting up my subviews, I cannot properly calculate the sizes of anything.
Within the viewWillAppear: method, the view does know it's proper size, but that would mean setting & calculating the subview's frames every time the view will appear (e.g. tab changes or popping from child view controllers on the navigation stack.
Is the only way to do this properly to layout in viewWillAppear:?
I have tried using the autoresizing properties (parent's autoresizesSubviews & autoresizingMask) but they don't seem to work at all!? Do these only take effect once the view is all setup and then it is resized (manually / orientation change?).
I'd be grateful if someone could let me know why the autoresizing isn't working, and how best to lay things out by not hardcoding any sizes.
You can do your layout logic inside the viewWillLayoutSubviews of the UIViewController.
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
// Your layout logic here
}
DOC: Called just before the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary. The default is a nop.
autoresizesSubviews should be set on your parent view, while autoresizingMask should be set on the child views - this is the mistake I made so you could, too.
In loadView you should size your subviews to fit whatever size of parent view is at the moment, and then later on when parent view is resized from 460 to 367 pixels your sub-views will be resized as well, according to your mask settings above.
If that fails, there is nothing wrong in setting the view size within viewWillAppear - the performance impact of doing it every time is negligible.
If nothing else works, there is always layoutSubviews: - there you could do manual layout if you have to, it's invoked when system believes layout may have to change. there is also setNeedsLayout: that I sometimes invoke from viewWillRotate:/viewDidRotate: etc. But really this shouldn't be needed and autoresize should be good enough.
EDIT: Yes, to implement custom layout logic in layoutSubviews as I mention above one would need to subclass UIView.
Swift 5:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Your Code
}

Difference between directly setting a controller's view and adding view as a subview

I have just started learning objective-C and the iphone sdk and I have a question that I hope someone can help shed some light on.
What is the difference in the following:
self.view = someView;
and
[self.view addSubView: someView];
Say for example, in a simple app, where we have only one controller and one container view (has a few image subviews).
What is the difference between the two statements? The reason that I'm asking is because I was tinkering around in some sample code and I noticed the view was being initialized with images as subviews like so:
if (self = [super initWithFrame:CGRectZero])
{
//adds some images as subviews here
}
As I understand it the initWithFrame: CGRectZero, creates a frame with size at [0,0,0,0] (essentially invisible).
When I directly set the view with
self.view = someView;
I notice the view actually displays the image. But when I add the view to as a subview of controller's 'default' view, it doesn't. So basically my question is, whats going on behind the scenes? Why is the first method "resizing" the frame and the second one not doing the same thing.
What you see on the screen of your iPhone is almost always a hierarchy of views.
When you look at, say, your inbox in Mail, you're seeing a bunch of views. There's a big containing view.[1] Within that, there's a navigation bar view, a table view, and a toolbar view. Within the navigation bar view, there's a button view on each side and a label view in the middle. Inside the table view, there are a bunch of table cell views, and each of those cells has several label views. The toolbar has five button views. I could go further and talk about the views inside those buttons and so on, but I'm sure you get the idea.
The view above any given view is its superview; the views below it are its subviews. So a table cell view has a table view as its superview and a bunch of label views as its subviews. The top view, the one that has all the other views inside it, is called the root view.
Each view has its own drawing surface. The rectangle formed by that drawing surface is called the frame. The frame of a view is relative to the frame of its containing view. So if one of our table cell's label subviews has its frame at (0,0), that means it will be in the table cell's top left corner, even if the cell is halfway down the screen.
When you're writing a view controller, self.view is that root view I mentioned earlier; all the other views are subviews of that one (or subviews of its subviews, etc.). One of the features of a view controller is that it automatically resizes its self.view to fit the screen. (The available area will be smaller in the middle of a phone call: the status bar is twice as high then, so there's less space for your app. It will also be smaller if your view controller is being managed by a navigation controller or tab bar controller, but that's a different story.) But just because you resize its root view doesn't mean that the root view's subviews will automatically resize. To do that, you need to set their autoresizing mask (a property which tells the view how it should react when its superview changes size):
someView.autoresizingMask = UIViewAutoresizingFlexibleWidth
| UIViewAutoresizingFlexibleHeight;
(There's a graphical way to set up the autoresizing mask in Interface Builder—click the ruler icon in the inspector window and look at the "Autosizing" section.)
Even that's not enough, though, if someView isn't the right size to start with. To do that, adjust its frame before you add it as a subview of self.view:
someView.frame = CGRectMake(
0, // all the way to the left
0, // all the way at the top
self.view.frame.size.width, // same width as the root view
self.view.frame.size.height, // same height too
);
So why would you ever use subviews if you have to do all this twiddling that the root view does for you? Simple: you can only have one root view, but one view is almost never enough for what you need to do. If you really need only one view, of course, you can just set it as the root view and go on your merry way, but chances are, things are more complicated than that.
[1] I'm simplifying a bit here, but that's fine for right now.
When you add a view as a subview, you need to make sure that you're actually adding to an existing view.
self.view = view sets the controller's view. Without this (either in code or done with a XIB) you'll never see anything as the controller has no view to show.
[self.view addSubView: someView] assumes that self.view is already set. If it doesn't, you're adding someview as a subview of nil, and it will never get seen.
Basically, think of self.view as the big container, and all the subviews are just pieces inside of it. If you don't need any subviews, setting self.view to a UIImageView or UIWebView is fine. If you do need subviews, you'll need a big, empty container view in which to put them.
In your case, I'm betting self.view is never set, and you're adding your image views to nil.
Setting the view controller "view" property only changes the view it is managing.
Adding a view as a subview of another view, actually adds the subview underneath the other view.
They are very different things, as one adjusts a view controller and the other alters a view hierarchy.
As a guess, the reason you didn't see anything the first way was the frame for the subview you were adding was CGRectZero (0 in size).