// | |
// PagerView.swift | |
// | |
// Created by Majid Jabrayilov on 12/5/19. | |
// Copyright © 2019 Majid Jabrayilov. All rights reserved. | |
// | |
import SwiftUI | |
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) | |
} | |
) | |
} | |
} | |
} |
This comment has been minimized.
This comment has been minimized.
I'm not sure I understand the problem, could you please describe it? |
This comment has been minimized.
This comment has been minimized.
@majid Here is a sample + relevant code https://gist.github.com/Plnda/121b5960807074cf5fd68c68f959aa54 |
This comment has been minimized.
This comment has been minimized.
Interesting issue, you can add animation(nil) modifier to the content inside PagerView, I think it should help. |
This comment has been minimized.
This comment has been minimized.
@mecid A bug I noticed (which I've seen in some other apps) when you are switching between vertical scroll views diagonally, all objects will shift horizontally and remain that way until you scroll horizontally again to the next page. Is there a way to disable horizontal scrolling while you are scrolling vertically? |
This comment has been minimized.
This comment has been minimized.
I've added a Page Control at bottom. import SwiftUI
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
ZStack {
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)
}
)
VStack {
Spacer()
HStack {
ForEach(0..<self.pageCount, id: \.self) { index in
Circle()
.fill(index == self.currentIndex ? Color.white : Color.gray)
.frame(width: 10, height: 10)
}
}
}
.offset(y: 16)
}
}
}
} |
This comment has been minimized.
This comment has been minimized.
@fnazarios that is great! Thanks 🙏🏻 |
This comment has been minimized.
This comment has been minimized.
Did you guys found a solution for this problem? I still facing the problem. :( |
This comment has been minimized.
This comment has been minimized.
@mmosimann disabling animation with .animation(nil) or applying it by withAnimation should solve the issue. |
This comment has been minimized.
This comment has been minimized.
Hello! I have tried to make the animation a bit smoother and also tried to use import SwiftUI
struct PagerView<Content: View>: View {
let pageCount: Int
@Binding var currentIndex: Int
@State var currentFloatIndex: CGFloat = 0
let content: Content
@GestureState private var offsetX: 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.currentFloatIndex) * geometry.size.width)
.offset(x: self.offsetX)
.highPriorityGesture(
DragGesture().updating(self.$offsetX) { value, state, _ in
state = value.translation.width
}
.onEnded({ (value) in
let offset = value.translation.width / geometry.size.width
let offsetPredicted = value.predictedEndTranslation.width / geometry.size.width
let newIndex = CGFloat(self.currentFloatIndex) - offset
self.currentFloatIndex = newIndex
withAnimation(.easeOut) {
if(offsetPredicted < -0.5 && offset > -0.5) {
self.currentFloatIndex = CGFloat(min(max(Int(newIndex.rounded() + 1), 0), self.pageCount - 1))
} else if (offsetPredicted > 0.5 && offset < 0.5) {
self.currentFloatIndex = CGFloat(min(max(Int(newIndex.rounded() - 1), 0), self.pageCount - 1))
} else {
self.currentFloatIndex = CGFloat(min(max(Int(newIndex.rounded()), 0), self.pageCount - 1))
}
}
})
)
}
}
} |
This comment has been minimized.
This comment has been minimized.
@d03090 The animation looks very nice. Have you found a solution for Binding? |
This comment has been minimized.
This comment has been minimized.
Thanks. I just have a VERY UGLY solution and I would be very happy, if someone can do it better. import SwiftUI
struct PagerView<Content: View>: View {
let pageCount: Int
@State var ignore: Bool = false
@Binding var currentIndex: Int {
didSet {
if (!ignore) {
currentFloatIndex = CGFloat(currentIndex)
}
}
}
@State var currentFloatIndex: CGFloat = 0 {
didSet {
ignore = true
currentIndex = min(max(Int(currentFloatIndex.rounded()), 0), self.pageCount - 1)
ignore = false
}
}
let content: Content
@GestureState private var offsetX: 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.currentFloatIndex) * geometry.size.width)
.offset(x: self.offsetX)
.highPriorityGesture(
DragGesture().updating(self.$offsetX) { value, state, _ in
state = value.translation.width
}
.onEnded({ (value) in
let offset = value.translation.width / geometry.size.width
let offsetPredicted = value.predictedEndTranslation.width / geometry.size.width
let newIndex = CGFloat(self.currentFloatIndex) - offset
self.currentFloatIndex = newIndex
withAnimation(.easeOut) {
if(offsetPredicted < -0.5 && offset > -0.5) {
self.currentFloatIndex = CGFloat(min(max(Int(newIndex.rounded() + 1), 0), self.pageCount - 1))
} else if (offsetPredicted > 0.5 && offset < 0.5) {
self.currentFloatIndex = CGFloat(min(max(Int(newIndex.rounded() - 1), 0), self.pageCount - 1))
} else {
self.currentFloatIndex = CGFloat(min(max(Int(newIndex.rounded()), 0), self.pageCount - 1))
}
}
})
)
}
.onChange(of: currentIndex, perform: { value in
print("index changed")
// this is probably animated twice, if the tab change occurs because of the drag gesture
withAnimation(.easeOut) {
currentFloatIndex = CGFloat(value)
}
})
}
} |
This comment has been minimized.
@mecid How would you solve animation on the pages? now when I have a animation set to a page the whole thing starts animating when I drag