Show action buttons in export previews
QLPreviewController only renders its share, print and open-in actions when it is inside a navigation controller, so diagram and report previews were dead ends. Wrap it in one, add an explicit Done button and assert both in a UI test.
This commit is contained in:
@@ -232,8 +232,10 @@ struct LoadsView: View {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if let previewURL {
|
if let previewURL {
|
||||||
QuickLookPreview(url: previewURL)
|
QuickLookPreview(url: previewURL) {
|
||||||
.ignoresSafeArea()
|
self.previewURL = nil
|
||||||
|
}
|
||||||
|
.ignoresSafeArea()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: previewURL) { _, newValue in
|
.onChange(of: previewURL) { _, newValue in
|
||||||
|
|||||||
@@ -5,38 +5,53 @@
|
|||||||
|
|
||||||
import QuickLook
|
import QuickLook
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
/// Presents a single exported file (PDF or PNG) in Quick Look so the user can
|
/// Presents a single exported file (PDF or PNG) in Quick Look so the user can
|
||||||
/// inspect it before sharing.
|
/// inspect it before sharing.
|
||||||
///
|
///
|
||||||
/// SwiftUI's `quickLookPreview(_:)` modifier is macOS-only, so iOS wraps
|
/// SwiftUI's `quickLookPreview(_:)` modifier is macOS-only, so iOS wraps
|
||||||
/// `QLPreviewController` directly.
|
/// `QLPreviewController` directly. The controller only shows its action
|
||||||
|
/// buttons — share, print, open in another app — when it sits inside a
|
||||||
|
/// navigation controller, so it is wrapped in one and gets an explicit Done
|
||||||
|
/// button to close the sheet.
|
||||||
struct QuickLookPreview: UIViewControllerRepresentable {
|
struct QuickLookPreview: UIViewControllerRepresentable {
|
||||||
let url: URL
|
let url: URL
|
||||||
|
var onDone: () -> Void
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> QLPreviewController {
|
func makeUIViewController(context: Context) -> UINavigationController {
|
||||||
let controller = QLPreviewController()
|
let preview = QLPreviewController()
|
||||||
controller.dataSource = context.coordinator
|
preview.dataSource = context.coordinator
|
||||||
return controller
|
preview.navigationItem.leftBarButtonItem = UIBarButtonItem(
|
||||||
|
barButtonSystemItem: .done,
|
||||||
|
target: context.coordinator,
|
||||||
|
action: #selector(Coordinator.done)
|
||||||
|
)
|
||||||
|
preview.navigationItem.leftBarButtonItem?.accessibilityIdentifier = "quick-look-done-button"
|
||||||
|
return UINavigationController(rootViewController: preview)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ controller: QLPreviewController, context: Context) {
|
func updateUIViewController(_ controller: UINavigationController, context: Context) {
|
||||||
|
context.coordinator.onDone = onDone
|
||||||
|
|
||||||
guard context.coordinator.url != url as NSURL else { return }
|
guard context.coordinator.url != url as NSURL else { return }
|
||||||
context.coordinator.url = url as NSURL
|
context.coordinator.url = url as NSURL
|
||||||
controller.reloadData()
|
(controller.viewControllers.first as? QLPreviewController)?.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeCoordinator() -> Coordinator {
|
func makeCoordinator() -> Coordinator {
|
||||||
Coordinator(url: url as NSURL)
|
Coordinator(url: url as NSURL, onDone: onDone)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Coordinator
|
// MARK: - Coordinator
|
||||||
|
|
||||||
final class Coordinator: NSObject, QLPreviewControllerDataSource {
|
final class Coordinator: NSObject, QLPreviewControllerDataSource {
|
||||||
var url: NSURL
|
var url: NSURL
|
||||||
|
var onDone: () -> Void
|
||||||
|
|
||||||
init(url: NSURL) {
|
init(url: NSURL, onDone: @escaping () -> Void) {
|
||||||
self.url = url
|
self.url = url
|
||||||
|
self.onDone = onDone
|
||||||
}
|
}
|
||||||
|
|
||||||
func numberOfPreviewItems(in controller: QLPreviewController) -> Int { 1 }
|
func numberOfPreviewItems(in controller: QLPreviewController) -> Int { 1 }
|
||||||
@@ -44,5 +59,9 @@ struct QuickLookPreview: UIViewControllerRepresentable {
|
|||||||
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
|
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
|
||||||
url
|
url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func done() {
|
||||||
|
onDone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,50 @@ final class SystemExportButtonUITests: XCTestCase {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
func testReportPreviewOffersActionButtons() throws {
|
||||||
|
let app = launch(arguments: ["--uitest-reset-data", "--uitest-sample-data"])
|
||||||
|
|
||||||
|
openFirstSystem(in: app)
|
||||||
|
|
||||||
|
let shareButton = app.buttons["system-overview-share-button"]
|
||||||
|
XCTAssertTrue(shareButton.waitForExistence(timeout: 15))
|
||||||
|
shareButton.tap()
|
||||||
|
|
||||||
|
let reportItem = app.buttons.matching(
|
||||||
|
NSPredicate(format: "label CONTAINS[c] 'PDF'")
|
||||||
|
).firstMatch
|
||||||
|
XCTAssertTrue(reportItem.waitForExistence(timeout: 10))
|
||||||
|
reportItem.tap()
|
||||||
|
|
||||||
|
// Quick Look lives in its own navigation stack; the Done button proves
|
||||||
|
// the navigation bar exists, the share item proves the file can leave
|
||||||
|
// the preview.
|
||||||
|
let doneButton = app.buttons["quick-look-done-button"]
|
||||||
|
XCTAssertTrue(
|
||||||
|
doneButton.waitForExistence(timeout: 60),
|
||||||
|
"Quick Look preview must be embedded in a navigation bar"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scope the action lookup to the preview's own navigation bar so the
|
||||||
|
// Overview share button underneath can never satisfy it.
|
||||||
|
let previewBar = app.navigationBars.containing(
|
||||||
|
.button,
|
||||||
|
identifier: "quick-look-done-button"
|
||||||
|
).firstMatch
|
||||||
|
XCTAssertTrue(previewBar.waitForExistence(timeout: 10))
|
||||||
|
|
||||||
|
let actionButtons = previewBar.buttons.allElementsBoundByIndex
|
||||||
|
.filter { $0.identifier != "quick-look-done-button" }
|
||||||
|
XCTAssertFalse(
|
||||||
|
actionButtons.isEmpty,
|
||||||
|
"Quick Look preview must offer an action button to share or open the file"
|
||||||
|
)
|
||||||
|
|
||||||
|
doneButton.tap()
|
||||||
|
XCTAssertTrue(shareButton.waitForExistence(timeout: 10))
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private func launch(arguments: [String]) -> XCUIApplication {
|
private func launch(arguments: [String]) -> XCUIApplication {
|
||||||
|
|||||||
Reference in New Issue
Block a user