Skip to content

Commit

Permalink
Merge pull request fermoya#161 from fermoya/feat/bounce-workaround
Browse files Browse the repository at this point in the history
Feat/bounce workaround
  • Loading branch information
fermoya authored Nov 26, 2020
2 parents 6b5afb7 + 81c2719 commit 3e091c5
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 256 deletions.
39 changes: 38 additions & 1 deletion Documentation/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ Pager(...)

Be aware that this modifier will change the loading policy. See [Content Loading Policy](#content-loading-policy) for more information.

### More modifiers
| **Modifier** | **Description** |
|---|---|
| `allowsDragging` | whether or not dragging is allowed |
| `disableDragging` | disables dragging |
| `bounces` | whether or not `Pager` should bounce |
| `delaysTouches` | whether or not touches shoulf be delayed. Useful if nested in `ScrollView` |
| `pageOffset` | allows _manual_ scroll |
| `expandPageToEdges` | modifies `itemAspectRatio` so that the use up all the space available |


## Paging Priority

For complex pages where a `Gesture` might be used, or any other `View` that internally holds a `Gesture` like `Button` or `NavigationLink`, you might come across issues when trying to swipe on top of certain areas within the page. For these scenarios, use `pagingPriority` to select the option that best suits your purpose. For instance, a page containing a `NavigationLink` won't be scrollable over the link area unless `pagingPrioriry(.simultaneous)` is added:
Expand Down Expand Up @@ -197,7 +208,7 @@ 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
## Page Transitions

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
Expand Down Expand Up @@ -225,6 +236,8 @@ Pager(...)
})
```

You can also use `onDraggingBegan`, `onDraggingChanged` and `onDragginEnded` to keep track of the dragging.

## Add pages on demand

You can use `onPageChanged` to add new items on demand whenever the user is getting to the last page:
Expand All @@ -247,6 +260,30 @@ var body: some View {
}
```

At the same time, items can be added at the start. Notice you'll need to update the page yourself (as you're inserting new elements) to keep `Pager` focused on the right element:

```swift

@State var count: Int = -1
@State var page: Int = 3
@State var data = Array(0..<5)

Pager(page: self.$page,
data: self.data,
id: \.self) {
self.pageView($0)
}
.onPageChanged({ page in
guard page == 1 else { return }
let newData = (1...5).map { $0 * self.count }
withAnimation {
self.data1.insert(contentsOf: newData, at: 0)
self.page1 += 5
self.count -= 1
}
})
```

## Content Loading Policy

`Pager` recycles views by default and won't have loaded all pages in memory at once. In some scenarios, this might be counterproductive, for example, if you're trying to manually scroll from the first page to the last. For these scenarios, use `.contentLoadingPolicy` and choose among the options available.
Expand Down
13 changes: 0 additions & 13 deletions Example/SwiftUIPagerExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
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 */; };
6BB1AAD324C9C9D20032B5A3 /* PagerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BB1AAD224C9C9D20032B5A3 /* PagerProxy.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 */; };
Expand Down Expand Up @@ -67,7 +66,6 @@
6B4EC8A7240D1182001E7490 /* BizarreExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BizarreExampleView.swift; sourceTree = "<group>"; };
6B6FAA3C24D553C8000D1539 /* PagingAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PagingAnimation.swift; path = ../../Sources/SwiftUIPager/PageConfiguration/PagingAnimation.swift; sourceTree = "<group>"; };
6B9C4A8924B45F66004C06C5 /* OnDeactivateModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnDeactivateModifier.swift; sourceTree = "<group>"; };
6BB1AAD224C9C9D20032B5A3 /* PagerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagerProxy.swift; sourceTree = "<group>"; };
6BB1AAD424C9CA1C0032B5A3 /* PagerContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PagerContent.swift; path = ../../Sources/SwiftUIPager/PagerContent.swift; sourceTree = "<group>"; };
6BB1AAD624C9D0820032B5A3 /* Pager+Buildable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Pager+Buildable.swift"; path = "../../Sources/SwiftUIPager/Pager+Buildable.swift"; sourceTree = "<group>"; };
6BC5EDF424866D9500E1E78C /* PagerContent+Buildable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "PagerContent+Buildable.swift"; path = "../../Sources/SwiftUIPager/PagerContent+Buildable.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -147,21 +145,11 @@
path = Examples;
sourceTree = "<group>";
};
6BB1AAD124C9C9370032B5A3 /* Proxy */ = {
isa = PBXGroup;
children = (
6BB1AAD224C9C9D20032B5A3 /* PagerProxy.swift */,
);
name = Proxy;
path = ../../Sources/SwiftUIPager/Proxy;
sourceTree = "<group>";
};
6BC5EDEB24866D6000E1E78C /* Pagination */ = {
isa = PBXGroup;
children = (
6BC5EDF524866D9500E1E78C /* Helpers */,
6BEA730E24ACF8BB007EA8DC /* PageConfiguration */,
6BB1AAD124C9C9370032B5A3 /* Proxy */,
6BC5EDFA24866D9500E1E78C /* Pager.swift */,
6BB1AAD624C9D0820032B5A3 /* Pager+Buildable.swift */,
6BB1AAD424C9CA1C0032B5A3 /* PagerContent.swift */,
Expand Down Expand Up @@ -274,7 +262,6 @@
buildActionMask = 2147483647;
files = (
6B4EC8A2240D072B001E7490 /* ColorsExampleView.swift in Sources */,
6BB1AAD324C9C9D20032B5A3 /* PagerProxy.swift in Sources */,
17D9E0F423D4CF6700C5AE93 /* AppDelegate.swift in Sources */,
6BEF677024C98B62008533FE /* PageWrapper.swift in Sources */,
6BD3828224C97DE3007B1CF6 /* CGPoint+Angle.swift in Sources */,
Expand Down
19 changes: 19 additions & 0 deletions Example/SwiftUIPagerExample/Examples/NestedExampleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import SwiftUI
struct NestedExampleView: View {

@State var page: Int = 0
@State var pageOffset: Double = 0
@State var nestedPages: [Int] = [0, 0, 0, 0]

var data = Array(0..<4)
Expand All @@ -22,6 +23,7 @@ struct NestedExampleView: View {
id: \.self) { page in
self.nestedPager(page)
}
.pageOffset(pageOffset)
.swipeInteractionArea(.allAvailable)
.background(Color.gray.opacity(0.2))
}
Expand All @@ -47,9 +49,26 @@ struct NestedExampleView: View {
id: \.self) { page in
self.pageView(page)
}
.bounces(false)
.onDraggingBegan({
print("Dragging Began")
})
.onDraggingChanged { increment in
withAnimation {
if binding.wrappedValue == self.nestedData.count - 1, increment > 0 {
pageOffset = increment
} else if binding.wrappedValue == 0, increment < 0 {
pageOffset = increment
}
}
}
.onDraggingEnded { increment in
guard pageOffset != 0 else { return }
withAnimation {
pageOffset = 0
page += Int(increment.rounded())
}
}
.itemSpacing(10)
.itemAspectRatio(0.8, alignment: .end)
.padding(8)
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

_SwiftUIPager_ provides a `Pager` component built with SwiftUI native components. `Pager` is a view that renders a scrollable container to display a handful of pages. These pages are recycled on scroll, so you don't have to worry about memory issues. `Pager` will load just a handful of items, enough to beatifully scroll along.

Create vertical or horizontal pagers, align the cards, change the direction of the scroll, animate the pagintation... `Pager` lets you do anything you want.
Create vertical or horizontal pagers, align the cards, change the direction of the scroll, animate the pagination... `Pager` lets you do anything you want.

- [Requirements](#requirements)
- [Installation](#installation)
Expand All @@ -28,12 +28,13 @@ Create vertical or horizontal pagers, align the cards, change the direction of t
- [Alignment](Documentation/Usage.md#alignment)
- [Partial pagination](Documentation/Usage.md#partial-pagination)
- [Multiple pagination](Documentation/Usage.md#multiple-pagination)
- [More modifiers](Documentation/Usage.md#more-modifiers)
- [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)
- [Page Transitions](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
35 changes: 27 additions & 8 deletions Sources/SwiftUIPager/Pager+Buildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import SwiftUI

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension Pager: Buildable, PagerProxy {
extension Pager: Buildable {

/// Result of paginating
public typealias DragResult = (page: Int, newPage: Int, translation: CGFloat, velocity: Double)
Expand Down Expand Up @@ -113,6 +113,32 @@ extension Pager: Buildable, PagerProxy {
mutating(keyPath: \.swipeInteractionArea, value: value)
}

/// Sets whether `Pager` should bounce or not
public func bounces(_ value: Bool) -> Self {
mutating(keyPath: \.bounces, value: value)
}

/// Adds a callback to react when dragging begins
///
/// - Parameter callback: block to be called when dragging begins
public func onDraggingBegan(_ callback: (() -> Void)?) -> Self {
mutating(keyPath: \.onDraggingBegan, value: callback)
}

/// Adds a callback to react when dragging changes
///
/// - Parameter callback: block to be called when dragging changes. `pageInrement` is passed as argument
public func onDraggingChanged(_ callback: ((Double) -> Void)?) -> Self {
mutating(keyPath: \.onDraggingChanged, value: callback)
}

/// Adds a callback to react when dragging ends
///
/// - Parameter callback: block to be called when dragging ends. `pageInrement` is passed as argument
public func onDraggingEnded(_ callback: ((Double) -> Void)?) -> Self {
mutating(keyPath: \.onDraggingEnded, value: callback)
}

#endif

/// Changes the a the alignment of the pages relative to their container
Expand Down Expand Up @@ -216,13 +242,6 @@ extension Pager: Buildable, PagerProxy {
public func onPageChanged(_ callback: ((Int) -> Void)?) -> Self {
mutating(keyPath: \.onPageChanged, value: callback)
}

/// Adds a callback to react when dragging begins. Useful for dismissing a keyboard like a scrollview
///
/// - Parameter callback: block to be called when dragging begins
public func onDraggingBegan(_ callback: (() -> Void)?) -> Self {
mutating(keyPath: \.onDraggingBegan, value: callback)
}

/// Sets some padding on the non-scroll axis
///
Expand Down
14 changes: 13 additions & 1 deletion 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 ***/

/// Whether `Pager` should bounce or not
var bounces: Bool = true

/// Max relative item size that `Pager` will scroll before determining whether to move to the next page
var pageRatio: CGFloat = 1

Expand Down Expand Up @@ -139,6 +142,12 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
/// Callback for when dragging begins
var onDraggingBegan: (() -> Void)?

/// Callback for when dragging changes
var onDraggingChanged: ((Double) -> Void)?

/// Callback for when dragging ends
var onDraggingEnded: ((Double) -> Void)?

/*** State and Binding properties ***/

/// Size of the view
Expand Down Expand Up @@ -200,7 +209,6 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
.itemSpacing(itemSpacing)
.itemAspectRatio(itemAspectRatio, alignment: itemAlignment)
.onPageChanged(onPageChanged)
.onDraggingBegan(onDraggingBegan)
.padding(sideInsets)
.pagingAnimation(pagingAnimation)
.partialPagination(pageRatio)
Expand All @@ -212,6 +220,10 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
.pagingPriority(gesturePriority)
.delaysTouches(delaysTouches)
.sensitivity(sensitivity)
.onDraggingBegan(onDraggingBegan)
.onDraggingChanged(onDraggingChanged)
.onDraggingEnded(onDraggingEnded)
.bounces(bounces)
#endif

pagerContent = allowsMultiplePagination ? pagerContent.multiplePagination() : pagerContent
Expand Down
35 changes: 27 additions & 8 deletions Sources/SwiftUIPager/PagerContent+Buildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import SwiftUI

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension Pager.PagerContent: Buildable, PagerProxy {
extension Pager.PagerContent: Buildable {

/// Sets the animation to be applied when the user stops dragging
///
Expand Down Expand Up @@ -109,6 +109,32 @@ extension Pager.PagerContent: Buildable, PagerProxy {
mutating(keyPath: \.swipeInteractionArea, value: value)
}

/// Sets whether `Pager` should bounce or not
func bounces(_ value: Bool) -> Self {
mutating(keyPath: \.bounces, value: value)
}

/// Adds a callback to react when dragging begins. Useful for dismissing a keyboard like a scrollview
///
/// - Parameter callback: block to be called when dragging begins
func onDraggingBegan(_ callback: (() -> Void)?) -> Self {
mutating(keyPath: \.onDraggingBegan, value: callback)
}

/// Adds a callback to react when dragging changes
///
/// - Parameter callback: block to be called when dragging changes. `pageInrement` is passed as argument
func onDraggingChanged(_ callback: ((Double) -> Void)?) -> Self {
mutating(keyPath: \.onDraggingChanged, value: callback)
}

/// Adds a callback to react when dragging ends
///
/// - Parameter callback: block to be called when dragging ends. `pageInrement` is passed as argument
func onDraggingEnded(_ callback: ((Double) -> Void)?) -> Self {
mutating(keyPath: \.onDraggingEnded, value: callback)
}

#endif

/// Changes the a the alignment of the pages relative to their container
Expand Down Expand Up @@ -205,13 +231,6 @@ extension Pager.PagerContent: Buildable, PagerProxy {
func onPageChanged(_ callback: ((Int) -> Void)?) -> Self {
mutating(keyPath: \.onPageChanged, value: callback)
}

/// Adds a callback to react when dragging begins. Useful for dismissing a keyboard like a scrollview
///
/// - Parameter callback: block to be called when dragging begins
func onDraggingBegan(_ callback: (() -> Void)?) -> Self {
mutating(keyPath: \.onDraggingBegan, value: callback)
}

/// Sets some padding on the non-scroll axis
///
Expand Down
7 changes: 4 additions & 3 deletions Sources/SwiftUIPager/PagerContent+Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,16 @@ extension Pager.PagerContent {
/// Minimum offset allowed. This allows a bounce offset
var offsetLowerbound: CGFloat {
guard currentPage == 0, !isInifinitePager else { return CGFloat(numberOfPages) * self.size.width }
return CGFloat(numberOfPagesDisplayed) / 2 * pageDistance - pageDistance / 4 + alignmentOffset
let bounceOffset = bounces ? -pageDistance / 4 : -pageDistance / 2
return CGFloat(numberOfPagesDisplayed) / 2 * pageDistance + bounceOffset + alignmentOffset
}

/// Maximum offset allowed. This allows a bounce offset
var offsetUpperbound: CGFloat {
guard currentPage == numberOfPages - 1, !isInifinitePager else { return -CGFloat(numberOfPages) * self.size.width }
let a = -CGFloat(numberOfPagesDisplayed) / 2
let b = pageDistance / 4
return a * pageDistance + b + alignmentOffset
let bounceOffset = bounces ? pageDistance / 4 : pageDistance / 2
return a * pageDistance + bounceOffset + alignmentOffset
}

/// Addition of `draggingOffset` and `contentOffset`
Expand Down
Loading

0 comments on commit 3e091c5

Please sign in to comment.