Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Presenting UIDocumentInteractionController with UIViewControllerRepresentable in SwiftUI

I'm creating a new iOS app using SwiftUI where ever possible. However, I want to be able to generate a PDF with some data. In a similar project without swiftUI I can do this

let docController = UIDocumentInteractionController.init(url: "PATH_TO_FILE")
                        docController.delegate = self
                        self.dismiss(animated: false, completion: {
                            docController.presentPreview(animated: true)
                        })

and as long as somewhere else in the view controller I have this:

func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
        return self
    }

I'm good to go. What I can't work out is how to apply this to a UIViewControllerRepresentable and have it working in SwiftUI. Should my UIViewControllerRepresentable be aiming to be a UIViewController? How do I then set the delegate and presentPreview? Will this overlay any view and display full screen over my SwiftUI app as it does for my standard iOS app? Thanks

like image 987
Simon Avatar asked Apr 18 '26 17:04

Simon


2 Answers

Here is possible approach to integrate UIDocumentInteractionController for usage from SwiftUI view.

demo

Full-module code. Tested with Xcode 11.2 / iOS 13.2

import SwiftUI
import UIKit

struct DocumentPreview: UIViewControllerRepresentable {
    private var isActive: Binding<Bool>
    private let viewController = UIViewController()
    private let docController: UIDocumentInteractionController

    init(_ isActive: Binding<Bool>, url: URL) {
        self.isActive = isActive
        self.docController = UIDocumentInteractionController(url: url)
    }
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<DocumentPreview>) -> UIViewController {
        return viewController
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<DocumentPreview>) {
        if self.isActive.wrappedValue && docController.delegate == nil { // to not show twice
            docController.delegate = context.coordinator
            self.docController.presentPreview(animated: true)
        }
    }
    
    func makeCoordinator() -> Coordintor {
        return Coordintor(owner: self)
    }
    
    final class Coordintor: NSObject, UIDocumentInteractionControllerDelegate { // works as delegate
        let owner: DocumentPreview
        init(owner: DocumentPreview) {
            self.owner = owner
        }
        func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
            return owner.viewController
        }
        
        func documentInteractionControllerDidEndPreview(_ controller: UIDocumentInteractionController) {
            controller.delegate = nil // done, so unlink self
            owner.isActive.wrappedValue = false // notify external about done
        }
    }
}

// Demo of possible usage
struct DemoPDFPreview: View {
    @State private var showPreview = false // state activating preview

    var body: some View {
        VStack {
            Button("Show Preview") { self.showPreview = true }
                .background(DocumentPreview($showPreview, // no matter where it is, because no content
                            url: Bundle.main.url(forResource: "example", withExtension: "pdf")!))
        }
    }
}

struct DemoPDFPreview_Previews: PreviewProvider {
    static var previews: some View {
        DemoPDFPreview()
    }
}
like image 71
Asperi Avatar answered Apr 20 '26 13:04

Asperi


I ended up doing something like the following as I wasn't able to get this working reliably with UIViewControllerRepresentable and the above answer. You might need to edit / extend this for your usecase.

class DocumentController: NSObject, ObservableObject, UIDocumentInteractionControllerDelegate {
    let controller = UIDocumentInteractionController()
    func presentDocument(url: URL) {
        controller.delegate = self
        controller.url = url
        controller.presentPreview(animated: true)
    }

    func documentInteractionControllerViewControllerForPreview(_: UIDocumentInteractionController) -> UIViewController {
        return UIApplication.shared.windows.first!.rootViewController!
    }
}

Usage:

struct DocumentView: View {
  @StateObject var documentController = DocumentController()

  var body: some View {
      Button(action: {
          documentController.presentDocument(url: ...)
      }, label: {
          Text("Show Doc")
      })
  }
}

like image 36
Theo Lampert Avatar answered Apr 20 '26 14:04

Theo Lampert



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!