Presenting View Controller in SwiftUI

Viewed 12847

How to achieve what the following Objective-C code achieves with SwiftUI? I haven't been able to get a firm grasp on the ideas presented.

    [self presentViewController:messageViewController animated:YES completion:nil];
2 Answers

Until ios 13.x, there is no way provided by SwiftUI. As I had the same need, wrote a custom modifier of View to achieve it.

extension View {
    func uiKitFullPresent<V: View>(isPresented: Binding<Bool>, style: UIModalPresentationStyle = .fullScreen, content: @escaping (_ dismissHandler: @escaping () -> Void) -> V) -> some View {
        self.modifier(FullScreenPresent(isPresented: isPresented, style: style, contentView: content))
    }
}

struct FullScreenPresent<V: View>: ViewModifier {
    @Binding var isPresented: Bool
    @State private var isAlreadyPresented: Bool = false
    
    let style: UIModalPresentationStyle
    let contentView: (_ dismissHandler: @escaping () -> Void) -> V
    
    @ViewBuilder
    func body(content: Content) -> some View {
        if isPresented {
            content
                .onAppear {
                    if self.isAlreadyPresented == false {
                        let hostingVC = UIHostingController(rootView: self.contentView({
                            self.isPresented = false
                            self.isAlreadyPresented = false
                            UIViewController.topMost?.dismiss(animated: true, completion: nil)
                        }))
                        hostingVC.modalPresentationStyle = self.style
                        UIViewController.topMost?.present(hostingVC, animated: true) {
                            self.isAlreadyPresented = true
                        }
                    }
                }
        } else {
            content
        }
    }
}

And, you can use it as the following.

.uiKitFullPresent(isPresented: $isShowingPicker, content: { closeHandler in
    SomeFullScreenView()
        .onClose(closeHandler) // '.onClose' is a custom extension function written. you can invent your own way to call 'closeHandler'.
})

content parameter of .uiKitFullPresent is a closure that has a callback handler as its parameter. you can use this callback to dismiss the presented view.

It's worked well so far. It looks a little bit tricky though.

As you may know, iOS 14 will bring us a method to present any view in the way you want. Check fullScreenCover() out.

Regarding presenting UIViewController written by Objective-C, it would be possible as Asperi mentioned in his post.

UPDATE
Here is the full source code I am using so far. https://gist.github.com/fullc0de/3d68b6b871f20630b981c7b4d51c8373

UPDATE_2
Now, I'd like to say that it's not a good approach because the idea underlying doesn't actually seem to match well with the mechanism of SwiftUI.

As there is no provided related code, so in pseudo-code it would look like the following

struct YourParentView: View {
   @State private var presented = false
   var body: some View {

      // some other code that activates `presented` state

      SomeUIElement()
         .sheet(isPresented: $presented) {
             YourMessageViewControllerRepresentable()
         }
   }
}
Related