diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b43129a82..9efdd533ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -142,6 +142,7 @@ All notable changes to this project will be documented in this file. Take a look * Support for displaying Divina (image-based publications like CBZ) in the fixed-layout EPUB navigator. * Bitmap images in the EPUB reading order are now supported as a fixed layout resource. * Added `offsetFirstPage` preference for fixed-layout EPUBs to control whether the first page is displayed alone or alongside the second page when spreads are enabled. +* Added `func navigator(_ navigator: any VisualNavigator, didDragBy delta: CGFloat)` and `func navigator(_ navigator: any VisualNavigator, didEndDraggingWithVelocity velocity: CGFloat)` to `VisualNavigatorDelegate`. #### Streamer diff --git a/Sources/Navigator/EPUB/EPUBNavigatorViewController.swift b/Sources/Navigator/EPUB/EPUBNavigatorViewController.swift index 34be1ba0f8..445558ee74 100644 --- a/Sources/Navigator/EPUB/EPUBNavigatorViewController.swift +++ b/Sources/Navigator/EPUB/EPUBNavigatorViewController.swift @@ -1232,6 +1232,18 @@ extension EPUBNavigatorViewController: EPUBSpreadViewDelegate { } } + func spreadView(_ spreadView: EPUBSpreadView, didDragBy delta: CGFloat) { + if paginationView?.currentView == spreadView { + delegate?.navigator(self, didDragBy: delta) + } + } + + func spreadView(_ spreadView: EPUBSpreadView, didEndDraggingWithVelocity velocity: CGFloat) { + if paginationView?.currentView == spreadView { + delegate?.navigator(self, didEndDraggingWithVelocity: velocity) + } + } + func spreadView(_ spreadView: EPUBSpreadView, present viewController: UIViewController) { present(viewController, animated: true) } diff --git a/Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift b/Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift index abdde9284f..4f3911e2e8 100644 --- a/Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift +++ b/Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift @@ -370,6 +370,8 @@ final class EPUBReflowableSpreadView: EPUBSpreadView { private var progression: ClosedRange? /// To check if a progression change was cancelled or not. private var previousProgression: ClosedRange? + /// Previous scroll offset while the user is dragging. + private var previousDragOffset: CGPoint? /// Called by the javascript code to notify that scrolling ended. private func progressionDidChange(_ body: Any) { @@ -432,8 +434,48 @@ final class EPUBReflowableSpreadView: EPUBSpreadView { // MARK: - UIScrollViewDelegate + override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + super.scrollViewWillBeginDragging(scrollView) + previousDragOffset = scrollView.contentOffset + } + override func scrollViewDidScroll(_ scrollView: UIScrollView) { super.scrollViewDidScroll(scrollView) setNeedsNotifyPagesDidChange() + + let currentOffset = scrollView.contentOffset + if scrollView.isDragging, let previousOffset = previousDragOffset { + let delta = progressionDelta(from: previousOffset, to: currentOffset) + if delta != 0 { + delegate?.spreadView(self, didDragBy: delta) + } + } + previousDragOffset = currentOffset + } + + func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + delegate?.spreadView(self, didEndDraggingWithVelocity: progressionVelocity(from: velocity)) + previousDragOffset = nil + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + previousDragOffset = nil + } + + private func progressionDelta(from previousOffset: CGPoint, to currentOffset: CGPoint) -> CGFloat { + if viewModel.scroll, !viewModel.verticalText { + return currentOffset.y - previousOffset.y + } + + let delta = currentOffset.x - previousOffset.x + return viewModel.readingProgression == .rtl ? -delta : delta + } + + private func progressionVelocity(from velocity: CGPoint) -> CGFloat { + if viewModel.scroll, !viewModel.verticalText { + return velocity.y + } + + return viewModel.readingProgression == .rtl ? -velocity.x : velocity.x } } diff --git a/Sources/Navigator/EPUB/EPUBSpreadView.swift b/Sources/Navigator/EPUB/EPUBSpreadView.swift index fa809378e4..7fb1fb44e1 100644 --- a/Sources/Navigator/EPUB/EPUBSpreadView.swift +++ b/Sources/Navigator/EPUB/EPUBSpreadView.swift @@ -29,6 +29,12 @@ protocol EPUBSpreadViewDelegate: AnyObject { /// Called when the pages visible in the spread changed. func spreadViewPagesDidChange(_ spreadView: EPUBSpreadView) + /// Called continuously while the user drags the content. + func spreadView(_ spreadView: EPUBSpreadView, didDragBy delta: CGFloat) + + /// Called when the user lifts their finger after dragging. + func spreadView(_ spreadView: EPUBSpreadView, didEndDraggingWithVelocity velocity: CGFloat) + /// Called when the spread view needs to present a view controller. func spreadView(_ spreadView: EPUBSpreadView, present viewController: UIViewController) diff --git a/Sources/Navigator/VisualNavigator.swift b/Sources/Navigator/VisualNavigator.swift index c9bf1ccca5..1da90d5903 100644 --- a/Sources/Navigator/VisualNavigator.swift +++ b/Sources/Navigator/VisualNavigator.swift @@ -117,6 +117,21 @@ public struct VisualNavigatorPresentation { /// Return `true` to navigate to the link, or `false` if you intend to /// present the link yourself func navigator(_ navigator: VisualNavigator, shouldNavigateToLink link: Link) -> Bool + + /// Called continuously while the user drags the content. + /// + /// `delta` is the distance in points along the scroll axis since the last + /// call. Positive values indicate forward progression through the content. + /// + /// Not called during momentum deceleration after the user lifts their + /// finger. + func navigator(_ navigator: any VisualNavigator, didDragBy delta: CGFloat) + + /// Called when the user lifts their finger after dragging. + /// + /// `velocity` is in points per second along the scroll axis. Positive + /// values indicate forward momentum. + func navigator(_ navigator: any VisualNavigator, didEndDraggingWithVelocity velocity: CGFloat) } public extension VisualNavigatorDelegate { @@ -143,4 +158,12 @@ public extension VisualNavigatorDelegate { func navigator(_ navigator: VisualNavigator, shouldNavigateToLink link: Link) -> Bool { true } + + func navigator(_ navigator: any VisualNavigator, didDragBy delta: CGFloat) { + // Optional + } + + func navigator(_ navigator: any VisualNavigator, didEndDraggingWithVelocity velocity: CGFloat) { + // Optional + } }