I have a custom view with a grid-like layout. When I touch each subview I want to animate them. The superview overrides layoutSubviews to create the grid of subviews.
The problem is whenever I change the frame of a subview for animation, the superview's layoutSubviews method is called overwriting the frame and no animation happens.
Why does layoutSubviews get called in this scenario and how can I prevent it?
There are other questions similar to this, but none of them have been properly answered.
Thanks!
I ended up adding a flag property on my superview and then set it to YES from my subview to prevent it from laying out subviews. Seems very hacky but it works.
Related
I was reading the description of viewDidLayoutSubviews of UIViewController:
Called to notify the view controller that its view has just laid out its subviews [...] However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted. Each subview is responsible for adjusting its own layout [...].
For me, it means: "Called when the layout of subviews is complete, but actually this is not true". So what's really behind viewDidLayoutSubviews?
When bounds change for a ViewControllers View, this method is called after the positions and sizes of the subviews have changed.
So this is our chance to make changes to view after it has laid out its subviews, but before it is visible on screen.
Any changes that depending on bounds has to be done, we can do here and not in ViewDidLoad or ViewWillAppear.
While ViewDidLoad & ViewWillAppear, the frame and bounds of a view are
not finalised. So when AutoLayout has done it's job of fixing mainView and
it's subviews, this method is called.
When using autolayout, framework does not call layoutSubviews every time. This is called in these cases.
Rotating a device: only calls layoutSubview on the parent view (the responding viewControllers primary view)
Its own bounds (not frame) changed. (The bounds are considered changed only if the new value is different, including a different origin.)
A subview is added to the view or removed from the view.
Your application forces layout to occur by calling the setNeedsLayout or layoutIfNeeded method of a view.
Scrolling a UIScrollView causes layoutSubviews to be called on the scrollView, and its superview.
Note:
The call for viewDidLayoutSubviews also depends on various factors like autoresize mask, using Auto-Layout or not, and whether view is in view hierarchy or not.
For any other clarification, check When is layoutSubviews called?
viewDidLayoutSubviews will be called when
When the bounds change for a view controller's view, the view
adjusts the positions of its subviews and then the system calls this
method.
For example you have set constraints of your view then you want to update the frame for your subview in viewDidLoad(), which will not make any impact as in viewDidLoad() your constraints are not properly set, they will get properly set when viewDidLayoutSubviews get called, now you want to update the frames of your subview, then you can do that in this method as this method get called only after all the constraints of your view are properly set.
I want to programatically create UIViews which depend on the size of the bounds of self.view. I've put the code to create the UIViews in viewDidLayoutSubviews.
The problem is that viewDidLayoutSubviews is called multiple times when my viewController appears on screen, thus creating multiple instances of the UIView.
I'm thinking that this could be solved by using some sort of flag.
Is there a better way to do this? Should the code be put somewhere else in the view controller lifecycle?
You should not put creating UIView code in viewDidLayoutSubviews, you should create it in viewDidLoad instead. You can put view frame update code in viewDidLayout. Or you can use autolayout so you don't need any view update code manually. I prefer autolayout.
In the method of viewDidLayoutSubviews, you can get the updated the frame size for UIControls, after that you can programatically create UIViews in the viewDidAppear.
While I do not think you should create UIViews in viewDidLoad method, in autolayout you can not get correct size of views until viewDidLayoutSubviews has been called.
I have a UIView that contains subviews that contains subviews...(Hierarchy of UIViews).
Every UIView is set with auto layout. When I press a button, this UIView is created and then I would like it to drop from the top of the screen.
The problem when I use layoutIfNeeded in an animation is that if will animate everything at the same time as it is a newly created UIView (not yet display).
My question is, is there a way to do the animation of only the UIView dropping with all the subviews already laid out in it?
I guess you can do it by creating the UIView when UIViewController displays and then hide it but I was wondering it there was another way.
Cheers
If you call layoutIfNeeded right after you add it to a view, while still outside the view, it should lay out it's views correctly. And then in the animation you call it again so the view animate to the final position.
I have a scroll view that has a variable number of UIImageView subviews. I subclassed UIScrollView because I want to be able to move and resize the images with gestures, and do some other custom behavior. As soon as I switch the scroll view to my subclass in the nib file, some strange things happen: the scroll view will expand vertically and cover other views when scrolled upward, and the bottom edge stops short of the full screen and leaves a big gap...but when I change it back to a regular UIScrollView, it's fine. I don't override anything in my subclass or set anything in the nib that I believe could cause this...all I do is override the addSubview method, and add gesture recognizers to subviews as they are added to my scroll view, and of course have methods to handle those gesture recognizers. Any ideas as to what I'm doing wrong?
Thanks in advance!
Got it! The problem, it seems, was that I had a method called handlePan...by doing this I had inadvertently overridden an identically-named method of UIScrollView. So, my handlePan (which I had intended only to handle the pans for my subviews) was instead handling all pans, including the scrollview's built-in one, and causing the weird scrolling. Whoops! Problem solved.
When my main view rotates I want to re-arrange the sub views, so in my ViewController, I override
willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration
and set the frames of the subViews in there. This is all well and good, but in the subViews I have also overridden
layoutSubviews
so they layout themselves correctly. But the problem is this now gets called twice - presumably once when I set the Frame in willAnimateRotationToInterfaceOrientation and once because of the rotation. (If I don't set the Frame it gets called once.)
Surely it's the responsibility of the ViewController to layout the frames so this seems like a design flaw - what's the solution so layoutSubviews is only called once?
I had the same question. I found this page to be helpful for me. Is this useful for you?
EDIT:
Here is the summary of the page (copied):
init does not cause layoutSubviews to be called (duh)
addSubview causes layoutSubviews to be called on the view being added, the view it’s being added to (target view), and all the subviews of the target view
setFrame intelligently calls layoutSubviews on the view having it’s frame set only if the size parameter of the frame is different
scrolling a UIScrollView causes layoutSubviews to be called on the scrollView, and it’s superview
rotating a device only calls layoutSubview on the parent view (the responding viewControllers primary view)
removeFromSuperview – layoutSubviews is called on superview only (not show in table)