Last active
October 14, 2022 17:26
-
-
Save fishkingsin/35133f417a2c39b43c0acc15df36072b to your computer and use it in GitHub Desktop.
SwiftUI Pager Demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct PageControl: View { | |
@Binding var selectedPage: Int | |
var pages: Int | |
var circleDiameter: CGFloat | |
var circleMargin: CGFloat | |
var body: some View { | |
ZStack { | |
// Total number of pages | |
HStack(spacing: circleMargin) { | |
ForEach(0 ..< pages) { _ in | |
Circle() | |
.stroke(Color.black, style: StrokeStyle(lineWidth: 2, lineCap: .round)) | |
.frame(width: circleDiameter, height: circleDiameter) | |
} | |
} | |
// Current page index | |
Circle() | |
.foregroundColor(.black) | |
.frame(width: circleDiameter, height: circleDiameter) | |
.offset(x: currentPosition).animation(.linear(duration: 0.3)) | |
} | |
} | |
private var circleRadius: CGFloat { circleDiameter / 2} | |
private var pageIndex: CGFloat { CGFloat(selectedPage - 1) } | |
private var currentPosition: CGFloat { | |
// Get the first circle position | |
let stackWidth = circleDiameter * CGFloat(pages) + circleMargin * CGFloat(pages - 1) | |
let halfStackWidth = stackWidth / 2 | |
let iniPosition = -halfStackWidth + circleRadius | |
// Calculate the distance to get the next circle | |
let distanceToNextPoint = circleDiameter + circleMargin | |
// Use the pageIndex to get the current position | |
return iniPosition + (pageIndex * distanceToNextPoint) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
struct PagerView<Content: View>: View { | |
let pageCount: Int | |
@Binding var currentIndex: Int | |
let content: Content | |
@GestureState private var translation: CGFloat = 0 | |
init(pageCount: Int, currentIndex: Binding<Int>, @ViewBuilder content: () -> Content) { | |
self.pageCount = pageCount | |
self._currentIndex = currentIndex | |
self.content = content() | |
} | |
var body: some View { | |
GeometryReader { geometry in | |
HStack(spacing: 0) { | |
self.content.frame(width: geometry.size.width) | |
} | |
.frame(width: geometry.size.width, alignment: .leading) | |
.offset(x: -CGFloat(self.currentIndex) * geometry.size.width) | |
.offset(x: self.translation) | |
.animation(.interactiveSpring()) | |
.gesture( | |
DragGesture().updating(self.$translation) { value, state, _ in | |
state = value.translation.width | |
}.onEnded { value in | |
let offset = value.translation.width / geometry.size.width | |
let newIndex = (CGFloat(self.currentIndex) - offset).rounded() | |
self.currentIndex = min(max(Int(newIndex), 0), self.pageCount - 1) | |
} | |
) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment