diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
index e570248..85ec871 100644
--- a/.github/workflows/create-release.yml
+++ b/.github/workflows/create-release.yml
@@ -17,6 +17,10 @@ jobs:
steps:
- uses: actions/checkout@v2
+ - uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: latest-stable
+
- name: Create XCFramework
id: xcframework
run: ./scripts/build_xcframework.sh
diff --git a/Documentation/Usage.md b/Documentation/Usage.md
index fbbdf23..57eb5c0 100644
--- a/Documentation/Usage.md
+++ b/Documentation/Usage.md
@@ -98,6 +98,21 @@ Pager(...)
+### Partial pagination
+
+By default, `Pager` will reveal the neighbor items completely (100% of their relative size). If you wish to limit this _reveal ratio_, you can use `singlePatination(ratio:sensitivity)` to modify this ratio:
+
+```swift
+Pager(...)
+ .singlePagination(0.33, sensitivity: .custom(0.2))
+ .preferredItemSize(CGSize(width: 300, height: 400))
+ .itemSpacing(10)
+ .background(Color.gray.opacity(0.2))
+```
+
+
+For more information about `sensitivity`, check out [Pagination sensitivity](#pagination-sensitivity).
+
### Multiple pagination
It's possible for `Pager` to swipe more than one page at a time. This is especially useful if your page size is small. Use `multiplePagination`.
@@ -182,6 +197,23 @@ Transform your `Pager` into an endless sroll by using `loopPages`:
**Note**: You'll need a minimum number of elements to use this modifier based on the page size. If you need more items, use `loopPages(repeating:)` to let `Pager` know elements should be repeated in batches.
+## Page Tranistions
+
+Use `pagingAnimation` to customize the _transition_ to the next page once the drag has ended. This is achieve by a block with a `DragResult`which contains:
+* Current page
+* Next page
+* Total shift
+* Velocity
+
+By default, `pagingAnimation`is set to `standard`(a.k.a, `.easeOut`) for `singlePagination`and `steep`([custom bezier curve](https://cubic-bezier.com/#.2,1,.9,1)) for `multiplePagination`. If you wish to change the animation, you could do it as follows:
+
+```swift
+Pager(...)
+ .pagingAnimation({ currentPage, nextPage, totalShift, velocity in
+ return PagingAnimation.custom(animation: .easeInOut)
+ })
+```
+
## Events
Use `onPageChanged` to react to any change on the page index:
diff --git a/Example/SwiftUIPagerExample/Examples/InfiniteExampleView.swift b/Example/SwiftUIPagerExample/Examples/InfiniteExampleView.swift
index 552ee3d..a3d3464 100644
--- a/Example/SwiftUIPagerExample/Examples/InfiniteExampleView.swift
+++ b/Example/SwiftUIPagerExample/Examples/InfiniteExampleView.swift
@@ -28,6 +28,7 @@ struct InfiniteExampleView: View {
id: \.self) {
self.pageView($0)
}
+ .singlePagination(ratio: 0.5, sensitivity: .high)
.onPageChanged({ page in
guard page == self.data1.count - 2 else { return }
guard let last = self.data1.last else { return }
@@ -38,7 +39,7 @@ struct InfiniteExampleView: View {
}
})
.pagingPriority(.simultaneous)
- .preferredItemSize(CGSize(width: 300, height: 50))
+ .preferredItemSize(CGSize(width: 200, height: 100))
.itemSpacing(10)
.background(Color.gray.opacity(0.2))
.alert(isPresented: self.$isPresented, content: {
diff --git a/README.md b/README.md
index 5110ed9..d6fb117 100644
--- a/README.md
+++ b/README.md
@@ -26,12 +26,14 @@ Create vertical or horizontal pagers, align the cards, change the direction of t
- [Pagination sensitivity](Documentation/Usage.md#pagination-sensitivity)
- [Orientation and direction](Documentation/Usage.md#orientation-and-direction)
- [Alignment](Documentation/Usage.md#alignment)
+ - [Partial pagination](Documentation/Usage.md#partial-pagination)
- [Multiple pagination](Documentation/Usage.md#multiple-pagination)
- [Paging Priority](Documentation/Usage.md#paging-priority)
- [Animations](Documentation/Usage.md#animations)
- [Scale](Documentation/Usage.md#scale)
- [Rotation](Documentation/Usage.md#rotation)
- [Loop](Documentation/Usage.md#loop)
+ - [Page Tranistions](Documentation/Usage.md#page-transitions)
- [Add pages on demand](Documentation/Usage.md#add-pages-on-demand)
- [Content Loading Policy](Documentation/Usage.md#content-loading-policy)
- [Examples](Documentation/Usage.md#examples)
diff --git a/Sources/SwiftUIPager/Pager+Buildable.swift b/Sources/SwiftUIPager/Pager+Buildable.swift
index 8816bea..4f5873c 100644
--- a/Sources/SwiftUIPager/Pager+Buildable.swift
+++ b/Sources/SwiftUIPager/Pager+Buildable.swift
@@ -29,6 +29,21 @@ extension Pager: Buildable, PagerProxy {
.mutating(keyPath: \.contentLoadingPolicy, value: .eager)
}
+ /// Allows to scroll one page at a time. Use `ratio` to limit next item's reveal ratio.
+ /// Once reached, items won't keep scrolling further.
+ /// `Pager` will use then `sensitivity` to determine whether to paginate to the next page.
+ ///
+ /// - Parameter ratio: max page reveal ratio. Should be `0 < ratio < 1`. `default` is `1`
+ /// - Parameter sensitivity: sensitivity to be applied when paginating. `default` is `medium` a.k.a `0.5`
+ ///
+ /// For instance, setting `ratio` to `0.33` will make `Pager` reveal up to a third of the next item.
+ /// A proper `sensitivy` for this scenario would be `high` (a.k.a, `0.33`) or a custom value lower than `ratio`
+ public func singlePagination(ratio: CGFloat = 1, sensitivity: PaginationSensitivity = .medium) -> Self {
+ mutating(keyPath: \.pageRatio, value: min(1, max(0, ratio)))
+ .mutating(keyPath: \.allowsMultiplePagination, value: false)
+ .mutating(keyPath: \.sensitivity, value: sensitivity)
+ }
+
/// Sets the policy followed to load `Pager` content.
///
/// - Parameter value: policy to load the content.
diff --git a/Sources/SwiftUIPager/Pager.swift b/Sources/SwiftUIPager/Pager.swift
index becba7c..9f5d8c3 100644
--- a/Sources/SwiftUIPager/Pager.swift
+++ b/Sources/SwiftUIPager/Pager.swift
@@ -64,6 +64,9 @@ public struct Pager: View where PageView: View, Element:
/*** ViewModified properties ***/
+ /// Max relative item size that `Pager` will scroll before determining whether to move to the next page
+ var pageRatio: CGFloat = 1
+
/// Animation to be applied when the user stops dragging
var pagingAnimation: ((DragResult) -> PagingAnimation)?
@@ -200,6 +203,7 @@ public struct Pager: View where PageView: View, Element:
.onDraggingBegan(onDraggingBegan)
.padding(sideInsets)
.pagingAnimation(pagingAnimation)
+ .partialPagination(pageRatio)
#if !os(tvOS)
pagerContent = pagerContent
diff --git a/Sources/SwiftUIPager/PagerContent+Buildable.swift b/Sources/SwiftUIPager/PagerContent+Buildable.swift
index 601ac54..192f816 100644
--- a/Sources/SwiftUIPager/PagerContent+Buildable.swift
+++ b/Sources/SwiftUIPager/PagerContent+Buildable.swift
@@ -56,6 +56,18 @@ extension Pager.PagerContent: Buildable, PagerProxy {
.mutating(keyPath: \.data, value: newData)
}
+ /// Sets a limit to the dragging offset, affecting the pagination towards neighboring items.
+ /// When the limit is reached, items won't keep scrolling further.
+ /// This modifier is incompatible with `multiplePagination` and will modify its value.
+ ///
+ /// - Parameter ratio: max page percentage. Should be `0 < ratio < 1`
+ /// - Note: This modifier is incompatible with `multiplePagination`
+ ///
+ /// For instance, setting this `ratio` to `0.5` will make `Pager` reveal half of the next item tops.
+ func partialPagination(_ ratio: CGFloat) -> Self {
+ mutating(keyPath: \.pageRatio, value: ratio)
+ }
+
#if !os(tvOS)
/// Sensitivity used to determine whether or not to swipe the page
diff --git a/Sources/SwiftUIPager/PagerContent.swift b/Sources/SwiftUIPager/PagerContent.swift
index 50d46df..b8c432b 100644
--- a/Sources/SwiftUIPager/PagerContent.swift
+++ b/Sources/SwiftUIPager/PagerContent.swift
@@ -50,6 +50,9 @@ extension Pager {
/*** ViewModified properties ***/
+ /// Max relative item size that `Pager` will scroll before determining whether to move to the next page
+ var pageRatio: CGFloat = 1
+
/// Animation to be applied when the user stops dragging
var pagingAnimation: ((DragResult) -> PagingAnimation)?
@@ -246,7 +249,12 @@ extension Pager.PagerContent {
self.draggingVelocity = Double(offsetIncrement) / timeIncrement
}
- self.draggingOffset += offsetIncrement
+ var newOffset = self.draggingOffset + offsetIncrement
+ if !allowsMultiplePagination {
+ newOffset = self.direction == .forward ? max(newOffset, self.pageRatio * -self.pageDistance) : min(newOffset, self.pageRatio * self.pageDistance)
+ }
+
+ self.draggingOffset = newOffset
self.lastDraggingValue = value
}
}
diff --git a/Tests/SwiftUIPagerTests/Pager+Buildable_Tests.swift b/Tests/SwiftUIPagerTests/Pager+Buildable_Tests.swift
index efefb90..0f51866 100644
--- a/Tests/SwiftUIPagerTests/Pager+Buildable_Tests.swift
+++ b/Tests/SwiftUIPagerTests/Pager+Buildable_Tests.swift
@@ -36,6 +36,7 @@ final class Pager_Buildable_Tests: XCTestCase {
XCTAssertEqual(pager.allowsMultiplePagination, false)
XCTAssertNil(pager.pagingAnimation)
XCTAssertEqual(pager.sensitivity, .default)
+ XCTAssertEqual(pager.pageRatio, 1)
let pagerContent = pager.content(for: CGSize(width: 100, height: 100))
XCTAssertNil(pagerContent.direction)
@@ -43,6 +44,41 @@ final class Pager_Buildable_Tests: XCTestCase {
XCTAssertFalse(pagerContent.isDragging)
}
+ func test_GivenPager_WhenSinglePagination_ThenRatioChanges() {
+ var pager = givenPager
+ pager = pager.singlePagination(ratio: 0.33, sensitivity: .high)
+
+ let pagerContent = pager.content(for: CGSize(width: 100, height: 100))
+ XCTAssertEqual(pagerContent.sensitivity, .high)
+ XCTAssertEqual(pagerContent.pageRatio, 0.33)
+ }
+
+ func test_GivenPager_WhenSinglePaginationNegativeValue_ThenRatioZero() {
+ var pager = givenPager
+ pager = pager.singlePagination(ratio: -0.33, sensitivity: .high)
+
+ let pagerContent = pager.content(for: CGSize(width: 100, height: 100))
+ XCTAssertEqual(pagerContent.sensitivity, .high)
+ XCTAssertEqual(pagerContent.pageRatio, 0)
+ }
+
+ func test_GivenPager_WhenSinglePaginationTooLarge_ThenRatio1() {
+ var pager = givenPager
+ pager = pager.singlePagination(ratio: 1.2, sensitivity: .high)
+
+ let pagerContent = pager.content(for: CGSize(width: 100, height: 100))
+ XCTAssertEqual(pagerContent.sensitivity, .high)
+ XCTAssertEqual(pagerContent.pageRatio, 1)
+ }
+
+ func test_GivenMultiplePaginationPager_WhenSinglePagination_ThenAllowsMultiplePaginationFalse() {
+ var pager = givenPager.multiplePagination()
+ pager = pager.singlePagination()
+
+ let pagerContent = pager.content(for: CGSize(width: 100, height: 100))
+ XCTAssertFalse(pagerContent.allowsMultiplePagination)
+ }
+
func test_GivenPager_WhenSensitivityHigh_ThenSensitivityHigh() {
var pager = givenPager
pager = pager.sensitivity(.high)
@@ -540,7 +576,11 @@ final class Pager_Buildable_Tests: XCTestCase {
("test_GivenPager_WhenMultiplePagination_ThenAllowsMultiplePagination", test_GivenPager_WhenMultiplePagination_ThenAllowsMultiplePagination),
("test_GivenPager_WhenPagingAnimation_ThenPagingAnimationNotNil", test_GivenPager_WhenPagingAnimation_ThenPagingAnimationNotNil),
("test_GivenPager_WhenPageOffsetPositive_ThenDirectionForward", test_GivenPager_WhenPageOffsetPositive_ThenDirectionForward),
- ("test_GivenPager_WhenPageOffsetNegative_ThenDirectionBackward", test_GivenPager_WhenPageOffsetNegative_ThenDirectionBackward)
+ ("test_GivenPager_WhenPageOffsetNegative_ThenDirectionBackward", test_GivenPager_WhenPageOffsetNegative_ThenDirectionBackward),
+ ("test_GivenPager_WhenSinglePagination_ThenRatioChanges", test_GivenPager_WhenSinglePagination_ThenRatioChanges),
+ ("test_GivenPager_WhenSinglePaginationNegativeValue_ThenRatioZero", test_GivenPager_WhenSinglePaginationNegativeValue_ThenRatioZero),
+ ("test_GivenPager_WhenSinglePaginationTooLarge_ThenRatio1", test_GivenPager_WhenSinglePaginationTooLarge_ThenRatio1),
+ ("test_GivenMultiplePaginationPager_WhenSinglePagination_ThenAllowsMultiplePaginationFalse", test_GivenMultiplePaginationPager_WhenSinglePagination_ThenAllowsMultiplePaginationFalse)
]
}
diff --git a/release_description.md b/release_description.md
index 66e580c..a5fd51d 100644
--- a/release_description.md
+++ b/release_description.md
@@ -1,5 +1,5 @@
### Features
-- New modifier to adjust pagination sensitivity
+- New modifier to switch back to `singlePagination` and provide a reveal `ratio`
### Fixes
-- Fixed `animation` disabled with infinite pagers
+- Enhancement on the pagination animation