Created
March 10, 2021 15:32
-
-
Save wtpalexander/565ccf7bb092734dbf2c584dd639fb46 to your computer and use it in GitHub Desktop.
Custom PagerView to replace PageTabViewStyle, that currently has performance issues with complex views. Originally created by @mecid (https://gist.github.com/mecid/e0d4d6652ccc8b5737449a01ee8cbc6f), I've changed the animation to work just like the PageTabViewStyle when changed using `withAnimation`.
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
// | |
// PagerView.swift | |
// | |
// Originally Created by Majid Jabrayilov on 12/5/19. | |
// Modified by Will Alexander on 10/3/2021 | |
// Copyright © 2019 Majid Jabrayilov. All rights reserved. | |
// Copyright © 2020 appstrm. All rights reserved. | |
// | |
import SwiftUI | |
class PagerViewModel: ObservableObject { | |
@Published var lastDrag: CGFloat = 0.0 | |
@Binding var currentIndex: Int | |
init(currentIndex: Binding<Int>) { | |
self._currentIndex = currentIndex | |
} | |
} | |
struct PagerView<Content: View>: View { | |
let pageCount: Int | |
@Binding var currentIndex: Int | |
let content: Content | |
@ObservedObject var viewModel: PagerViewModel | |
@GestureState private var translation: CGFloat = 0 | |
init(pageCount: Int, currentIndex: Binding<Int>, @ViewBuilder content: () -> Content) { | |
self.pageCount = pageCount | |
self._currentIndex = currentIndex | |
self.content = content() | |
viewModel = PagerViewModel(currentIndex: currentIndex) | |
} | |
var body: some View { | |
GeometryReader { geometry in | |
HStack(spacing: 0) { | |
content.frame(width: geometry.size.width) | |
} | |
.frame(width: geometry.size.width, alignment: .leading) | |
.offset(x: (-CGFloat(viewModel.currentIndex) * geometry.size.width) + viewModel.lastDrag) | |
.gesture( | |
DragGesture().updating($translation) { value, state, _ in | |
state = value.translation.width | |
viewModel.lastDrag = value.translation.width | |
}.onEnded { value in | |
let offset = value.predictedEndTranslation.width / geometry.size.width | |
let newIndex = (CGFloat(self.currentIndex) - offset).rounded() | |
let newIndexBound = min(max(Int(newIndex), 0), self.pageCount - 1) | |
let indexToChangeTo = min(max(newIndexBound, currentIndex - 1), currentIndex + 1) | |
if indexToChangeTo != viewModel.currentIndex { | |
withAnimation(.easeOut) { viewModel.currentIndex = indexToChangeTo } | |
} else { | |
withAnimation(.easeOut) { viewModel.lastDrag = 0 } | |
} | |
} | |
) | |
} | |
} | |
} | |
#if DEBUG | |
struct PagerView_Previews: PreviewProvider { | |
static var previews: some View { | |
PagerViewWrapper() | |
} | |
struct PagerViewWrapper: View { | |
@State var index = 0 | |
var body: some View { | |
PagerView(pageCount: 4, currentIndex: $index) { | |
Color.blue | |
Color.red | |
Color.green | |
Color.yellow | |
} | |
} | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, thanks for the code ! I've tried to use it inside a scrollView but I'm experiencing two issues:
1-First, when a Text with a long string inside is one of the pager components, it is automatically truncated if it has no height specified
2-If there is a LazyGrid in the content: the height of the pager is well detected inside the scrollView (which was my main issue with TabView) but the scrollview itself doesn't wan't to scroll: when scrolling down, you see the bottom of the pager view, but it springs back as soon as you end the drag gesture. As if the scrollView wasn't aware of its inner size...
I'm really not that good at GeometryReader stuff so I can't really contribute here, but I was wandering if these issues can be fixed or if I need to switch to another solution (grids are mandatory for my project)...
I can send you the project if you're interested.
Jo