QLPreviewController 显示文件然后在 SwiftUI 中变为空白

QLPreviewController showing file then going blank in SwiftUI

我在 :

中为 UIKit 的 QLPreviewController 添加了 UIViewControllerRepresentable
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
        }
    }
}

在我的应用程序中,我通过 Alamofire 下载文件 (jpg/png/pdf):

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
    }

...并将其本地 url 传递给 QuickLookView 以呈现它:

@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
        }
    }
}

发生的情况是 QuickLookView 打开为 sheet,文件闪烁(显示大约 0.1 秒)然后视图变为空白:

我在 Finder 中检查了应用程序的 Documents 文件夹,文件在那里并且匹配传递给 QuickLookView 的 url。我注意到当视图打开时,然后我通过 Finder 从文件夹中删除文件,然后视图将抛出一个错误,指出没有这样的文件——这意味着它在删除之前确实正确读取了它。

注意:我在某处读到 QL 控制器在放置在导航控制器中时出现问题。在我的视图层次结构中,我的视图嵌入在 NavigationView 中——这可能会导致问题吗?

我该如何解决这个问题?

我终于让它工作了——非常感谢Leo Dabus for his help in
这是我目前的工作代码:

@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!)
    }
}

有了这个 QuickLookView:(大部分没变)

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
        }
    }
}

如您所见,我的代码与我提出问题时几乎没有任何区别。昨天晚上,不知为何fileURL总是nil;然而,现在它开始工作得很好。作为交换,我列表中的远程图像(此处未显示)停止工作,即使我没有触摸它们,哈哈。

我不知道发生了什么,也不知道我什至做了什么让它起作用,但它起作用了,我不会抱怨!

您只需在呈现 sheet 之前更新视图,否则它将无法工作。它可以是按钮标题、不透明度或任何东西。虽然它看起来像 hack,但工作正常。如果有人解释为什么会发生这种情况以及是否有适当的方法使其在不更新视图的情况下工作,我将非常高兴。


import SwiftUI

struct ContentView: View {
    @State private var fileURL: URL!
    @State private var isDisabled = false
    @State private var isDownloadFinished = false
    @State private var buttonTitle: String = "Download PDF"
    private let url = URL(string: "https://www.dropbox.com/s/bxrhk6194lf0n73/macpro_mid2010-macpro_mid2012.pdf?dl=1")!
    var body: some View {
        Button(buttonTitle) {
            isDisabled = true
            buttonTitle = "Downloading..."
            URLSession.shared.downloadTask(with: url) { location, response, error in
                guard
                    let location = location, error == nil,
                    let suggestedFilename = (response as? HTTPURLResponse)?.suggestedFilename,
                    let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
                else { return }
                fileURL = documentDirectory.appendingPathComponent(suggestedFilename)
                if !FileManager.default.fileExists(atPath: fileURL.path) {
                    do {
                        try FileManager.default.moveItem(at: location, to: fileURL)
                    } catch {
                        print(error)
                    }
                }
                DispatchQueue.main.async {
                    isDownloadFinished = true
                    buttonTitle = ""  // you need to change the view prefore presenting the sheet otherwise it wont work
                }
            }.resume()
        }
        .disabled(isDisabled == true)
        .sheet(isPresented: $isDownloadFinished) {
            isDisabled = false
            isDownloadFinished = false
            fileURL = nil
            buttonTitle = "Download PDF"
        } content: {
            if isDownloadFinished {
                PreviewController(previewItems: [PreviewItem(url: fileURL, title: fileURL?.lastPathComponent)], index: 0)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


import SwiftUI
import QuickLook

struct PreviewController: UIViewControllerRepresentable {
    
    var previewItems: [PreviewItem] = []
    
    var index: Int
    
    func makeCoordinator() -> Coordinator { .init(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.delegate = context.coordinator
        controller.reloadData()
        return .init(rootViewController: controller)
    }
    class Coordinator: NSObject, QLPreviewControllerDataSource, QLPreviewControllerDelegate {
        let previewController: PreviewController
        init(_ previewController: PreviewController) {
            self.previewController = previewController
        }
        func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
            previewController.previewItems.count
        }
        func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
            previewController.previewItems[index]
        }
    }
}

class PreviewItem: NSObject, QLPreviewItem {
    var previewItemURL: URL?
    var previewItemTitle: String?
    init(url: URL? = nil, title: String? = nil) {
        previewItemURL = url
        previewItemTitle = title
    }
}