swiftswiftuiquicklook

QLPreviewController showing file then going blank in SwiftUI


I've added a UIViewControllerRepresentable for UIKit's QLPreviewController which I've found in a related question:

struct QuickLookView: UIViewControllerRepresentable {
    
    var url: URL
    var onDismiss: (() -> Void) = { }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func updateUIViewController(_ viewController: UINavigationController, context: UIViewControllerRepresentableContext<Self>) {
        (viewController.topViewController as? QLPreviewController)?.reloadData()
    }
    
    func makeUIViewController(context: Context) -> UINavigationController {
        let controller = QLPreviewController()
        
        controller.dataSource = context.coordinator
        controller.reloadData()
        return UINavigationController(rootViewController: controller)
    }
    
    class Coordinator: NSObject, QLPreviewControllerDataSource {
        var parent: QuickLookView
        
        init(_ qlPreviewController: QuickLookView) {
            self.parent = qlPreviewController
            super.init()
        }
        
        func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
             1
        }
        
        func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
            self.parent.url as QLPreviewItem
        }
    }
}

In my app, I download a file (jpg/png/pdf) via Alamofire:

let destination: DownloadRequest.Destination = { _, _ in
    let documentsURL = FileManager.default.documentsDirectory
        .appendingPathComponent(document.id.string)
        .appendingPathComponent(document.name ?? "file.jpg")
    return (documentsURL, [.removePreviousFile, .createIntermediateDirectories])
}
AF
    .download(url, to: destination)
    .responseURL { (response) in
        guard let url = response.fileURL else { return }
        self.fileURL = url
        self.isShowingDoc = true
    }

...and pass its local url to the QuickLookView to present it:

@State private var isShowingDoc = false
@State private var fileURL: URL?

var body: some View {
    // ...
    .sheet(isPresented: $isShowingDoc, onDismiss: { isShowingDoc = false }) {
        QuickLookView(url: fileURL!) {
            isShowingDoc = false
        }
    }
}

What happens is that the QuickLookView opens as sheet, the file flashes (is displayed for like 0.1 seconds) and then the view goes blank:

I checked the Documents folder of the app in Finder and the file is there and matches the url passed to the QuickLookView. I've noticed that when the view is open, and I then delete the file from the folder via Finder, then the view will throw an error saying there's no such file – that means it did read it properly before it was deleted.

Note: I read somewhere that the QL controller has had issues when placed inside a navigation controller. In my view hierarchy, my views are embedded inside a NavigationView – might that cause issues?

How do I solve this?


Solution

  • I finally got it to work – big thanks to Leo Dabus for his help in the comments.
    Here's my currently working code:

    @State private var isShowingDoc = false
    @State private var isLoadingFile = false
    @State private var fileURL: URL?
    
    var body: some View {
        Button {
            let destination: DownloadRequest.Destination = { _, _ in
                let documentsURL = FileManager.default.documentsDirectory
                    .appendingPathComponent(document.id.string)
                    .appendingPathComponent(document.name ?? "file.jpg")
                return (documentsURL, [.removePreviousFile, .createIntermediateDirectories])
            }
            isLoadingFile = true
            AF
                .download(url, to: destination)
                .responseURL { (response) in
                    self.isLoadingFile = false
                    guard let url = response.fileURL else { return }
                    isShowingDoc = true
                    self.fileURL = url
                }
        } label: {
            VStack {
                Text("download")
                if isLoadingFile {
                    ActivityIndicator(style: .medium)
                }
            }
        }
        .sheet(isPresented: $isShowingDoc, onDismiss: { isShowingDoc = false }) {
            QuickLookView(url: fileURL!)
        }
    }
    

    with this QuickLookView: (mostly unchanged)

    struct QuickLookView: UIViewControllerRepresentable {
        
        var url: URL
        
        func makeCoordinator() -> Coordinator {
            Coordinator(self)
        }
        
        func updateUIViewController(_ viewController: UINavigationController, context: UIViewControllerRepresentableContext<Self>) {
            (viewController.topViewController as? QLPreviewController)?.reloadData()
        }
        
        func makeUIViewController(context: Context) -> UINavigationController {
            let controller = QLPreviewController()
            
            controller.dataSource = context.coordinator
            controller.reloadData()
            return UINavigationController(rootViewController: controller)
        }
        
        class Coordinator: NSObject, QLPreviewControllerDataSource {
            var parent: QuickLookView
            
            init(_ qlPreviewController: QuickLookView) {
                self.parent = qlPreviewController
                super.init()
            }
            
            func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
                 1
            }
            
            func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
                self.parent.url as QLPreviewItem
            }
        }
    }
    

    As you can see, there's hardly any difference to my code from when I asked the question. Yesterday night, the fileURL was always nil for an unclear reason; yet, now it started working just fine. In exchange, the remote images in my list (not shown here) stopped working even though I haven't touched them, haha.

    I don't know what's going on and what I even changed to make it work, but it works and I won't complain!