Skip to content

Commit

Permalink
Merge pull request #159 from fermoya/feat/partial-pagination
Browse files Browse the repository at this point in the history
Feat/partial pagination
  • Loading branch information
fermoya authored Nov 26, 2020
2 parents 791131c + 6e083a9 commit 8ca5f59
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 5 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions Documentation/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,21 @@ Pager(...)

<img src="/resources/usage/item-alignment-start.gif" alt="Pages positioned at the start of the horizontal pager" height="640"/>

### 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))
```
<img src="/resources/usage/single-pagination-ratio.gif" alt="Reveal Ratio set to a third of the page" height="640"/>

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`.
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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: {
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions Sources/SwiftUIPager/Pager+Buildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftUIPager/Pager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public struct Pager<Element, ID, PageView>: 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)?

Expand Down Expand Up @@ -200,6 +203,7 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
.onDraggingBegan(onDraggingBegan)
.padding(sideInsets)
.pagingAnimation(pagingAnimation)
.partialPagination(pageRatio)

#if !os(tvOS)
pagerContent = pagerContent
Expand Down
12 changes: 12 additions & 0 deletions Sources/SwiftUIPager/PagerContent+Buildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion Sources/SwiftUIPager/PagerContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)?

Expand Down Expand Up @@ -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
}
}
Expand Down
42 changes: 41 additions & 1 deletion Tests/SwiftUIPagerTests/Pager+Buildable_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,49 @@ 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)
XCTAssertEqual(pagerContent.minimumDistance, 15)
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)
Expand Down Expand Up @@ -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)
]
}

Expand Down
4 changes: 2 additions & 2 deletions release_description.md
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 8ca5f59

Please sign in to comment.