Swift: What is the meaning of viewDidLayoutSubviews? - ios

I have code under viewDidLayoutSubviews with intention of making an image transparent (alpha = 0) in the beginning which slowly fades in (into alpha = 1) with code under viewDidAppear. Also, I have a button which changes it's own label text when pressed.
However, when the button is pressed and the label changes, the image (which I've specified under viewDidLayoutSubviews) also disappears. Which I think is because the code under viewDidLayoutSubviews has been called?
My question is:
1) What is the meaning of viewDidLayoutSubviews? Why is it called when the button label changes? Just briefly/generally, what is setNeedsLayout & setNeedsDisplayInRect?
2) Is it better to use viewWillAppear then?
Here are 2 similar questions but I couldn't understand enough from them:
1) viewDidLayoutSubviews is called when label changes (xcode) (swift)
2) Why viewdidlayoutsubviews call multiple times?

As name suggests, viewDidLayoutSubviews is called when the view of your viewController has just finished its laying out and you can assume that at that point your views are in their correct places/frames according to your autolayout constraints. You shouldn't perform any action assuming your views have their final frames before that method is called: for example, if you try to aView setFrame: in your viewDidLoad it will probably fail/have no effect as autolayout changes its frame during laying out phase. Therefore, if you really need to change its frame, you should call setFrame: in/after viewDidLayoutSubviews
In short, if your concern is about alpha value of an image or imageView, viewDidLayoutSubviews is probably not the correct place. viewDidAppear seems to be more appropriate for this. viewDidAppear is pretty straighforward: when the view appears on the screen, it is called.

Have you tried to read the docs? viewDidLayoutSubviews, setNeedsLayout etc?
Button label change -> width change -> rearrange -> viewDidLayoutSubviews.
You should not treat viewDidLayoutSubviews as a single call event. You should start an image transparency animation in viewDidAppear with UIView.animateWithDuration, it is a good place to start it, since everything is aligned properly. Let the image alpha be 0 in IB or init method and then just animate it.

Related

Why viewDidLayoutSubviews is called twice only on initialize?

I am going to use it by adding a child VC. (Coding with code)
ChildVC is using autolayout (snapkit).
At initialization, viewDidLayoutSubviews is called twice.
At the first call, the childVC.View is set to full screen Frame.
In the second call, it is set according to the autolayout setting.
I found out that if the child VC's Frame is not set, it is set to full screen.
(Resize View From NIB)
I want to know how to turn off Resize View From NIB programmatically.
The problem is, I want to know why viewDidLayoutSubviews is called twice.
I'm curious as to why the childVC.View size changes when the second call is made.
I want to know one more thing. Isn't this usually used when setting subView's witdh and Height?
someView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.width.equalTo(view.bounds.width)
make.height.equalTo(view.bounds.height * 0.58)
}
Based on VC.View bounds ratio?

layoutIfNeeded not working in iPad

layoutIfNeeded is not working properly in iPad but it is working on iPhone. I want to a circular button. On the iPhone the circle is generated properly but in iPad it looks like a rhombus shape.
_btnthur.layer.cornerRadius = _btnthur.frame.size.width/2;
[self._btnthur layoutIfNeeded];
Expected Result :
Current Output:
I can see two main issues here:
In your code here you are setting the cornerRadius on btnthur, but telling btnSat to layout.
You should not be calling layoutIfNeeded, you should be calling setNeedsLayout and allow the run cycle to coalesce layout calls for performance.
Hope this helps :)
Edit for updated question:
Your corner radius is wrong in the second one, it is much too large. Can you check that the frame is correctly set on the button at the point you are setting the corner radius?
You may want to move your code
_btnthur.layer.cornerRadius = _btnthur.frame.size.width/2;
[self._btnthur setNeedsLayout];
into layoutSubviews or viewDidLayoutSubviews (depending on view or view controller) and make sure that the corner radius is always updated when the frame (or more importantly, the size) is set.

Issue while using viewDidLayoutSubviews

I have heard that viewDidLayoutSubviews is the best place to alter the layout when we have used constraints.
So I jumped to viewDidLayoutSubviews
I have created three UIViews and SubViewed them on SuperView. I am not mentioning the code for frame calculation.
[self.view addSubview:imgMiddleCircle];
[self.view addSubview:imgFirstCircle];
[self.view addSubview:imgLastCircle];
Using this piece of I am adding these circles.
Now when I run my code In viewDidLayoutSubviews I get following screens:
And when I switch to viewWillLayoutSubviews I am getting this on screen:
Why I am getting extra two circles in viewDidLayoutSubviews even I am creating three circles.
And why in viewWillLayout gives the correct Output.
You should code for the fact that viewDidLayoutSubviews is called multiple types.
Ideally addSubview: should be happening in a place like viewDidLoad where you are sure it is only called once.
You can create a flag to avoid calling addSubview: multiples types (not my personal choice)
Otherwise, try to move your set up code to viewDidLoad and force the view to render itself before doing your calculation:
[yourView setNeedsLayout];
[yourView layoutIfNeeded];
Per Apple Documentation,
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. 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.
Your view controller can override this method to make changes after
the view lays out its subviews. The default implementation of this
method does nothing.
So, essentially, viewDidLayoutSubiews gets called multiple times during the creation of your viewController including in cases like rotating the device, scrolling etc. So, you should be really careful with the code you add to this method, because it might be executed multiple times as well. Use this method to reposition your sub-views and not to add/remove them.
Take a look at this blog for more details.

viewDidLayoutSubviews called after touchesBegan - again and again

In an iPhone word game I have an UIScrollView (holding UIImageView) and 7 draggable custom UIViews placed initially at the bottom (and outside the scroll view):
In the single ViewController.m I have overwritten viewDidLayoutSubviews so that it always sets the zoomScale of the scroll view - to have the UIImageView fill exactly 100% of the screen width.
This works well - for portrait and landscape modes. And when the app is just started:
My problem is however, when I first pinch/zoom/double-tap the scroll view and then move one of the seven Tile.m views:
Suddenly (not every time) viewDidLayoutSubviews is called after touchesBegan.
This resets the zoom of the scroll view - unexpectedly for the user.
My question is if there is any way to disable this behavior?
Is it possible to prevent viewDidLayoutSubviews call of the parent, when its child UIView is being touched/dragged?
UPDATE:
I've moved the zoomScale setting code from viewDidLayoutSubviews to didRotateFromInterfaceOrientation and the scroll view zoom is okay now, but its contentOffset is reset to {0,0}, when (not always) I drag a Tile - i.e. the scroll view jumps suddenly, the app is unusable.
Adding the following to viewDidLoad hasn't helped:
if ([self respondsToSelector:#selector(setAutomaticallyAdjustsScrollViewInsets:)])
self.automaticallyAdjustsScrollViewInsets = NO;
viewDidLayoutSubviews is called whenever the system performs layout on the view (layoutSubviews). This can be called for a plethora of reasons; you can subclass your view, implement an empty layoutSubviews method (don't forget to call the super implementation!) and put a breakpoint there to see who causes the layout. You may also want to implement setNeedsLayout and layoutIfNeeded, and put breakpoints there also for your investigation, as these trigger layout on followup runloops.
But you will never be able to prevent layout. The system performs layout on many occasions which are outside of your control. For example, if a user makes a call, exists the phone app and returns to your app; a layout is triggered because the bounds and frame of the window have changed. The call ends; layout is again triggered because now the window is back to previous size.
You should be responsible for figuring out when to set the zoom scale. For example, if the user starts a gesture, you should signal your code not to perform changes, even if a layout was performed.

When is layoutSubviews called?

I have a custom view that's not getting layoutSubview messages during animation.
I have a view that fills the screen. It has a custom subview at the bottom of the screen that correctly resizes in Interface Builder if I change the height of the nav bar. layoutSubviews is called when the view is created, but never again. My subviews are correctly laid out. If I toggle the in-call status bar off, the subview's layoutSubviews is not called at all, even though the main view does animate its resize.
Under what circumstances is layoutSubviews actually called?
I have autoresizesSubviews set to NO for my custom view. And in Interface Builder I have the top and bottom struts and the vertical arrow set.
Another part of the puzzle is that the window must be made key:
[window makeKeyAndVisible];
of else the subviews are not automatically resized.
I had a similar question, but wasn't satisfied with the answer (or any I could find on the net), so I tried it in practice and here is what I got:
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 its frame set only
if the size parameter of the frame is
different
scrolling a UIScrollView
causes layoutSubviews to be called on
the scrollView, and its superview
rotating a device only calls
layoutSubview on the parent view (the
responding viewControllers primary
view)
Resizing a view will call layoutSubviews on its superview (Important: views with an intrinsic content size will re-size if the content that determines their size changes; for example, updating the text on a UILabel will cause the intrinsic content size to be updated and thus call layoutSubviews on its superview)
My results - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/
Building on the previous answer by #BadPirate, I experimented a bit further and came up with some clarifications/corrections. I found that layoutSubviews: will be called on a view if and only if:
Its own bounds (not frame) changed.
The bounds of one of its direct subviews changed.
A subview is added to the view or removed from the view.
Some relevant details:
The bounds are considered changed only if the new value is different, including a different origin. Note specifically that is why layoutSubviews: is called whenever a UIScrollView scrolls, as it performs the scrolling by changing its bounds' origin.
Changing the frame will only change the bounds if the size has changed, as this is the only thing propagated to the bounds property.
A change in bounds of a view that is not yet in a view hierarchy will result in a call to layoutSubviews: when the view is eventually added to a view hierarchy.
And just for completeness: these triggers do not directly call layoutSubviews, but rather call setNeedsLayout, which sets/raises a flag. Each iteration of the run loop, for all views in the view hierarchy, this flag is checked. For each view where the flag is found raised, layoutSubviews: is called on it and the flag is reset. Views higher up the hierarchy will be checked/called first.
https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1
Layout changes can occur whenever any of the following events happens
in a view:
a. The size of a view’s bounds rectangle changes.
b. An interface orientation change occurs, which usually triggers a change in the root view’s bounds rectangle.
c. The set of Core Animation sublayers associated with the view’s layer changes and requires layout.
d. Your application forces layout to occur by calling the setNeedsLayout or layoutIfNeeded method of a view.
e. Your application forces layout by calling the setNeedsLayout method of the view’s underlying layer object.
Some of the points in BadPirate's answer are only partially true:
For addSubView point
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.
It depends on the view's (target view) autoresize mask. If it has autoresize mask ON, layoutSubview will be called on each addSubview. If it has no autoresize mask then layoutSubview will be called only when the view's (target View) frame size changes.
Example: if you created UIView programmatically (it has no autoresize mask by default), LayoutSubview will be called only when UIView frame changes not on every addSubview.
It is through this technique that the performance of the application also increases.
For the device rotation point
Rotating a device only calls layoutSubview on the parent view (the responding viewController's primary view)
This can be true only when your VC is in the VC hierarchy (root at window.rootViewController), well this is most common case. In iOS 5, if you create a VC, but it is not added into any another VC, then this VC would not get any noticed when device rotate. Therefore its view would not get noticed by calling layoutSubviews.
I tracked the solution down to Interface Builder's insistence that springs cannot be changed on a view that has the simulated screen elements turned on (status bar, etc.). Since the springs were off for the main view, that view could not change size and hence was scrolled down in its entirety when the in-call bar appeared.
Turning the simulated features off, then resizing the view and setting the springs correctly caused the animation to occur and my method to be called.
An extra problem in debugging this is that the simulator quits the app when the in-call status is toggled via the menu. Quit app = no debugger.
calling
[self.view setNeedsLayout];
in viewController makes it to call viewDidLayoutSubviews
have you looked at layoutIfNeeded?
The documentation snippet is below. Does the animation work if you call this method explicitly during the animation?
layoutIfNeeded
Lays out the subviews if needed.
- (void)layoutIfNeeded
Discussion
Use this method to force the layout of subviews before drawing.
Availability
Available in iPhone OS 2.0 and later.
When migrating an OpenGL app from SDK 3 to 4, layoutSubviews was not called anymore. After a lot of trial and error I finally opened MainWindow.xib, selected the Window object, in the inspector chose Window Attributes tab (leftmost) and checked "Visible at launch". It seems that in SDK 3 it still used to cause a layoutSubViews call, but not in 4.
6 hours of frustration put to an end.

Resources