Home > Software design >  UIHostingController intrinsicContentSize is incorrect on initialize even with the simplest possible
UIHostingController intrinsicContentSize is incorrect on initialize even with the simplest possible

Time:06-07

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):

enter image description here

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:

enter image description here

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.

  • Related