Created
October 26, 2023 15:57
-
-
Save doganaysahins/40afc011f3b3a2ed6689d2520acc522f to your computer and use it in GitHub Desktop.
SwiftUI Ready-to-Use Carousel
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 SnapCarousel<Content: View, T: Identifiable>: View { | |
| var content: (T) -> Content | |
| var list: [T] | |
| var spacing: CGFloat | |
| var trialingSpace: CGFloat | |
| @Binding var index: Int | |
| init(spacing: CGFloat = 15, trialingSpace: CGFloat = 100, index: Binding<Int>, items: [T], @ViewBuilder content: @escaping (T)->Content) { | |
| self.list = items | |
| self.spacing = spacing | |
| self.trialingSpace = trialingSpace | |
| self._index = index | |
| self.content = content | |
| } | |
| @GestureState var offset: CGFloat = 0 | |
| @State var currentIndex: Int = 0 | |
| var body: some View { | |
| GeometryReader{proxy in | |
| let width = proxy.size.width - (trialingSpace - spacing) | |
| let adjustmentWidth = (trialingSpace / 2) - spacing | |
| HStack(spacing: spacing) { | |
| ForEach(list){item in | |
| content(item) | |
| .frame(width: proxy.size.width - trialingSpace) | |
| .offset(y: getOffset(item: item, width: width)) | |
| } | |
| } | |
| .padding(.horizontal, spacing) | |
| .offset(x: (CGFloat(currentIndex) * -width) + (currentIndex != 0 ? adjustmentWidth : 0) + offset) | |
| .gesture( | |
| DragGesture() | |
| .updating($offset, body: {value, out, _ in | |
| out = (value.translation.width / 1.5) | |
| }) | |
| .onEnded({value in | |
| let offsetX = value.translation.width | |
| let progress = -offsetX / width | |
| let roundIndex = progress.rounded() | |
| currentIndex = max(min(currentIndex + Int(roundIndex), list.count - 1), 0) | |
| currentIndex = index | |
| }) | |
| .onChanged({value in | |
| let offsetX = value.translation.width | |
| let progress = -offsetX / width | |
| let roundIndex = progress.rounded() | |
| index = max(min(currentIndex + Int(roundIndex), list.count - 1), 0) | |
| }) | |
| ) | |
| } | |
| .animation(.easeInOut, value: offset == 0) | |
| } | |
| func getOffset(item: T, width: CGFloat)->CGFloat { | |
| let progress = ((offset < 0 ? offset : -offset) / width) * 60 | |
| let topOffset = -progress < 60 ? progress : -(progress + 120) | |
| let previous = getIndex(item: item) - 1 == currentIndex ? (offset < 0 ? topOffset : -topOffset) : 0 | |
| let next = getIndex(item: item) + 1 == currentIndex ? (offset < 0 ? -topOffset : topOffset) : 0 | |
| let checkBetween = currentIndex >= 0 && currentIndex < list.count ? (getIndex(item: item) - 1 == currentIndex ? previous : next) : 0 | |
| return getIndex(item: item) == currentIndex ? -60 - topOffset : checkBetween | |
| } | |
| func getIndex(item: T)-> Int { | |
| let index = list.firstIndex {currentItem in | |
| return currentItem.id == item.id | |
| } ?? 0 | |
| return index | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment