nathantannar4 / Transmission

Bridges UIKit presentation APIs to a SwiftUI API so you can use presentation controllers, interactive transitions and more.
BSD 2-Clause "Simplified" License
378 stars 13 forks source link

Keyboard doesn't appear after presentation from UIHostingController #40

Closed PhilipDukhov closed 3 months ago

PhilipDukhov commented 3 months ago

Steps to reproduce:

  1. run the code below
  2. focus text field
  3. click show button
  4. hide the presentation view

Now keyboard won't appear.

I tried using the id modifier - it solves the problem when applied to the BasicHosting parent view, but in real app these two places are widely separated.

Switching BasicHosting to UIViewControllerRepresentable solves the problem, but I can't do that in my use case for two reasons:

  1. I need the _overrideSizeThatFits method to size the view properly.
  2. Using it with collection view cell configuration gives the following error:

    UIViewControllerRepresentable is not supported inside of UIHostingConfiguration.

struct App: View {
    @State var isPresented = false

    var body: some View {
        VStack {
            TextField("placeholder", text: .constant(""))

            BasicHosting {
                Button("show") {
                    isPresented = true
                }
                .presentation(transition: .default, isPresented: $isPresented) {
                    Text("Fullscreen")
                        .background(Color.gray)
                }
            }
        }
    }
}

struct BasicHosting<Content: View>: UIViewRepresentable {
    @ViewBuilder let content: () -> Content

    func makeUIView(context: Context) -> UIView {
        let controller = UIHostingController(rootView: content())
        context.coordinator.parentController = controller
        return controller.view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
        context.coordinator.parentController.rootView = content()
    }

    class Coordinator: NSObject {
        var parentController: UIHostingController<Content>!
    }

    func makeCoordinator() -> Coordinator {
        Coordinator()
    }
}

https://github.com/nathantannar4/Transmission/assets/6103621/c8bb8828-1c96-49d3-8b62-20bbca9018de

nathantannar4 commented 3 months ago

Could you please clarify your usage of BasicHosting?

I was able to repo the keyboard issue when using BasicHosting, it also reproduces when using the SwiftUI .sheet(isPresented: ...) modifier.

However, the following does not reproduce the issue:

struct ContentView: View {
    @State var isPresented = false

    var body: some View {
        VStack {
            TextField("placeholder", text: .constant(""))

            Button("show") {
                withAnimation {
                    isPresented = true
                }
            }
//            .sheet(isPresented: $isPresented) {
            .presentation(transition: .default, isPresented: $isPresented) {
                Text("Fullscreen")
            }
        }
    }
}
PhilipDukhov commented 3 months ago

We use it to make a custom long press menu with UIContextMenuInteraction

I should have tested with sheet, now I see that it's happening with it too.

Presenting view controller from detached view controller is not supported, and may result in incorrect safe area insets and a corrupt root presentation. Make sure is in the view controller hierarchy before presenting from it. Will become a hard exception in a future release.

I guess I'll have to switch to using UIViewControllerRepresentable and sizing it somehow - I failed when I first tried it, but maybe there's a way.

nathantannar4 commented 3 months ago

I see so you have a BasicHosting but really thats for a UIContextMenuInteraction? What you should do in that case is move the presentation modifier to outside.

struct App: View {
    @State var isPresented = false

    var body: some View {
        VStack {
            TextField("placeholder", text: .constant(""))

            BasicHosting {
                Button("show") {
                    isPresented = true
                }
            }
            .presentation(transition: .default, isPresented: $isPresented) {
                Text("Fullscreen")
            }
        }
    }
}
PhilipDukhov commented 3 months ago

thanks, maybe I'll do it. it's not that simple since it's deep in the view tree, but may still be easier than using UIViewControllerRepresentable