Skip to content

Commit

Permalink
Adding modifier loopPages
Browse files Browse the repository at this point in the history
Support for an inifinite scrolll
  • Loading branch information
Fernando Moya de Rivas committed May 23, 2020
1 parent 10d017d commit a43c647
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Example/SwiftUIPagerExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@
6B4EC8A0240D0710001E7490 /* Examples */ = {
isa = PBXGroup;
children = (
6B4EC8A1240D072B001E7490 /* ColorsExampleView.swift */,
6B4EC8A3240D07D5001E7490 /* SimpleExampleView.swift */,
6B4EC8A1240D072B001E7490 /* ColorsExampleView.swift */,
6B4EC8A5240D0918001E7490 /* PresentedExampleView.swift */,
6B4EC8A7240D1182001E7490 /* BizarreExampleView.swift */,
);
Expand Down
3 changes: 2 additions & 1 deletion Example/SwiftUIPagerExample/Examples/SimpleExampleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI
struct SimpleExampleView: View {

@State var page: Int = 0
@State var data = Array(0..<10)
@State var data = Array(0..<5)

var body: some View {
GeometryReader { proxy in
Expand All @@ -20,6 +20,7 @@ struct SimpleExampleView: View {
id: \.self) {
self.pageView($0)
}
.loopPages()
.itemSpacing(10)
.itemAspectRatio(1.3, alignment: .end)
.padding(20)
Expand Down
7 changes: 6 additions & 1 deletion Sources/SwiftUIPager/Pager+Buildable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,17 @@ extension Pager: Buildable {
case page
}

/// Sets `Pager` to loop the items in a never-ending scroll
public func loopPages(_ value: Bool = true) -> Self {
mutating(keyPath: \.isInifinitePager, value: value)
}

/// Disables dragging on `Pager`
public func disableDragging() -> Self {
mutating(keyPath: \.allowsDragging, value: false)
}

/// Disables dragging on `Pager`
/// Sets whether the dragging is enabled or not
public func allowsDragging(_ value: Bool = true) -> Self {
mutating(keyPath: \.allowsDragging, value: value)
}
Expand Down
51 changes: 39 additions & 12 deletions Sources/SwiftUIPager/Pager+Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,21 @@ extension Pager {
/// The current page index. Will equal `page` if not dragging
var currentPage: Int {
guard isDragging else { return page }
let newPage = -Int((totalOffset / self.pageDistance).rounded()) + self.page
return max(min(newPage, self.numberOfPages - 1), 0)
let newPage = -Int((totalOffset / pageDistance).rounded()) + page

guard isInifinitePager else { return max(min(newPage, numberOfPages - 1), 0) }
return max((newPage + numberOfPages) % numberOfPages, 0)
}

/// Minimum offset allowed. This allows a bounce offset
var offsetLowerbound: CGFloat {
guard currentPage == 0 else { return CGFloat(numberOfPages) * self.size.width }
guard currentPage == 0, !isInifinitePager else { return CGFloat(numberOfPages) * self.size.width }
return CGFloat(numberOfPagesDisplayed) / 2 * pageDistance - pageDistance / 4 + alignmentOffset
}

/// Maximum offset allowed. This allows a bounce offset
var offsetUpperbound: CGFloat {
guard currentPage == numberOfPages - 1 else { return -CGFloat(numberOfPages) * self.size.width }
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
Expand Down Expand Up @@ -97,27 +99,38 @@ extension Pager {
var maximumNumberOfPages: Int {
guard pageDistance != 0 else { return 0 }
let side = isHorizontal ? size.width : size.height
return Int((CGFloat(recyclingRatio) * side / pageDistance / 2).rounded(.up))
return min(numberOfPages, Int((CGFloat(recyclingRatio) * side / pageDistance).rounded(.up)))
}

/// Number of pages displayed at the moment
/// Number of pages in memory at the moment
var numberOfPagesDisplayed: Int {
upperPageDisplayed - lowerPageDisplayed
guard isInifinitePager else { return upperPageDisplayed - lowerPageDisplayed }
return maximumNumberOfPages
}

/// Data that is being displayed at the moment
var dataDisplayed: [Element] {
Array(data[lowerPageDisplayed..<upperPageDisplayed])
var items: [Element] = []

var index = lowerPageDisplayed
while items.count < numberOfPagesDisplayed {
items.append(data[index])
index = (index + 1) % numberOfPages
}

return items
}

/// Lower bound of the data displaed
var lowerPageDisplayed: Int {
return max(0, page - maximumNumberOfPages)
guard isInifinitePager else { return max(0, page - maximumNumberOfPages) }
return ((page - maximumNumberOfPages / 2) + numberOfPages) % numberOfPages
}

/// Upper bound of the data displaed
var upperPageDisplayed: Int {
return min(numberOfPages, maximumNumberOfPages + page)
guard isInifinitePager else { return min(numberOfPages, maximumNumberOfPages + page) }
return (Int((Float(maximumNumberOfPages) / 2).rounded(.up)) + page) % numberOfPages
}

/// Extra offset to complentate the alignment
Expand Down Expand Up @@ -157,10 +170,11 @@ extension Pager {

/// Offset applied to `HStack` along the X-Axis. It's limitted by `offsetUpperbound` and `offsetUpperbound`
var xOffset: CGFloat {
let page = CGFloat(self.page - lowerPageDisplayed)
let indexOfPageFocused = CGFloat(dataDisplayed.firstIndex(where: { data.firstIndex(of: $0) == self.page }) ?? 0)
let numberOfPages = CGFloat(numberOfPagesDisplayed)
let xIncrement = pageDistance / 2
let offset = (numberOfPages / 2 - page) * pageDistance - xIncrement + totalOffset + alignmentOffset
let offset = (numberOfPages / 2 - indexOfPageFocused) * pageDistance - xIncrement + totalOffset + alignmentOffset

return max(offsetUpperbound, min(offsetLowerbound, offset))
}

Expand Down Expand Up @@ -213,4 +227,17 @@ extension Pager {
data.firstIndex(of: item) == currentPage
}

/// Returns true if the item is the first or last element in memory
func isEdgePage(_ item: Element) -> Bool {
let side = isHorizontal ? size.width : size.height
let numberOfElementsOnScreen = Int(((side - pageDistance) / pageDistance).rounded(.up)) * 2 + 1

guard numberOfElementsOnScreen < maximumNumberOfPages else { return false }
guard let index = dataDisplayed.firstIndex(of: item) else { return false }
guard dataDisplayed.count == maximumNumberOfPages else {
return false
}
return index == 0 || index == maximumNumberOfPages - 1
}

}
11 changes: 8 additions & 3 deletions Sources/SwiftUIPager/Pager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
/*** Constants ***/

/// Manages the number of items that should be displayed in the screen.
/// A ratio of 3, for instance, would mean the items held in memory are enough
/// to cover 3 times the size of the pager
/// A ratio of 5, for instance, would mean the items held in memory are enough
/// to cover 5 times the size of the pager
let recyclingRatio = 5

/// Angle of rotation when should rotate
Expand Down Expand Up @@ -101,6 +101,9 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
/// Space between pages
var itemSpacing: CGFloat = 0

/// Whether the `Pager` loops endlessly
var isInifinitePager: Bool = false

/// Minimum distance for `Pager` to start scrolling
var minimumDistance: CGFloat = 15

Expand Down Expand Up @@ -146,6 +149,7 @@ public struct Pager<Element, ID, PageView>: View where PageView: View, Element:
ForEach(dataDisplayed, id: id) { item in
self.content(item)
.frame(size: self.pageSize)
.opacity(self.isInifinitePager && self.isEdgePage(item) ? 0 : 1)
.scaleEffect(self.scale(for: item))
.rotation3DEffect((self.isHorizontal ? .zero : Angle(degrees: -90)) - self.scrollDirectionAngle,
axis: (0, 0, 1))
Expand Down Expand Up @@ -212,11 +216,12 @@ extension Pager {
self.draggingOffset = value.translation.width
}
}).onEnded({ (value) in
let velocity = -Double(value.translation.width) / value.time.timeIntervalSince(self.draggingStartTime ?? Date())
let velocity = -Double(self.draggingOffset) / value.time.timeIntervalSince(self.draggingStartTime ?? Date())
var newPage = self.currentPage
if newPage == self.page, abs(velocity) > 1000 {
newPage = newPage + Int(velocity / abs(velocity))
}

newPage = max(0, min(self.numberOfPages - 1, newPage))

withAnimation(.easeOut) {
Expand Down

0 comments on commit a43c647

Please sign in to comment.