Jordan H
Jordan H

Reputation: 55865

Support swipe to dismiss for UIViewControllerRepresentable presented in sheet

It seems if you use UIViewControllerRepresentable to implement a view controller in your SwiftUI app, when you present it via sheet you cannot swipe to dismiss it. Is there something you need to do to support swipe to dismiss?

struct ContentView: View {
    @State var showingPicker = false
    
    var body: some View {
        Text("Hello, world!")
            .onAppear {
                showingPicker = true
            }
            .sheet(isPresented: $showingPicker, content: {
                PHPicker() //cannot swipe to dismiss
                //Text("Test") //can swipe to dismiss
            })
    }
}

struct PHPicker: UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<PHPicker>) -> PHPickerViewController {
        let config = PHPickerConfiguration()
        return PHPickerViewController(configuration: config)
    }
    
    func updateUIViewController(_ uiViewController: PHPickerViewController, context: UIViewControllerRepresentableContext<PHPicker>) { }
}

Upvotes: 6

Views: 1522

Answers (2)

Jordan H
Jordan H

Reputation: 55865

iOS 17 allows swipe to dismiss for view controllers presented in sheets. You don’t need to do anything to enable this behavior. I tested with SFSafariViewController and MFMailComposeViewController.

Upvotes: 0

Asperi
Asperi

Reputation: 258461

Possible solution is to add something like handle to drag (no styling - simplified for demo),

demo

.sheet(isPresented: $showingPicker, content: {
        VStack {
            RoundedRectangle(cornerRadius: 8).fill(Color.gray)
                .frame(width: 60, height: 8)
                .padding(.top, 8)
            PHPicker()
        }
})

Alternate: the solution is to make presentation by UIKit completely and just pass activation binding inside representable.

Here is a demo of possible approach. Tested with Xcode 12.1 / iOS 14.1

demo2

struct PHPickerContentView: View {
    @State var showingPicker = false
    
    var body: some View {
        Button("Picker") {
           showingPicker = true
        }
        .background(PHPicker(isPresented: $showingPicker))    // << here !!
    }
}

struct PHPicker: UIViewControllerRepresentable {
    @Binding var isPresented: Bool

    func makeUIViewController(context: Context) -> UIViewController {
        UIViewController()   // << picker presenter
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        // react on binding & show if not shown
        if isPresented && uiViewController.presentedViewController == nil {
            let config = PHPickerConfiguration()
            let picker = PHPickerViewController(configuration: config)
            picker.delegate = context.coordinator

            uiViewController.present(picker, animated: true)
            picker.presentationController?.delegate = context.coordinator
        }
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    class Coordinator: NSObject, PHPickerViewControllerDelegate, UIAdaptivePresentationControllerDelegate {
        let owner: PHPicker
        init(_ owner: PHPicker) {
            self.owner = owner
        }

        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {

            // picked image handling code here
        
            picker.presentingViewController?.dismiss(animated: true)
            owner.isPresented = false    // << reset on action !!
        }
        
        func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
            owner.isPresented = false    // << reset on swipe !!
        }
    }
}

Upvotes: 2

Related Questions