Skip to content

Instantly share code, notes, and snippets.

@alexdremov
Last active August 11, 2021 11:18
Show Gist options
  • Save alexdremov/b1a16f5adc47c6a59e3625f0b980ff7e to your computer and use it in GitHub Desktop.
Save alexdremov/b1a16f5adc47c6a59e3625f0b980ff7e to your computer and use it in GitHub Desktop.
Swift remastered minimal snap carousel View
//
// SnapCarousel.swift
// Spontanea
//
// Created by  Alex Dremov on 10.08.2021.
//
import Foundation
import SwiftUI
struct Card: Decodable, Hashable, Identifiable {
var id: Int
var name: String = ""
}
struct SnapCarousel: View {
@State var state = CarouselState()
var body: some View {
let items = [
Card(id: 0, name: "Hey"),
Card(id: 1, name: "Ho"),
Card(id: 2, name: "Lets"),
Card(id: 3, name: "Go")
]
let width = UIConfig.screenWidth
state.cardsNum = items.count
return Carousel(cardWidth: width,
state: $state){
ForEach(items, id:\.id) { item in
Text(item.name)
.padding()
.background(Color.black)
.foregroundColor(Color.white)
.cornerRadius(8)
.shadow(color: Color.black, radius: 4, x: 0, y: 4)
.frame(width: width, height: 100, alignment: .center)
.transition(AnyTransition.slide)
.animation(.spring())
}
}
}
}
public class CarouselState: ObservableObject {
@Published var cardsNum: Int = 0
@Published var activeCard: Int = 0
}
struct Carousel<Content : View>: View {
@State var offset: CGFloat = 0
@Binding var state: CarouselState
@State var screenDrag: Float = 0.0
var onSnap: ()->() = {}
let cardWidth: CGFloat
let items: Content
@inline(__always) func updateOffset() {
self.offset = self.cardWidth * CGFloat(self.state.activeCard) + CGFloat(self.screenDrag)
}
@inline(__always) func updateOffset(_ loc: Int) {
self.offset = self.cardWidth * CGFloat(loc) + CGFloat(self.screenDrag)
}
@inlinable public init(
cardWidth: CGFloat,
state: Binding<CarouselState>,
onSnap: @escaping ()->() = {},
@ViewBuilder items: () -> Content) {
self.items = items()
self.cardWidth = cardWidth
self._state = state
self.onSnap = onSnap
}
var body: some View {
return Group{
HStack(alignment: .center, spacing: 0) {
items
.frame(minWidth: 0,
maxWidth: .infinity,
alignment: .leading)
}
.frame(width: cardWidth * CGFloat(state.cardsNum), alignment: .leading)
}
.offset(x: -offset + CGFloat(screenDrag), y: 0)
.frame(width: cardWidth, alignment: .leading)
.contentShape(Rectangle())
.simultaneousGesture(DragGesture(minimumDistance: 0)
.onChanged { currentState in
self.screenDrag = Float(currentState.translation.width)
}.onEnded { value in
self.screenDrag = 0
if self.state.cardsNum != 0 {
if (value.translation.width < -50) {
self.state.activeCard = (self.state.activeCard + 1) % self.state.cardsNum
onSnap()
}
if (value.translation.width > 50) {
self.state.activeCard = (self.state.cardsNum + self.state.activeCard - 1) % self.state.cardsNum
onSnap()
}
}
})
.onReceive(state.$activeCard, perform: { activeCard in
updateOffset(activeCard)
})
.onReceive(state.$cardsNum, perform: { activeCard in
updateOffset(activeCard)
})
}
}
struct SnapCarousel_Previews: PreviewProvider {
static var previews: some View {
SnapCarousel()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment