Skip to content

Instantly share code, notes, and snippets.

@fishkingsin
Last active October 14, 2022 17:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fishkingsin/35133f417a2c39b43c0acc15df36072b to your computer and use it in GitHub Desktop.
Save fishkingsin/35133f417a2c39b43c0acc15df36072b to your computer and use it in GitHub Desktop.
SwiftUI Pager Demo
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)
}
}
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)
}
)
}
}
}
public struct ShareCardBaseView<HeaderContent: View, BodyContent: View, FooterContent: View>: View {
public var headerContent: HeaderContent
public var bodyContent: BodyContent
public var footerContent: FooterContent
public init(@ViewBuilder headerContent: @escaping () -> HeaderContent,
@ViewBuilder bodyContent: @escaping () -> BodyContent,
@ViewBuilder footerContent: @escaping () -> FooterContent) {
self.headerContent = headerContent()
self.bodyContent = bodyContent()
self.footerContent = footerContent()
}
public var body: some View {
GeometryReader { metrics in
VStack(alignment: .center, spacing: 0, content: {
headerContent
bodyContent
footerContent
})
.padding(0)
.background(Color.yellow)
.clipShape(
RoundedRectangle(
cornerRadius: 10
)
)
.frame(width: metrics.size.width * 0.9,
height: metrics.size.height * 0.8,
alignment: .center)
.position(x: metrics.frame(in: .local).midX,
y: metrics.frame(in: .local).midY)
}
}
}
public struct ShareCardContentDecorator<Content>: View where Content: View {
public var contentBuilder: () -> Content
public init(@ViewBuilder contentBuilder: @escaping () -> Content) {
self.contentBuilder = contentBuilder
}
public var body: some View {
ZStack {
contentBuilder()
.frame(
minWidth: 0,
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .center
)
}.background(Color.red)
}
}
public struct ShareCardFootDecorator<Content>: View where Content: View {
public var contentBuilder: () -> Content
public init(@ViewBuilder contentBuilder: @escaping () -> Content) {
self.contentBuilder = contentBuilder
}
public var body: some View {
ZStack {
contentBuilder()
}.background(Color.green)
}
}
public struct ShareCardHeaderDecorator<Content>: View where Content: View {
public var contentBuilder: () -> Content
public init(@ViewBuilder contentBuilder: @escaping () -> Content) {
self.contentBuilder = contentBuilder
}
public var body: some View {
ZStack {
contentBuilder()
}
}
}
public struct ShareDefaultCardView: View {
public init() {
}
public var body: some View {
ShareCardBaseView<
ShareCardHeaderDecorator,
ShareCardContentDecorator,
ShareCardFootDecorator> {
ShareCardHeaderDecorator(contentBuilder: {
VStack {
Text("Dummy Header")
Text("Dummy Header")
Text("Dummy Header")
}
.frame(
minWidth: 0,
maxWidth: .infinity,
alignment: .center
)
.background(Color.blue)
})
} bodyContent: {
ShareCardContentDecorator {
Text("Dummy Content")
}
} footerContent: {
ShareCardFootDecorator(contentBuilder: {
VStack {
Text("Dummy Footer")
Text("Dummy Footer")
Text("Dummy Footer")
}
.frame(
minWidth: 0,
maxWidth: .infinity,
alignment: .center
)
.background(Color.green)
})
}
}
}
public struct ShareDefaultCardView2: View {
public init() {
}
public var body: some View {
ShareCardBaseView<
ShareCardHeaderDecorator,
ShareCardContentDecorator,
ShareCardFootDecorator> {
ShareCardHeaderDecorator(contentBuilder: {
VStack {
Image(systemName: "heart.fill")
}
.frame(
minWidth: 0,
maxWidth: .infinity,
alignment: .center
)
.background(Color.blue)
})
} bodyContent: {
ShareCardContentDecorator {
Text("Dummy")
}
} footerContent: {
ShareCardFootDecorator(contentBuilder: {
VStack {
Image(systemName: "star.fill")
}
.frame(
minWidth: 0,
maxWidth: .infinity,
alignment: .center
)
.background(Color.green)
})
}
}
}
struct PagerData: Identifiable {
let id = UUID()
var title: String
var type: Int
}
public struct ShareDemoContentView: View {
@State private var currentPage = 0
var datas = [
PagerData(title: "Juan Chavez", type: 0),
PagerData(title: "Mei Chen", type: 1),
PagerData(title: "Title", type: 0)
]
public init() {
}
public var body: some View {
VStack {
PagerView(pageCount: 3, currentIndex: $currentPage) {
ForEach(datas) { data in
switch data.type {
case 1:
ShareDefaultCardView2()
default:
ShareDefaultCardView()
}
}
}
PageControl(selectedPage: .constant(currentPage + 1),
pages: 3,
circleDiameter: 15.0,
circleMargin: 10.0)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment