Animating UIHostingController size change with SwiftUI view size change

I have a UIKit app with a UIHostingController embedded as a child controller.

In this UIHostingController there's a SwiftUI view which expands and collapses with an animation to show/hide content within it.

The hosting controller uses .intrinsicContentSize sizing option.

This all works fine, and the animation of the expand/collapse looks good so far as the SwiftUI view, in a preview for example.

But running the app the hosting controller doesn't animate its view's size alongside the SwiftUI view animating its size.

Instead the hosting controller jumps from the correct start/end sizes without any animation.

So technically although it has the right size when the SwiftUI view is expanded/collapsed, this not a nice UX as the hosting controller jumps immediately from its small size to its larger one on expanding and vice versa for collapsing while the SwiftUI contents animates nicely.

I'm assuming there's somewhere I should be calling (in some form or another) a:

UIView.animate(withDuration: 0.4) {
  hostingController.view.layoutIfNeeded()
}

alongside the SwiftUI animations - but I can't see any hook between my SwiftUI view .animation(value:) function and somewhere that hosting controller could jump in alongside this animation and animate its view's frame.

It seems like the only solution I can find here is to bubble events back up from SwiftUI to UIKit, explicitly setting the hosting controller's view's height.

You can use some kind of callback from SwiftUI (eg a closure delegate) to call:

hostingController.view.superview?.layoutIfNeeded()

UIView.animate(.snappy) {
  self.heightConstraint.constant = self.calculateHeight()
  self.hostingController.view.superview?.layoutIfNeeded()
}

Set the height constraint of the hosting controller's view and activate it.

You can calculate the view's height with the auto layout/UIKit methods you might be used to already, wrapping it in deactivate/activate height constraint to get the exact (minimum) height:

func calculateHeight() -> CGFloat {
  heightConstraint.isActive = false

  defer {
    heightConstraint.isActive = true
  }

  return hostingController.view.systemLayoutSizeFitting(
    .init(
      width: hostingController.view.bounds.width,
height: UIView.layoutFittingCompressedSize.height
    ),
    withHorizontalFittingPriority: .fittingSizeLevel,
    verticalFittingPriority: .fittingSizeLevel
  ).height
}
Animating UIHostingController size change with SwiftUI view size change
 
 
Q