From a43c647afc5b7f7c67c6f7dcba2920f72a0fb973 Mon Sep 17 00:00:00 2001 From: Fernando Moya de Rivas Date: Sat, 23 May 2020 20:36:32 +0100 Subject: [PATCH] Adding modifier loopPages Support for an inifinite scrolll --- .../project.pbxproj | 2 +- .../Examples/SimpleExampleView.swift | 3 +- Sources/SwiftUIPager/Pager+Buildable.swift | 7 ++- Sources/SwiftUIPager/Pager+Helper.swift | 51 ++++++++++++++----- Sources/SwiftUIPager/Pager.swift | 11 ++-- 5 files changed, 56 insertions(+), 18 deletions(-) diff --git a/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj b/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj index 42a6f0f..0d2260e 100644 --- a/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj +++ b/Example/SwiftUIPagerExample.xcodeproj/project.pbxproj @@ -114,8 +114,8 @@ 6B4EC8A0240D0710001E7490 /* Examples */ = { isa = PBXGroup; children = ( - 6B4EC8A1240D072B001E7490 /* ColorsExampleView.swift */, 6B4EC8A3240D07D5001E7490 /* SimpleExampleView.swift */, + 6B4EC8A1240D072B001E7490 /* ColorsExampleView.swift */, 6B4EC8A5240D0918001E7490 /* PresentedExampleView.swift */, 6B4EC8A7240D1182001E7490 /* BizarreExampleView.swift */, ); diff --git a/Example/SwiftUIPagerExample/Examples/SimpleExampleView.swift b/Example/SwiftUIPagerExample/Examples/SimpleExampleView.swift index 26b43d6..f511059 100644 --- a/Example/SwiftUIPagerExample/Examples/SimpleExampleView.swift +++ b/Example/SwiftUIPagerExample/Examples/SimpleExampleView.swift @@ -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 @@ -20,6 +20,7 @@ struct SimpleExampleView: View { id: \.self) { self.pageView($0) } + .loopPages() .itemSpacing(10) .itemAspectRatio(1.3, alignment: .end) .padding(20) diff --git a/Sources/SwiftUIPager/Pager+Buildable.swift b/Sources/SwiftUIPager/Pager+Buildable.swift index cf117f2..fb81dfc 100644 --- a/Sources/SwiftUIPager/Pager+Buildable.swift +++ b/Sources/SwiftUIPager/Pager+Buildable.swift @@ -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) } diff --git a/Sources/SwiftUIPager/Pager+Helper.swift b/Sources/SwiftUIPager/Pager+Helper.swift index 840097b..eded93f 100644 --- a/Sources/SwiftUIPager/Pager+Helper.swift +++ b/Sources/SwiftUIPager/Pager+Helper.swift @@ -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 @@ -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.. 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 + } + } diff --git a/Sources/SwiftUIPager/Pager.swift b/Sources/SwiftUIPager/Pager.swift index d2102d8..1df2180 100644 --- a/Sources/SwiftUIPager/Pager.swift +++ b/Sources/SwiftUIPager/Pager.swift @@ -42,8 +42,8 @@ public struct Pager: 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 @@ -101,6 +101,9 @@ public struct Pager: 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 @@ -146,6 +149,7 @@ public struct Pager: 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)) @@ -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) {