Autolayout: Uiview Within Uiview Has Incorrect Frame

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 -viewWillAppear. Better than -viewDidAppear, and since you really need everything to be done after all the layout if completed but before it appears on screen, this is a legitimate reason why the method is there. If relevant, you could try calling -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.




  1. 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; after viewWillAppear
and viewDidLayoutSubviews 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 like DispatchQueue.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



Leave a reply



Submit