From 8c1f6c88a18d85509ca97de41bdc5f42d60708a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Tue, 2 Dec 2025 13:16:31 +0100 Subject: [PATCH 1/3] PDF: Handle swipes with paginated spreads --- .../PDF/PDFNavigatorViewController.swift | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Sources/Navigator/PDF/PDFNavigatorViewController.swift b/Sources/Navigator/PDF/PDFNavigatorViewController.swift index 93ac53efe..0c00e3b1c 100644 --- a/Sources/Navigator/PDF/PDFNavigatorViewController.swift +++ b/Sources/Navigator/PDF/PDFNavigatorViewController.swift @@ -76,6 +76,8 @@ open class PDFNavigatorViewController: // Holds a reference to make sure they are not garbage-collected. private var tapGestureController: PDFTapGestureController? private var clickGestureController: PDFTapGestureController? + private var swipeLeftGestureRecognizer: UISwipeGestureRecognizer? + private var swipeRightGestureRecognizer: UISwipeGestureRecognizer? private let server: HTTPServer? private let publicationEndpoint: HTTPServerEndpoint? @@ -263,6 +265,8 @@ open class PDFNavigatorViewController: target: self, action: #selector(didClick) ) + swipeLeftGestureRecognizer = recognizeSwipe(in: pdfView, direction: .left) + swipeRightGestureRecognizer = recognizeSwipe(in: pdfView, direction: .right) apply(settings: settings, to: pdfView) delegate?.navigator(self, setupPDFView: pdfView) @@ -341,6 +345,10 @@ open class PDFNavigatorViewController: } pdfView.backgroundColor = settings.backgroundColor?.uiColor ?? pdfViewDefaultBackgroundColor + + let enableSwipes = !settings.scroll && spread + swipeLeftGestureRecognizer?.isEnabled = enableSwipes + swipeRightGestureRecognizer?.isEnabled = enableSwipes } @objc private func didTap(_ gesture: UITapGestureRecognizer) { @@ -367,6 +375,25 @@ open class PDFNavigatorViewController: delegate?.navigator(self, didTapAt: location) } + private func recognizeSwipe(in view: UIView, direction: UISwipeGestureRecognizer.Direction) -> UISwipeGestureRecognizer { + let recognizer = UISwipeGestureRecognizer(target: self, action: #selector(didSwipe)) + recognizer.direction = direction + recognizer.numberOfTouchesRequired = 1 + view.addGestureRecognizer(recognizer) + return recognizer + } + + @objc private func didSwipe(_ gesture: UISwipeGestureRecognizer) { + switch gesture.direction { + case .left: + Task { await goRight(options: .animated) } + case .right: + Task { await goLeft(options: .animated) } + default: + break + } + } + @objc private func pageDidChange() { guard let locator = currentPosition else { return From 48a27962b4e5e07b859da074b6cc6388afeb7b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Tue, 2 Dec 2025 16:49:45 +0100 Subject: [PATCH 2/3] Fix the zoom to fit in paginated spread mode --- .../PDF/PDFNavigatorViewController.swift | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/Sources/Navigator/PDF/PDFNavigatorViewController.swift b/Sources/Navigator/PDF/PDFNavigatorViewController.swift index 0c00e3b1c..f2207ea48 100644 --- a/Sources/Navigator/PDF/PDFNavigatorViewController.swift +++ b/Sources/Navigator/PDF/PDFNavigatorViewController.swift @@ -57,7 +57,8 @@ open class PDFNavigatorViewController: } /// Whether the pages is always scaled to fit the screen, unless the user zoomed in. - public var scalesDocumentToFit = true + @available(*, unavailable, message: "This API is deprecated") + public var scalesDocumentToFit: Bool { true } public weak var delegate: PDFNavigatorDelegate? public private(set) var pdfView: PDFDocumentView? @@ -186,7 +187,7 @@ open class PDFNavigatorViewController: super.viewWillAppear(animated) // Hack to layout properly the first page when opening the PDF. - if let pdfView = pdfView, scalesDocumentToFit { + if let pdfView = pdfView { pdfView.scaleFactor = pdfView.minScaleFactor if let page = pdfView.currentPage { pdfView.go(to: page.bounds(for: pdfView.displayBox), on: page) @@ -197,14 +198,12 @@ open class PDFNavigatorViewController: override open func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) - if let pdfView = pdfView, scalesDocumentToFit { - // Makes sure that the PDF is always properly scaled down when rotating the screen, if the user didn't zoom in. + if let pdfView = pdfView { + // Makes sure that the PDF is always properly scaled down when + // rotating the screen, if the user didn't zoom in. let isAtMinScaleFactor = (pdfView.scaleFactor == pdfView.minScaleFactor) coordinator.animate(alongsideTransition: { _ in - self.updateScaleFactors() - if isAtMinScaleFactor { - pdfView.scaleFactor = pdfView.minScaleFactor - } + self.updateScaleFactors(zoomToFit: isAtMinScaleFactor) // Reset the PDF view to update the spread if needed. if self.settings.spread == .auto { @@ -272,6 +271,7 @@ open class PDFNavigatorViewController: delegate?.navigator(self, setupPDFView: pdfView) NotificationCenter.default.addObserver(self, selector: #selector(pageDidChange), name: .PDFViewPageChanged, object: pdfView) + NotificationCenter.default.addObserver(self, selector: #selector(visiblePagesDidChange), name: .PDFViewVisiblePagesChanged, object: pdfView) NotificationCenter.default.addObserver(self, selector: #selector(selectionDidChange), name: .PDFViewSelectionChanged, object: pdfView) if let locator = locator { @@ -332,7 +332,7 @@ open class PDFNavigatorViewController: pdfView.displaysRTL = isRTL pdfView.displaysPageBreaks = true - pdfView.autoScales = !scalesDocumentToFit + pdfView.autoScales = false if let scrollView = pdfView.firstScrollView { let showScrollbar = settings.visibleScrollbar @@ -401,6 +401,14 @@ open class PDFNavigatorViewController: delegate?.navigator(self, locationDidChange: locator) } + @objc private func visiblePagesDidChange() { + // In paginated mode, we want to refresh the scale factors to properly + // fit the newly visible pages. + if !settings.scroll { + updateScaleFactors(zoomToFit: true) + } + } + @discardableResult private func go(to locator: Locator, isJump: Bool) async -> Bool { let locator = publication.normalizeLocator(locator) @@ -453,7 +461,7 @@ open class PDFNavigatorViewController: currentResourceIndex = index documentHolder.set(document, at: href) pdfView.document = document - updateScaleFactors() + updateScaleFactors(zoomToFit: true) } guard let document = pdfView.document else { @@ -473,12 +481,20 @@ open class PDFNavigatorViewController: return true } - private func updateScaleFactors() { - guard let pdfView = pdfView, scalesDocumentToFit else { + /// Updates the scale factors to match the currently visible pages. + /// + /// - Parameter zoomToFit: When true, the document will be zoomed to fit the + /// visible pages. + private func updateScaleFactors(zoomToFit: Bool) { + guard let pdfView = pdfView else { return } pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit pdfView.maxScaleFactor = 4.0 + + if zoomToFit { + pdfView.scaleFactor = pdfView.minScaleFactor + } } private func pageNumber(for locator: Locator) -> Int? { From aa88df84cb643f3727b6244ed107f85f2d6afb9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mickae=CC=88l=20Menu?= Date: Tue, 2 Dec 2025 17:47:53 +0100 Subject: [PATCH 3/3] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 035f6820e..518fc35ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ All notable changes to this project will be documented in this file. Take a look * Added `DirectionalNavigationAdapter.onNavigation` callback to be notified when a navigation action is triggered. * This callback is called before executing any navigation action. * Useful for hiding UI elements when the user navigates, or implementing analytics. +* Added swipe gesture support for navigating in PDF paginated spread mode. + +### Deprecated + +#### Navigator + +* `PDFNavigatorViewController.scalesDocumentToFit` is now deprecated and non-functional. The navigator always scales the document to fit the viewport. ### Changed @@ -24,6 +31,7 @@ All notable changes to this project will be documented in this file. Take a look #### Navigator * Fixed EPUB fixed-layout spread settings not updating after device rotation when the app was in the background. +* Fixed zoom-to-fit scaling in PDF paginated spread mode when `offsetFirstPage` is enabled. #### LCP