From b1b9e658c9181bce0be9c5277db345d690b7b055 Mon Sep 17 00:00:00 2001 From: Greg Gardner Date: Fri, 27 Jan 2023 21:45:15 -0800 Subject: [PATCH] Work-around issue with QLPreviewController not supporting swipe-to-dismiss and pinch-to-dismiss when presented from SwiftUI by creating a transparent UIViewController wrapper around QLPreviewController that presents it using UIKit instead. (#452) close #106 Co-authored-by: Greg --- IceCubesApp/App/IceCubesApp.swift | 3 +- IceCubesApp/App/QuickLookRepresentable.swift | 108 +++++++++++-------- Packages/Env/Sources/Env/QuickLook.swift | 18 ++-- 3 files changed, 76 insertions(+), 53 deletions(-) diff --git a/IceCubesApp/App/IceCubesApp.swift b/IceCubesApp/App/IceCubesApp.swift index f0f43018..a8316247 100644 --- a/IceCubesApp/App/IceCubesApp.swift +++ b/IceCubesApp/App/IceCubesApp.swift @@ -53,9 +53,10 @@ struct IceCubesApp: App { .environmentObject(theme) .environmentObject(watcher) .environmentObject(PushNotificationsService.shared) - .sheet(item: $quickLook.url, content: { url in + .fullScreenCover(item: $quickLook.url, content: { url in QuickLookPreview(selectedURL: url, urls: quickLook.urls) .edgesIgnoringSafeArea(.bottom) + .background(TransparentBackground()) }) } .commands { diff --git a/IceCubesApp/App/QuickLookRepresentable.swift b/IceCubesApp/App/QuickLookRepresentable.swift index 292a104a..1aa86c3e 100644 --- a/IceCubesApp/App/QuickLookRepresentable.swift +++ b/IceCubesApp/App/QuickLookRepresentable.swift @@ -12,61 +12,81 @@ struct QuickLookPreview: UIViewControllerRepresentable { let selectedURL: URL let urls: [URL] - func makeUIViewController(context: Context) -> UINavigationController { - let controller = AppQLPreviewController() - controller.dataSource = context.coordinator - controller.delegate = context.coordinator - let nav = UINavigationController(rootViewController: controller) - return nav + func makeUIViewController(context: Context) -> UIViewController { + return AppQLPreviewController(selectedURL: selectedURL, urls: urls) } func updateUIViewController( - _: UINavigationController, context _: Context + _: UIViewController, context _: Context ) {} +} - func makeCoordinator() -> Coordinator { - return Coordinator(parent: self) +class AppQLPreviewController: UIViewController { + let selectedURL: URL + let urls: [URL] + + var qlController : QLPreviewController? + + init(selectedURL: URL, urls: [URL]) { + self.selectedURL = selectedURL + self.urls = urls + super.init(nibName: nil, bundle: nil) } - - class Coordinator: NSObject, QLPreviewControllerDataSource, QLPreviewControllerDelegate { - let parent: QuickLookPreview - - init(parent: QuickLookPreview) { - self.parent = parent - } - - func numberOfPreviewItems(in _: QLPreviewController) -> Int { - return parent.urls.count - } - - func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { - return parent.urls[index] as QLPreviewItem - } - - func previewController(_: QLPreviewController, editingModeFor _: QLPreviewItem) -> QLPreviewItemEditingMode { - .createCopy + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + if self.qlController == nil { + self.qlController = QLPreviewController() + self.qlController?.dataSource = self + self.qlController?.delegate = self + self.qlController?.currentPreviewItemIndex = urls.firstIndex(of: selectedURL) ?? 0 + self.present(self.qlController!, animated: true) } } } -class AppQLPreviewController: QLPreviewController { - private var closeButton: UIBarButtonItem { - .init( - title: NSLocalizedString("action.done", comment: ""), - style: .plain, - target: self, - action: #selector(onCloseButton) - ) +extension AppQLPreviewController : QLPreviewControllerDataSource { + func numberOfPreviewItems(in _: QLPreviewController) -> Int { + return self.urls.count } - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - if UIDevice.current.userInterfaceIdiom != .pad { - navigationItem.rightBarButtonItem = closeButton - } - } - - @objc private func onCloseButton() { - dismiss(animated: true) + func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { + return self.urls[index] as QLPreviewItem + } +} + +extension AppQLPreviewController : QLPreviewControllerDelegate { + func previewController(_: QLPreviewController, editingModeFor _: QLPreviewItem) -> QLPreviewItemEditingMode { + .createCopy + } + + func previewControllerWillDismiss(_ controller: QLPreviewController) { + self.dismiss(animated: true) + } +} + +struct TransparentBackground: UIViewControllerRepresentable { + public func makeUIViewController(context: Context) -> UIViewController { + return TransparentController() + } + + public func updateUIViewController(_ uiViewController: UIViewController, context: Context) { + } + + class TransparentController: UIViewController { + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .clear + } + + override func willMove(toParent parent: UIViewController?) { + super.willMove(toParent: parent) + parent?.view?.backgroundColor = .clear + parent?.modalPresentationStyle = .overCurrentContext + } } } diff --git a/Packages/Env/Sources/Env/QuickLook.swift b/Packages/Env/Sources/Env/QuickLook.swift index d9d03455..4c2e5f25 100644 --- a/Packages/Env/Sources/Env/QuickLook.swift +++ b/Packages/Env/Sources/Env/QuickLook.swift @@ -11,7 +11,9 @@ public class QuickLook: ObservableObject { public init() {} public func prepareFor(urls: [URL], selectedURL: URL) async { - withAnimation { + var transaction = Transaction(animation: .default) + transaction.disablesAnimations = true + withTransaction(transaction) { isPreparing = true } do { @@ -27,18 +29,18 @@ public class QuickLook: ObservableObject { } return paths }) - self.urls = paths - url = paths.first(where: { $0.lastPathComponent == selectedURL.lastPathComponent }) - withAnimation { + withTransaction(transaction) { + self.urls = paths + url = paths.first(where: { $0.lastPathComponent == selectedURL.lastPathComponent }) isPreparing = false } } catch { - withAnimation { + withTransaction(transaction) { isPreparing = false + self.urls = [] + url = nil + latestError = error } - self.urls = [] - url = nil - latestError = error } }