How do I animate a UIHostingController size with SwiftUI animations?

Asked on 08/05/2024

1 search

To animate a UIHostingController size with SwiftUI animations, you can leverage the new interoperability improvements between SwiftUI and UIKit. Specifically, you can use the new animate function on UIView and NSAnimationContext to bridge SwiftUI animations into UIKit.

Here's a step-by-step approach based on the information from the WWDC sessions:

  1. Use the animate Method: The animate method on the context allows you to apply any SwiftUI animation to the transaction associated with the update. This ensures that the animation runs in sync across both SwiftUI and UIKit views.

  2. Bridge the Animation: In your updateUIView method, you can grab the SwiftUI animation present on the transaction and bridge it to the UIView.animate method. This way, you can animate UIKit views using SwiftUI animations.

  3. Handle Transactions: If the current transaction isn't animated, the animation and completion are called immediately in line. This ensures that your code works whether the update is animated or not.

For a detailed explanation and example, you can refer to the session Enhance your UI animations and transitions (10:23).

Example Code

Here's a simplified example to illustrate the concept:

import SwiftUI
import UIKit

struct ContentView: View {
    @State private var isExpanded = false

    var body: some View {
        VStack {
            Button("Toggle Size") {
                withAnimation {
                    isExpanded.toggle()
                }
            }
            .padding()

            HostingControllerWrapper(isExpanded: $isExpanded)
                .frame(width: isExpanded ? 300 : 100, height: isExpanded ? 300 : 100)
                .animation(.spring(), value: isExpanded)
        }
    }
}

struct HostingControllerWrapper: UIViewControllerRepresentable {
    @Binding var isExpanded: Bool

    func makeUIViewController(context: Context) -> UIHostingController<InnerView> {
        return UIHostingController(rootView: InnerView())
    }

    func updateUIViewController(_ uiViewController: UIHostingController<InnerView>, context: Context) {
        let animation = context.transaction.animation
        UIView.animate(withDuration: animation?.duration ?? 0) {
            uiViewController.view.frame.size = CGSize(width: isExpanded ? 300 : 100, height: isExpanded ? 300 : 100)
        }
    }
}

struct InnerView: View {
    var body: some View {
        Color.blue
    }
}

In this example, the HostingControllerWrapper uses a UIViewControllerRepresentable to embed a SwiftUI view inside a UIKit view. The updateUIViewController method bridges the SwiftUI animation to UIKit using UIView.animate.

For more information on using animations across frameworks, you can also check out the session What’s new in SwiftUI (18:24).