From be3f03be5366baf64a1e5ebcf6e880a408e56284 Mon Sep 17 00:00:00 2001 From: fermoya Date: Tue, 29 Mar 2022 16:35:30 +0100 Subject: [PATCH] Fix: Finishing pagination if new touch is detected --- .../project.pbxproj | 4 - .../Helpers/OnDeactivateModifier.swift | 39 ------ Sources/SwiftUIPager/PagerContent.swift | 37 ++++-- SwiftUIPager.xcodeproj/project.pbxproj | 12 -- .../Helpers/OnDeactivateModifier.swift | 39 ------ SwiftUIPager/PagerContent+Buildable.swift | 1 + SwiftUIPager/PagerContent+Helper.swift | 44 ++----- SwiftUIPager/PagerContent.swift | 115 +++++++++++++----- Tests/SwiftUIPagerTests/DummyTests.swift | 6 - .../OnDeactivateModifier_Tests.swift | 40 ------ release_description.md | 3 +- 11 files changed, 124 insertions(+), 216 deletions(-) delete mode 100644 Sources/SwiftUIPager/Helpers/OnDeactivateModifier.swift delete mode 100644 SwiftUIPager/Helpers/OnDeactivateModifier.swift delete mode 100644 Tests/SwiftUIPagerTests/OnDeactivateModifier_Tests.swift diff --git a/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj b/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj index 6fbf74c..614fc0b 100644 --- a/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj +++ b/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj @@ -22,7 +22,6 @@ 6B4EC8A6240D0918001E7490 /* EmbeddedExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B4EC8A5240D0918001E7490 /* EmbeddedExampleView.swift */; }; 6B4EC8A8240D1182001E7490 /* BizarreExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B4EC8A7240D1182001E7490 /* BizarreExampleView.swift */; }; 6B6FAA3D24D553C8000D1539 /* PagingAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B6FAA3C24D553C8000D1539 /* PagingAnimation.swift */; }; - 6B9C4A8A24B45F66004C06C5 /* OnDeactivateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B9C4A8924B45F66004C06C5 /* OnDeactivateModifier.swift */; }; 6BB1AAD524C9CA1C0032B5A3 /* PagerContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB1AAD424C9CA1C0032B5A3 /* PagerContent.swift */; }; 6BB1AAD724C9D0820032B5A3 /* Pager+Buildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB1AAD624C9D0820032B5A3 /* Pager+Buildable.swift */; }; 6BC5EDFC24866D9500E1E78C /* PagerContent+Buildable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BC5EDF424866D9500E1E78C /* PagerContent+Buildable.swift */; }; @@ -71,7 +70,6 @@ 6B4EC8A5240D0918001E7490 /* EmbeddedExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedExampleView.swift; sourceTree = ""; }; 6B4EC8A7240D1182001E7490 /* BizarreExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BizarreExampleView.swift; sourceTree = ""; }; 6B6FAA3C24D553C8000D1539 /* PagingAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PagingAnimation.swift; path = ../../Sources/SwiftUIPager/PageConfiguration/PagingAnimation.swift; sourceTree = ""; }; - 6B9C4A8924B45F66004C06C5 /* OnDeactivateModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnDeactivateModifier.swift; sourceTree = ""; }; 6BB1AAD424C9CA1C0032B5A3 /* PagerContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PagerContent.swift; path = ../../Sources/SwiftUIPager/PagerContent.swift; sourceTree = ""; }; 6BB1AAD624C9D0820032B5A3 /* Pager+Buildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Pager+Buildable.swift"; path = "../../Sources/SwiftUIPager/Pager+Buildable.swift"; sourceTree = ""; }; 6BC5EDF424866D9500E1E78C /* PagerContent+Buildable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "PagerContent+Buildable.swift"; path = "../../Sources/SwiftUIPager/PagerContent+Buildable.swift"; sourceTree = ""; }; @@ -174,7 +172,6 @@ 6BC5EDF824866D9500E1E78C /* Buildable.swift */, 6BD3828124C97DE3007B1CF6 /* CGPoint+Angle.swift */, 17C41EA925B21D9A006F9EC4 /* OnAnimationCompletedModifier.swift */, - 6B9C4A8924B45F66004C06C5 /* OnDeactivateModifier.swift */, 6BC5EDF724866D9500E1E78C /* View+Helper.swift */, ); name = Helpers; @@ -282,7 +279,6 @@ 17C41EAA25B21D9A006F9EC4 /* OnAnimationCompletedModifier.swift in Sources */, 6BC5EDFF24866D9500E1E78C /* Buildable.swift in Sources */, 1748E8C026695E220016F534 /* PageTransition.swift in Sources */, - 6B9C4A8A24B45F66004C06C5 /* OnDeactivateModifier.swift in Sources */, 6BEA731624ACF8D7007EA8DC /* GesturePriority.swift in Sources */, 6B35B6C125346610000D618F /* PaginationSensitivity.swift in Sources */, 1751176A2573D93F00D809CF /* Page.swift in Sources */, diff --git a/Sources/SwiftUIPager/Helpers/OnDeactivateModifier.swift b/Sources/SwiftUIPager/Helpers/OnDeactivateModifier.swift deleted file mode 100644 index 1f8c6af..0000000 --- a/Sources/SwiftUIPager/Helpers/OnDeactivateModifier.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// OnDeactivateModifier.swift -// SwiftUIPagerExample -// -// Created by Fernando Moya de Rivas on 07/07/2020. -// Copyright © 2020 Fernando Moya de Rivas. All rights reserved. -// - -import SwiftUI - -/// This modifier allows the `View` to listen to the `UIScene.didActivateNotification` in `iOS` -/// and perform an action when received. -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -struct OnDeactivateView: View { - - var content: Content - var perform: () -> Void - - var body: some View { - #if os(iOS) - return content - .onReceive(NotificationCenter.default.publisher(for: UIScene.didActivateNotification), perform: { _ in - self.perform() - }) - #else - return content - #endif - } - -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -extension View { - - func onDeactivate(perform: @escaping () -> Void) -> some View { - return OnDeactivateView(content: self, perform: perform) - } - -} diff --git a/Sources/SwiftUIPager/PagerContent.swift b/Sources/SwiftUIPager/PagerContent.swift index 24aa31e..7f90949 100644 --- a/Sources/SwiftUIPager/PagerContent.swift +++ b/Sources/SwiftUIPager/PagerContent.swift @@ -142,6 +142,11 @@ extension Pager { /// Page index @ObservedObject var pagerModel: Page + #if !os(tvOS) + /// DragGesture state to indicate whether the gesture was interrupted + @GestureState var isGestureFinished = true + #endif + /// Initializes a new `Pager`. /// /// - Parameter size: Available size @@ -197,16 +202,10 @@ extension Pager { #endif var resultView = wrappedView - .rotation3DEffect((isHorizontal ? .zero : Angle(degrees: 90)) + scrollDirectionAngle, - axis: (0, 0, 1)) - .onDeactivate(perform: { - if self.isDragging { - #if !os(tvOS) - self.onDragCancelled() - #endif - } - }) - .eraseToAny() + .rotation3DEffect( + (isHorizontal ? .zero : Angle(degrees: 90)) + scrollDirectionAngle, + axis: (0, 0, 1) + ).eraseToAny() if #available(iOS 13.2, macOS 10.15, tvOS 13.0, watchOS 6.0, *) { resultView = resultView @@ -221,6 +220,18 @@ extension Pager { .eraseToAny() } + #if !os(tvOS) + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { + resultView = resultView + .onChange(of: isGestureFinished) { value in + if value { + onDragGestureEnded() + } + } + .eraseToAny() + } + #endif + return resultView.contentShape(Rectangle()) } } @@ -251,12 +262,12 @@ extension Pager.PagerContent { #if !os(tvOS) var swipeGesture: some Gesture { DragGesture(minimumDistance: minimumDistance, coordinateSpace: .global) + .updating($isGestureFinished) { _, state, _ in + state = false + } .onChanged({ value in self.onDragChanged(with: value) }) - .onEnded({ (value) in - self.onDragGestureEnded() - }) } func onDragChanged(with value: DragGesture.Value) { diff --git a/SwiftUIPager.xcodeproj/project.pbxproj b/SwiftUIPager.xcodeproj/project.pbxproj index 91d8c88..7a0323e 100644 --- a/SwiftUIPager.xcodeproj/project.pbxproj +++ b/SwiftUIPager.xcodeproj/project.pbxproj @@ -42,11 +42,6 @@ 17C7B2682743CB3300BC51D4 /* Pager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B2482743CB3300BC51D4 /* Pager.swift */; }; 17C7B2692743CB3300BC51D4 /* Pager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B2482743CB3300BC51D4 /* Pager.swift */; }; 17C7B26A2743CB3300BC51D4 /* Pager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B2482743CB3300BC51D4 /* Pager.swift */; }; - 17C7B26B2743CB3300BC51D4 /* OnDeactivateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B24A2743CB3300BC51D4 /* OnDeactivateModifier.swift */; }; - 17C7B26C2743CB3300BC51D4 /* OnDeactivateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B24A2743CB3300BC51D4 /* OnDeactivateModifier.swift */; }; - 17C7B26D2743CB3300BC51D4 /* OnDeactivateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B24A2743CB3300BC51D4 /* OnDeactivateModifier.swift */; }; - 17C7B26E2743CB3300BC51D4 /* OnDeactivateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B24A2743CB3300BC51D4 /* OnDeactivateModifier.swift */; }; - 17C7B26F2743CB3300BC51D4 /* OnDeactivateModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B24A2743CB3300BC51D4 /* OnDeactivateModifier.swift */; }; 17C7B2702743CB3300BC51D4 /* OnAnimationCompletedModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B24B2743CB3300BC51D4 /* OnAnimationCompletedModifier.swift */; }; 17C7B2712743CB3300BC51D4 /* OnAnimationCompletedModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B24B2743CB3300BC51D4 /* OnAnimationCompletedModifier.swift */; }; 17C7B2722743CB3300BC51D4 /* OnAnimationCompletedModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C7B24B2743CB3300BC51D4 /* OnAnimationCompletedModifier.swift */; }; @@ -153,7 +148,6 @@ 17C7B2462743CB3300BC51D4 /* PagerContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagerContent.swift; sourceTree = ""; }; 17C7B2472743CB3300BC51D4 /* PageTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageTransition.swift; sourceTree = ""; }; 17C7B2482743CB3300BC51D4 /* Pager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pager.swift; sourceTree = ""; }; - 17C7B24A2743CB3300BC51D4 /* OnDeactivateModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnDeactivateModifier.swift; sourceTree = ""; }; 17C7B24B2743CB3300BC51D4 /* OnAnimationCompletedModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnAnimationCompletedModifier.swift; sourceTree = ""; }; 17C7B24C2743CB3300BC51D4 /* View+Helper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Helper.swift"; sourceTree = ""; }; 17C7B24D2743CB3300BC51D4 /* CGPoint+Angle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPoint+Angle.swift"; sourceTree = ""; }; @@ -242,7 +236,6 @@ 17C7B2492743CB3300BC51D4 /* Helpers */ = { isa = PBXGroup; children = ( - 17C7B24A2743CB3300BC51D4 /* OnDeactivateModifier.swift */, 17C7B24B2743CB3300BC51D4 /* OnAnimationCompletedModifier.swift */, 17C7B24C2743CB3300BC51D4 /* View+Helper.swift */, 17C7B24D2743CB3300BC51D4 /* CGPoint+Angle.swift */, @@ -696,7 +689,6 @@ 17C7B2B32743CB3300BC51D4 /* Page.swift in Sources */, 17C7B2812743CB3300BC51D4 /* Buildable.swift in Sources */, 17C7B2902743CB3300BC51D4 /* SwipeInteractionArea.swift in Sources */, - 17C7B26D2743CB3300BC51D4 /* OnDeactivateModifier.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -723,7 +715,6 @@ 17C7B2B42743CB3300BC51D4 /* Page.swift in Sources */, 17C7B2822743CB3300BC51D4 /* Buildable.swift in Sources */, 17C7B2912743CB3300BC51D4 /* SwipeInteractionArea.swift in Sources */, - 17C7B26E2743CB3300BC51D4 /* OnDeactivateModifier.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -750,7 +741,6 @@ 17C7B2B22743CB3300BC51D4 /* Page.swift in Sources */, 17C7B2802743CB3300BC51D4 /* Buildable.swift in Sources */, 17C7B28F2743CB3300BC51D4 /* SwipeInteractionArea.swift in Sources */, - 17C7B26C2743CB3300BC51D4 /* OnDeactivateModifier.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -777,7 +767,6 @@ 17C7B2B52743CB3300BC51D4 /* Page.swift in Sources */, 17C7B2832743CB3300BC51D4 /* Buildable.swift in Sources */, 17C7B2922743CB3300BC51D4 /* SwipeInteractionArea.swift in Sources */, - 17C7B26F2743CB3300BC51D4 /* OnDeactivateModifier.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -804,7 +793,6 @@ 17C7B2B12743CB3300BC51D4 /* Page.swift in Sources */, 17C7B27F2743CB3300BC51D4 /* Buildable.swift in Sources */, 17C7B28E2743CB3300BC51D4 /* SwipeInteractionArea.swift in Sources */, - 17C7B26B2743CB3300BC51D4 /* OnDeactivateModifier.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/SwiftUIPager/Helpers/OnDeactivateModifier.swift b/SwiftUIPager/Helpers/OnDeactivateModifier.swift deleted file mode 100644 index 1f8c6af..0000000 --- a/SwiftUIPager/Helpers/OnDeactivateModifier.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// OnDeactivateModifier.swift -// SwiftUIPagerExample -// -// Created by Fernando Moya de Rivas on 07/07/2020. -// Copyright © 2020 Fernando Moya de Rivas. All rights reserved. -// - -import SwiftUI - -/// This modifier allows the `View` to listen to the `UIScene.didActivateNotification` in `iOS` -/// and perform an action when received. -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -struct OnDeactivateView: View { - - var content: Content - var perform: () -> Void - - var body: some View { - #if os(iOS) - return content - .onReceive(NotificationCenter.default.publisher(for: UIScene.didActivateNotification), perform: { _ in - self.perform() - }) - #else - return content - #endif - } - -} - -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) -extension View { - - func onDeactivate(perform: @escaping () -> Void) -> some View { - return OnDeactivateView(content: self, perform: perform) - } - -} diff --git a/SwiftUIPager/PagerContent+Buildable.swift b/SwiftUIPager/PagerContent+Buildable.swift index 4dfe955..e51e986 100644 --- a/SwiftUIPager/PagerContent+Buildable.swift +++ b/SwiftUIPager/PagerContent+Buildable.swift @@ -53,6 +53,7 @@ extension Pager.PagerContent: Buildable { }.flatMap { $0 } } self.pagerModel.isInfinite = value + self.pagerModel.totalPages = newData.count return mutating(keyPath: \.isInifinitePager, value: value) .mutating(keyPath: \.data, value: newData) } diff --git a/SwiftUIPager/PagerContent+Helper.swift b/SwiftUIPager/PagerContent+Helper.swift index c56e18c..c41fe71 100644 --- a/SwiftUIPager/PagerContent+Helper.swift +++ b/SwiftUIPager/PagerContent+Helper.swift @@ -243,11 +243,7 @@ extension Pager.PagerContent { /// Oppacity for each item when `faded` animation is chosen func opacity(for item: PageWrapper) -> Double { guard let opacityIncrement = opacityIncrement else { return 1 } - guard let index = data.firstIndex(of: item) else { return 1 } - let totalIncrement = abs(totalOffset / pageDistance) - let currentPage = direction == .forward ? CGFloat(page) + totalIncrement : CGFloat(page) - totalIncrement - - let distance = abs(CGFloat(index) - currentPage) + let distance = abs(distance(to: item)) return Double(max(0, min(1, 1 - distance * CGFloat(opacityIncrement)))) } @@ -264,17 +260,8 @@ extension Pager.PagerContent { /// Angle for the 3D rotation effect func angle(for item: PageWrapper) -> Angle { guard shouldRotate else { return .zero } - guard let index = data.firstIndex(of: item) else { return .zero } - - let totalIncrement = abs(totalOffset / pageDistance) - - let currentAngle = Angle(degrees: Double(page - index) * rotationDegrees) - guard isDragging else { - return currentAngle - } - - let newAngle = direction == .forward ? Angle(degrees: currentAngle.degrees + rotationDegrees * Double(totalIncrement)) : Angle(degrees: currentAngle.degrees - rotationDegrees * Double(totalIncrement) ) - return newAngle + let distance = distance(to: item) + return Angle(degrees: rotationDegrees * Double(distance)) } /// Axis for the rotations effect @@ -285,24 +272,19 @@ extension Pager.PagerContent { /// Scale that applies to a particular item func scale(for item: PageWrapper) -> CGFloat { - guard isDragging else { return isFocused(item) ? 1 : interactiveScale } - - let totalIncrement = abs(totalOffset / pageDistance) - let currentPage = direction == .forward ? CGFloat(page) + totalIncrement : CGFloat(page) - totalIncrement - - guard let indexInt = data.firstIndex(of: item) else { return interactiveScale } + let distance = abs(distance(to: item)) + return Double(max(interactiveScale, min(1, 1 - distance * scaleIncrement))) + } - let index = CGFloat(indexInt) - guard abs(currentPage - index) <= 1 else { return interactiveScale } + private func distance(to item: PageWrapper) -> CGFloat { + guard let index: Int = dataDisplayed.firstIndex(of: item) else { return 0 } + guard let displayedItem = dataDisplayed.first(where: { $0 == data[page] }) else { return 0 } + guard let displayedIndex: Int = dataDisplayed.firstIndex(of: displayedItem) else { return 0 } - let increment = totalIncrement - totalIncrement.rounded(.towardZero) - let nextPage = direction == .forward ? currentPage.rounded(.awayFromZero) : currentPage.rounded(.towardZero) - guard currentPage > 0 else { - return 1 - (scaleIncrement * increment) - } + let totalIncrement = abs(totalOffset / pageDistance) + let currentIndex = direction == .forward ? CGFloat(index) - totalIncrement : CGFloat(index) + totalIncrement - return index == nextPage ? interactiveScale + (scaleIncrement * increment) - : 1 - (scaleIncrement * increment) + return CGFloat(displayedIndex) - currentIndex } /// Returns true if the item is focused on the screen. diff --git a/SwiftUIPager/PagerContent.swift b/SwiftUIPager/PagerContent.swift index f9c8fd4..7f90949 100644 --- a/SwiftUIPager/PagerContent.swift +++ b/SwiftUIPager/PagerContent.swift @@ -142,6 +142,11 @@ extension Pager { /// Page index @ObservedObject var pagerModel: Page + #if !os(tvOS) + /// DragGesture state to indicate whether the gesture was interrupted + @GestureState var isGestureFinished = true + #endif + /// Initializes a new `Pager`. /// /// - Parameter size: Available size @@ -160,20 +165,21 @@ extension Pager { var body: some View { let stack = HStack(spacing: interactiveItemSpacing) { ForEach(dataDisplayed, id: id) { item in - Group { - if self.isInifinitePager && self.isEdgePage(item) { - EmptyView() - } else { - self.content(item.element) - } - } - .frame(size: self.pageSize) - .scaleEffect(self.scale(for: item)) - .rotation3DEffect((self.isHorizontal ? .zero : Angle(degrees: -90)) - self.scrollDirectionAngle, - axis: (0, 0, 1)) - .rotation3DEffect(self.angle(for: item), +// Group { +// if self.isInifinitePager && self.isEdgePage(item) { +// EmptyView() +// } else { +// self.content(item.element) +// } +// } + self.content(item.element) + .frame(size: self.pageSize) + .scaleEffect(self.scale(for: item)) + .rotation3DEffect((self.isHorizontal ? .zero : Angle(degrees: -90)) - self.scrollDirectionAngle, + axis: (0, 0, 1)) + .rotation3DEffect(self.angle(for: item), axis: self.axis) - .opacity(opacity(for: item)) + .opacity(opacity(for: item)) } .offset(x: self.xOffset, y : self.yOffset) } @@ -196,16 +202,10 @@ extension Pager { #endif var resultView = wrappedView - .rotation3DEffect((isHorizontal ? .zero : Angle(degrees: 90)) + scrollDirectionAngle, - axis: (0, 0, 1)) - .onDeactivate(perform: { - if self.isDragging { - #if !os(tvOS) - self.onDragGestureEnded() - #endif - } - }) - .eraseToAny() + .rotation3DEffect( + (isHorizontal ? .zero : Angle(degrees: 90)) + scrollDirectionAngle, + axis: (0, 0, 1) + ).eraseToAny() if #available(iOS 13.2, macOS 10.15, tvOS 13.0, watchOS 6.0, *) { resultView = resultView @@ -220,6 +220,18 @@ extension Pager { .eraseToAny() } + #if !os(tvOS) + if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) { + resultView = resultView + .onChange(of: isGestureFinished) { value in + if value { + onDragGestureEnded() + } + } + .eraseToAny() + } + #endif + return resultView.contentShape(Rectangle()) } } @@ -249,13 +261,13 @@ extension Pager.PagerContent { /// `DragGesture` customized to work with `Pager` #if !os(tvOS) var swipeGesture: some Gesture { - DragGesture(minimumDistance: minimumDistance) + DragGesture(minimumDistance: minimumDistance, coordinateSpace: .global) + .updating($isGestureFinished) { _, state, _ in + state = false + } .onChanged({ value in self.onDragChanged(with: value) }) - .onEnded({ (value) in - self.onDragGestureEnded() - }) } func onDragChanged(with value: DragGesture.Value) { @@ -265,8 +277,10 @@ extension Pager.PagerContent { onDraggingBegan?() } - let lastLocation = self.lastDraggingValue?.location ?? value.location - let swipeAngle = (value.location - lastLocation).angle ?? .zero + let currentLocation = dragLocation(for: value) + let currentTranslation = dragTranslation(for: value) + let lastLocation = self.lastDraggingValue.flatMap(dragLocation) ?? currentLocation + let swipeAngle = (currentLocation - lastLocation).angle ?? .zero // Ignore swipes that aren't on the X-Axis guard swipeAngle.isAlongXAxis else { self.pagerModel.lastDraggingValue = value @@ -275,10 +289,10 @@ extension Pager.PagerContent { let side = self.isHorizontal ? self.size.width : self.size.height let normalizedRatio = self.allowsMultiplePagination ? 1 : (self.pageDistance / side) - let offsetIncrement = (value.location.x - lastLocation.x) * normalizedRatio + let offsetIncrement = (currentLocation.x - lastLocation.x) * normalizedRatio // If swipe hasn't started yet, ignore swipes if they didn't start on the X-Axis - let isTranslationInXAxis = abs(value.translation.width) > abs(value.translation.height) + let isTranslationInXAxis = abs(currentTranslation.width) > abs(currentTranslation.height) guard self.draggingOffset != 0 || isTranslationInXAxis else { return } @@ -343,6 +357,15 @@ extension Pager.PagerContent { } } + func onDragCancelled() { + withAnimation { + pagerModel.draggingOffset = 0 + pagerModel.lastDraggingValue = nil + pagerModel.draggingVelocity = 0 + pagerModel.objectWillChange.send() + } + } + var dragResult: (page: Int, increment: Int) { let currentPage = self.currentPage(sensitivity: sensitivity.value) let velocity = -self.draggingVelocity @@ -379,6 +402,36 @@ extension Pager.PagerContent { newPage = max(0, min(self.numberOfPages - 1, newPage)) return (newPage, pageIncrement) } + + private func dragTranslation(for value: DragGesture.Value) -> CGSize { + let multiplier: CGFloat = scrollDirectionAngle == .zero ? 1 : -1 + if isHorizontal { + return CGSize( + width: value.translation.width * multiplier, + height: value.translation.height * multiplier + ) + } else { + return CGSize( + width: value.translation.height * multiplier, + height: value.translation.width * multiplier + ) + } + } + + private func dragLocation(for value: DragGesture.Value) -> CGPoint { + let multiplier: CGFloat = scrollDirectionAngle == .zero ? 1 : -1 + if isHorizontal { + return CGPoint( + x: value.location.x * multiplier, + y: value.location.y * multiplier + ) + } else { + return CGPoint( + x: value.location.y * multiplier, + y: value.location.x * multiplier + ) + } + } #endif } diff --git a/Tests/SwiftUIPagerTests/DummyTests.swift b/Tests/SwiftUIPagerTests/DummyTests.swift index 1270514..269e733 100644 --- a/Tests/SwiftUIPagerTests/DummyTests.swift +++ b/Tests/SwiftUIPagerTests/DummyTests.swift @@ -20,11 +20,6 @@ final class DummyTests: XCTestCase { _ = view.gesture(TapGesture(), priority: .normal) } - func test_dummyOnDeactivateModifier() { - let view = Text("dummy") - _ = view.onDeactivate { } - } - func test_dummyOnAnimationCompleted() { let view = Text("dummy") _ = view.onAnimationCompleted(for: 1) { } @@ -32,7 +27,6 @@ final class DummyTests: XCTestCase { static var allTests = [ ("test_dummyOnAnimationCompleted", test_dummyOnAnimationCompleted), - ("test_dummyOnDeactivateModifier", test_dummyOnDeactivateModifier), ("test_dummyGesturePriority", test_dummyGesturePriority) ] diff --git a/Tests/SwiftUIPagerTests/OnDeactivateModifier_Tests.swift b/Tests/SwiftUIPagerTests/OnDeactivateModifier_Tests.swift deleted file mode 100644 index e59f0c4..0000000 --- a/Tests/SwiftUIPagerTests/OnDeactivateModifier_Tests.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// File.swift -// -// -// Created by Fernando Moya de Rivas on 05/08/2020. -// - -import XCTest -import SwiftUI -@testable import SwiftUIPager - -final class OnDeactivateModifier_Tests: XCTestCase { - - func test_GivenView_WhenOnDeactivate_ThenPerformAction() { - #if os(iOS) - - let view = Text("") - let expectation = self.expectation(description: "UIScene.didActivateNotification triggers action callback") - - let subject = view.onDeactivate { - expectation.fulfill() - } - - let window = UIWindow(frame: UIScreen.main.bounds) - window.rootViewController = UIHostingController(rootView: subject) - window.makeKeyAndVisible() - - DispatchQueue.main.async { - NotificationCenter.default.post(Notification(name: UIScene.didActivateNotification)) - } - waitForExpectations(timeout: 1, handler: nil) - - #endif - } - - static var allTests = [ - ("test_GivenView_WhenOnDeactivate_ThenPerformAction", test_GivenView_WhenOnDeactivate_ThenPerformAction) - ] - -} diff --git a/release_description.md b/release_description.md index 4ee0ea7..7276b2b 100644 --- a/release_description.md +++ b/release_description.md @@ -2,4 +2,5 @@ - #244 `loopPages` fail to use right `page` if `repeating` elements - #253 #233 `loopPages` fades pages - #252 `NavigationView` swipe gestures not working if wrapping a `Pager` -- #259 `loopPages` not working well with `interactive` approaches \ No newline at end of file +- #259 `loopPages` not working well with `interactive` approaches +- #238 finish pagination if gesture interrupted \ No newline at end of file