How to animate `UIHostingController.view` frame when my View's size changes?

I have a UIHostingController on which I have set:

hostingController.sizingOptions = [.intrinsicContentSize]

The size of my SwiftUI content changes with animation (I update a @Published property on an ObservableObject inside a withAnimation block). However, I notice that my hostingController.view just jumps to the new frame without animating the change.

Question: how can I animate the frame changes in UIHostingController that are caused by sizingOptions = [.intrinsicContentSize]

Hello @isaacweisberg,

UIKit has its own .animate you can use on observed changes of .preferredContentSize, which reflects the view's current size.

For more information, ⌘+Click on a .UIHostingController in Xcode to read through the DocC comments that provides much context.

 Travis Trotto - DTS Engineer

Alright, I have tried to follow your advice and I don't see how this can help.

I have this setup:

struct AnimatedView: View {
    @ObservedObject var viewModel: ViewModel
    
    var body: some View {
        let width = viewModel.isExpanded ? 120.0 : 50.0
        let height = viewModel.isExpanded ? 400.0 : 100.0
        let cornerRadius = viewModel.isExpanded ? 42.0 : 8.0
        
        Color(uiColor: .red)
            .frame(width: width, height: height)
            .cornerRadius(cornerRadius)
    }
}

The hosting controller is mounted to the top of its parent via constraints.

Then I do this in my tap handler for a totally separate button:

@objc func tap() {
    withAnimation(Animation.easeInOut(duration: 0.5), {
        viewModel.isExpanded.toggle()
    })
}

And what I observe here is that my hosting controller's view immediately teleports to the new frame - its center is immediately in the position where it should be only at the end of the animation. Meanwhile, the SwiftUI content is animating correctly, except it has already teleported.

I have tried to use your answer @DTS Engineer

I have assigned hostingController.sizingOptions = [.intrinsicContentSize, .preferredContentSize] and added a KVO observation to the \.preferredContentSize.

observation = hostingController.observe(
    \.preferredContentSize,
     options: [.new]
) { [weak self] controller, change in
    guard let self else { return }
    
    UIView.animate(
        withDuration: 0.5,
        delay: 0,
        options: [.curveEaseInOut],
        animations: {

        self.view.layoutIfNeeded()
    })
}

Because my UIHostingController is laid out via constraints, I don't see what else I can do to animate the content other than to ask for a relayout on the parent.

This however, doesn't work. Also, in the KVO handler of the preferredContentSize, I have found that when the closure is called, the center value has already been set to the final value! So even if I launch UIView.animate, there will be no frame updates in the animation transaction, and nothing will be animated.

You can see the problem I am describing in this video:

https://github.com/user-attachments/assets/bb7c9d7c-ef6a-47d9-85fd-27fcc6f12b13

So I don't think that your answer answers my question. And I would like to inquire once again - what is the correct canonical way to animate the UIHostingController changing its position along with the SwiftUI animation?

Here is slide show of the behavior that I am observing. On the right you can see the same UI done in plain UIKit.

How to animate `UIHostingController.view` frame when my View's size changes?
 
 
Q