Search code examples
iosswiftuiuikitswiftydropboxuidocumentinteractioncontroller

SwiftUI view present UIKit ViewController errors


Many times, we are required to open a view that is required to be presented from a UIViewController in a SwiftUI view. Prominent examples of such being UIDocumentInteractionController and less prominent ones being SwiftyDropbox to open a login screen of Dropbox in Safari for authentication. The common technique is to create a UIViewController on demand in UIViewControllerRepresentable subclass and present view controller from it. But it has never worked for me.

struct DropboxView: UIViewControllerRepresentable {
   typealias UIViewControllerType = UIViewController

   @Binding var isShown : Bool

   func updateUIViewController(_ uiViewController: UIViewController, context: Context)  {
    
     if isShown {
        let scopeRequest = ScopeRequest(scopeType: .user, scopes: ["files.content.write"], includeGrantedScopes: false)
  
        DropboxClientsManager.authorizeFromControllerV2(
            UIApplication.shared,
            controller: uiViewController,
            loadingStatusDelegate: nil,
            openURL: { (url: URL) -> Void in UIApplication.shared.open(url, options: [:], completionHandler: nil) },
            scopeRequest: scopeRequest)
       }
    }

   func makeUIViewController(context _: Self.Context) -> UIViewController {
       return UIViewController()
   }
}

When I invoke such a view from a SwiftUI view using modifier such as:

    .fullScreenCover(isPresented: $showDropboxLoginView) {
        DropboxView(isShown: $showDropboxLoginView)
    }

I get two errors:

Cannot use Scene methods for URL, NSUserActivity, and other External Events without using SwiftUI Lifecycle. Without SwiftUI Lifecycle, advertising and handling External Events wastes resources, and will have unpredictable results.

and

Attempt to present <SwiftyDropbox.MobileSafariViewController: 0x107919600> on <UIViewController: 0x105a2f130> (from <UIViewController: 0x105a2f130>) whose view is not in the window hierarchy.

I wonder how to present such view controllers from within SwiftUI view?


Solution

  • With this setup you don’t need fullScreenCover just use the UIViewControllerRepresentable.

    DropboxView(isShown: $showDropboxLoginView)
        .frame(width:0, height: 0)
    

    Put it anywhere in the body.

    Both the fullScreenCover and DropboxClientsManager seems to be using the root.