Skip to content

Instantly share code, notes, and snippets.

@doganaysahins
Created October 26, 2023 15:57
Show Gist options
  • Select an option

  • Save doganaysahins/40afc011f3b3a2ed6689d2520acc522f to your computer and use it in GitHub Desktop.

Select an option

Save doganaysahins/40afc011f3b3a2ed6689d2520acc522f to your computer and use it in GitHub Desktop.

Revisions

  1. doganaysahins created this gist Oct 26, 2023.
    81 changes: 81 additions & 0 deletions Carousel.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,81 @@
    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
    }

    }