AutoLayout: UIView within UIView has incorrect frame
Auto Layout changes to bounds are not necessarily finished in -viewDidLayoutSubviews
. As put in "Advance Auto Layout Toolbox":
Constraint-based layout is an iterative process. The layout pass can
make changes to the constraints based on the previous layout solution,
which again triggers updating the constraints following another layout
pass.
The documentation for -viewDidLayoutSubviews
notes with emphasis in the original:
However, this method being called does not indicate that the individual layouts of the view's subviews have been adjusted.
So layout and constraint-based iterations in the subviews could continue to adjust their frames, but if a ViewController's view's bounds don't change, its -viewWillLayoutSubviews
won't get called.
Since you say your setup is "frame-dependent" you have a couple options.
1. Do your setup in but before it appears on screen, this is a legitimate reason why the method is there. If relevant, you could try calling -viewWillAppear
. Better than -viewDidAppear
, and since you really need everything to be done after all the layout if completed-isMovingToParentViewController
as described in Determining Why A View's Appearance Changed.
EDIT: -viewWillAppear
is called before the view is added to the view hierarchy and therefor layout may not be completed. Sorry everyone, and thankfully with Swift UI we are moving away from procedural layout to declarative layout, and this is why.
- Call
-layoutIfNeeded
on the views you need to have the correct values where you need them to. Because this does the layout work it's expensive, and it could lead to repetitive work being done or even an infinite loop if triggered during its own layout process. But it's useful if it's needed to sync some values with Auto Layout so they can be used.
If this doesn't work let us know, it could have to do with the nature of the constraints themselves or something else. The Auto Layout iterations can be tricky to sync with. Good luck.
Incorrect frame being reported using Autolayout & Size Classes
For posterity: the problem I was having -- getting incorrect values when printing the frame of a view -- was caused by accessing that frame during ViewDidLoad
. At this point in the ViewController lifecycle all views may have not been laid out.
Therefore, it is safest to access a UIView
property/outlet only after ViewDidLayoutSubviews
has been called.
I mean, it's right there in the name of the method: "Your views have been laid out... now do stuff with them."
UIView.frame is incorrect
Autolayout does not finish calculating frames until viewDidLayoutSubviews
. If you want to do something before the view appears, move anything that requires a complete frame to there.
Autolayout: Incorrect frame sizes in ViewDidAppear
I solved my own question.
I constrain views proportional to VC's height.
The VC that I'm displaying has a Navigation Bar. All the view sizes are correct,(even in viewDidLoad because I call layoutIfNeeded()), until the VC starts drawing the Navigation Bar and shrinks the Superview's height; thus, shrinking its subview's heights.
All I have to do is to Extend edges under the top bar by ticking the appropriate box in the VC's Attributes Inspector. This way VC will not shrink the superview of my subviews.
Using custom UIView class with Auto Layout returns incorrect bounds
There's no need to reference bounds
-- just use a relative width constraint.
Change this line:
dismissButton.widthAnchor.constraint(equalToConstant: self.bounds.width * 0.7), // <-- This gives incorrect bounds
to this:
dismissButton.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.7),
Now, the button will be 70% of the view's width. As an added benefit, it will adjust itself if/when the view width changes, instead of being stuck at a calculated Constant value.
Force Auto layout to update UIView frame correctly at viewDidLoad
As @DavidRönnqvist pointed out
The reason dispatch async gives you the "correct" value here is that
it get scheduled to run in the next run loop; afterviewWillAppear
andviewDidLayoutSubviews
has run.
Example
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.main.async {
print("DispatchQueue.main.async viewDidLoad")
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("viewWillAppear")
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("viewDidAppear")
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print("viewDidLayoutSubviews")
}
viewWillAppear
viewDidLayoutSubviews
viewDidAppear
DispatchQueue.main.async viewDidLoad
Code inside DispatchQueue.main.async
from viewDidLoad
even is called after viewDidAppear
. So using DispatchQueue.main.async
in viewDidLoad
gives you right frame but it isn't earliest as possible.
Answer
If you want to get right frame as early as possible,
viewDidLayoutSubviews
is the correct place to do it.If you have to put some code inside
viewDidLoad
, you are doing right way. Seem likeDispatchQueue.main.async
is the best way to do it.
Incorrect Frame Position when defined in ViewDidLayoutSubviews
For others, who may be facing similar issues. I was able to keep my code in viewDidLayoutSubviews()
by adding
self.view.layoutIfNeeded()
in my code after
let label = UILabel(frame: imageView.frame)
imageView.superview?.addSubview(label)
Related Topics
iOS Facebook Login "Given Url Is Not Allowed by the Application Configuration"
Get Index or Tag Value from Imageview Tap Gesture
How to Cancel Usernotifications
How to Remove the Bottom Gap of Uipageviewcontroller
Geofire Query on User Location
Measuring Time Accurately in Swift for Comparison Across Devices
Ios-8 and Later - Uitableview Inside an Uialertcontroller
Making Phone Calls on the Iphone
How Can One Enable Keyboard Like in Imessages/Fb Messenger in Landscape Mode at iOS8
iOS 7.0 and Arc: Uitableview Never Deallocated After Rows Animation
iOS 11 - Unable to Change Navigation Bar Height
Making Uitableview with Embedded Uicollectionview Using Uitableviewautomaticdimension
iPad 3 - Opengl Bug with Keagldrawablepropertyretainedbacking and Retina
How to Take a Snapshot of a Uiview That Isn't Rendered
Drawing a Polygon with One Color for Stroke, and a Different One for Fill
Mkmapview Doesn't Zoom Correctly While User Tracking Mode Is Mkusertrackingmodefollowwithheading
iOS - Using Uisearchdisplaycontroller with Uisearchbar That Is Uibarbuttonitem in Uitoolbar