I have been scratching my head over this for some time. I noticed my layouts being slightly off when adding UIHostingController
views to a view controller. Consider the simplest possible example:
import SwiftUI
struct SquareView: View {
var body: some View {
Rectangle()
.foregroundColor(.orange)
.frame(width: 50.0, height: 50.0)
}
}
struct SquareView_Previews: PreviewProvider {
static var previews: some View {
SquareView()
}
}
And now in a view controller:
import UIKit
import SwiftUI
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let squareView = SquareView()
let hostingController = UIHostingController(rootView: squareView)
addChild(hostingController)
hostingController.didMove(toParent: self)
view.addSubview(hostingController.view)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
let leading = hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant:32.0)
let top = hostingController.view.topAnchor.constraint(equalTo: view.topAnchor, constant: 32.0)
NSLayoutConstraint.activate([leading, top])
// Wait some time for the layout
DispatchQueue.main.asyncAfter(deadline: .now() 2.0) {
// Now the size is correct!
hostingController.view.invalidateIntrinsicContentSize()
}
}
}
When initially added to the view the intrinsic content size will be incorrect (the blue border is the intrinsic size):
This was causing my view to appear to have a bigger inset than 32.0
when pinned to the top.
If I wait for the layout and call hostingController.view.invalidateIntrinsicContentSize()
the size will update correctly:
Now I know I can use DispatchQueue.main.async
instead of a delay, and that will work on this layout. But in some situations using DispatchQueue.main.async
doesn't work since the layout still isn't ready. Note that calling hostingController.view.invalidateIntrinsicContentSize
in viewDidLayoutSubviews
is also not a solution since it will create an infinite loop.
It seems like complete madness that this doesn't work out of the box. Am I missing something here? How can I set the intrinsicContentSize
correctly?
CodePudding user response:
Instead of adding delays to wait for the layout to happen, how about just asking it to happen immediately by calling layoutIfNeeded
?
// do this after activating constraints
view.layoutIfNeeded()
Note that it is called on view
, rather than hostingController.view
. This is important, because the square does not need any lay anything out - it is self.view
that needs to lay out the square.