Search code examples
swiftuitransition

How to present a modal view controller like with UIKit's UIViewControllerTransitioningDelegate?


I am switching from UIKit to SwiftUI and still struggle to understand some parts of it, especially the transitions between views.

I have a situation where is displayed on screen a view controller with a list, say ElementsListViewController(), and when tapping an element I want to display a modal with a custom UI/animation: the opaque overlay would appear with an animation of the alpha value while the white modal "sheet" would appear from bottom to top.

Here is what it looks like:

enter image description here

With UIKit, I would use UIViewControllerAnimatedTransitioning to do that.

Now I would like to do the same with SwiftUI, but I am lost with what to do exactly here.

I have this so far:

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ElementsList()
        }
    }
}

struct ElementsList: View {
    @State var elements: [String] = (0..<100).map { "Element #\($0)" }

    var body: some View {
        List(elements, id: \.self) {
            Text($0)
                .onTapGesture {
                    // what to do here to display ModalView as I want to?
                }
        }
        .listStyle(PlainListStyle())
    }
}

struct ModalView: View {
    var body: some View {
        ZStack {
            Color.black.opacity(0.8)
            Color.white
                .cornerRadius(20.0)
                .padding(.top, 150)
        }
        .ignoresSafeArea()
    }
}

What is my best option here? Thank you for your help


Solution

  • Well there is no built-in such flexibility say with standard .sheet, but it can be implemented custom very fast.

    Here is simple demo (Xcode 13.3 / iOS 15.4)

    demo

    Main part:

    struct ElementsList: View {
    // ...
            ModalView(isPresented: $isModal) {
                List(elements, id: \.self) {
    
    
    struct ModalView<V: View>: View {
        @Binding var isPresented: Bool
    // ...
            ZStack {
                content()
                ZStack {
                    VStack {
                        if isPresented {
                            Color.black.opacity(0.8)
                                .transition(.opacity)
                        }
                    }.animation(.easeInOut(duration: 0.25), value: isPresented)
    
    
    

    Complete test code in project is here