Skip to content

Commit

Permalink
Merge pull request #38 from fermoya/feat/issue-34-infinite-loop
Browse files Browse the repository at this point in the history
Adding modifier loopPages
  • Loading branch information
fermoya authored May 23, 2020
2 parents 10d017d + 6e714e1 commit b5911b6
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 27 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
2 changes: 1 addition & 1 deletion SwiftUIPager.podspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|

s.name = "SwiftUIPager"
s.version = "1.4.1"
s.version = "1.5.0-beta.1"
s.summary = "Native pager for SwiftUI. Easily to use, easy to customize."

s.description = <<-DESC
Expand Down
14 changes: 6 additions & 8 deletions SwiftUIPager.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
/* Begin PBXFileReference section */
172F4D6023DF830600FD2F15 /* SwiftUIPager_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftUIPager_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
172F4D6123DF830600FD2F15 /* Info-macOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-macOS.plist"; path = "/Users/fermoya/Documents/Tests/SwiftUIPager/SwiftUIPager/Info-macOS.plist"; sourceTree = "<absolute>"; };
172F4D7223DF839000FD2F15 /* Info-watchOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-watchOS.plist"; path = "/Users/fermoya/Documents/Tests/SwiftUIPager/SwiftUIPager/Info-watchOS.plist"; sourceTree = "<absolute>"; };
172F4D7823DF8A6400FD2F15 /* SwiftUIPager_watchOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftUIPager_watchOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
172F4D7B23DF8A6400FD2F15 /* Info_watchOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info_watchOS.plist; sourceTree = "<group>"; };
6BBBB7D0240431370058EE56 /* PositionAlignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PositionAlignment.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -104,7 +103,6 @@
6BDE441B23DE10C10022A2F7 /* Info-iOS.plist */,
172F4D6123DF830600FD2F15 /* Info-macOS.plist */,
172F4D7B23DF8A6400FD2F15 /* Info_watchOS.plist */,
172F4D7223DF839000FD2F15 /* Info-watchOS.plist */,
);
path = SwiftUIPager;
sourceTree = "<group>";
Expand Down Expand Up @@ -329,7 +327,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.5.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fmoyader.example.SwiftUIPager;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
Expand Down Expand Up @@ -358,7 +356,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.5.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fmoyader.example.SwiftUIPager;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
Expand Down Expand Up @@ -386,7 +384,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.5.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.fermoya.challenge.SwiftUIPager-watchOS";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = watchos;
Expand Down Expand Up @@ -415,7 +413,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.5.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.fermoya.challenge.SwiftUIPager-watchOS";
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = watchos;
Expand Down Expand Up @@ -573,7 +571,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.5.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fmoyader.example.SwiftUIPager;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -601,7 +599,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.4.1;
MARKETING_VERSION = 1.5.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fmoyader.example.SwiftUIPager;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
Expand Down

0 comments on commit b5911b6

Please sign in to comment.