Skip to content

Instantly share code, notes, and snippets.

@xtabbas
Created May 10, 2020 18:13
Show Gist options
  • Select an option

  • Save xtabbas/97b44b854e1315384b7d1d5ccce20623 to your computer and use it in GitHub Desktop.

Select an option

Save xtabbas/97b44b854e1315384b7d1d5ccce20623 to your computer and use it in GitHub Desktop.

Revisions

  1. xtabbas created this gist May 10, 2020.
    177 changes: 177 additions & 0 deletions SnapCarousel.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,177 @@
    //
    // SnapCarousel.swift
    // prototype5
    //
    // Created by xtabbas on 5/7/20.
    // Copyright © 2020 xtadevs. All rights reserved.
    //

    import SwiftUI

    struct SnapCarousel: View {
    @EnvironmentObject var UIState: UIStateModel

    var body: some View {
    let spacing: CGFloat = 16
    let widthOfHiddenCards: CGFloat = 32 /// UIScreen.main.bounds.width - 10
    let cardHeight: CGFloat = 279

    let items = [
    Card(id: 0, name: "Hey"),
    Card(id: 1, name: "Ho"),
    Card(id: 2, name: "Lets"),
    Card(id: 3, name: "Go")
    ]

    return Canvas {
    /// TODO: find a way to avoid passing same arguments to Carousel and Item
    Carousel(
    numberOfItems: CGFloat(items.count),
    spacing: spacing,
    widthOfHiddenCards: widthOfHiddenCards
    ) {
    ForEach(items, id: \.self.id) { item in
    Item(
    _id: Int(item.id),
    spacing: spacing,
    widthOfHiddenCards: widthOfHiddenCards,
    cardHeight: cardHeight
    ) {
    Text("\(item.name)")
    }
    .foregroundColor(Color.white)
    .background(Color("surface"))
    .cornerRadius(8)
    .shadow(color: Color("shadow1"), radius: 4, x: 0, y: 4)
    .transition(AnyTransition.slide)
    .animation(.spring())
    }
    }
    }
    }
    }

    struct Card: Decodable, Hashable, Identifiable {
    var id: Int
    var name: String = ""
    }

    public class UIStateModel: ObservableObject {
    @Published var activeCard: Int = 0
    @Published var screenDrag: Float = 0.0
    }

    struct Carousel<Items : View> : View {
    let items: Items
    let numberOfItems: CGFloat //= 8
    let spacing: CGFloat //= 16
    let widthOfHiddenCards: CGFloat //= 32
    let totalSpacing: CGFloat
    let cardWidth: CGFloat

    @GestureState var isDetectingLongPress = false

    @EnvironmentObject var UIState: UIStateModel

    @inlinable public init(
    numberOfItems: CGFloat,
    spacing: CGFloat,
    widthOfHiddenCards: CGFloat,
    @ViewBuilder _ items: () -> Items) {

    self.items = items()
    self.numberOfItems = numberOfItems
    self.spacing = spacing
    self.widthOfHiddenCards = widthOfHiddenCards
    self.totalSpacing = (numberOfItems - 1) * spacing
    self.cardWidth = UIScreen.main.bounds.width - (widthOfHiddenCards*2) - (spacing*2) //279

    }

    var body: some View {
    let totalCanvasWidth: CGFloat = (cardWidth * numberOfItems) + totalSpacing
    let xOffsetToShift = (totalCanvasWidth - UIScreen.main.bounds.width) / 2
    let leftPadding = widthOfHiddenCards + spacing
    let totalMovement = cardWidth + spacing

    let activeOffset = xOffsetToShift + (leftPadding) - (totalMovement * CGFloat(UIState.activeCard))
    let nextOffset = xOffsetToShift + (leftPadding) - (totalMovement * CGFloat(UIState.activeCard) + 1)

    var calcOffset = Float(activeOffset)

    if (calcOffset != Float(nextOffset)) {
    calcOffset = Float(activeOffset) + UIState.screenDrag
    }

    return HStack(alignment: .center, spacing: spacing) {
    items
    }
    .offset(x: CGFloat(calcOffset), y: 0)
    .gesture(DragGesture().updating($isDetectingLongPress) { currentState, gestureState, transaction in
    self.UIState.screenDrag = Float(currentState.translation.width)

    }.onEnded { value in
    self.UIState.screenDrag = 0

    if (value.translation.width < -50) {
    self.UIState.activeCard = self.UIState.activeCard + 1
    let impactMed = UIImpactFeedbackGenerator(style: .medium)
    impactMed.impactOccurred()
    }

    if (value.translation.width > 50) {
    self.UIState.activeCard = self.UIState.activeCard - 1
    let impactMed = UIImpactFeedbackGenerator(style: .medium)
    impactMed.impactOccurred()
    }
    })
    }
    }

    struct Canvas<Content : View> : View {
    let content: Content
    @EnvironmentObject var UIState: UIStateModel

    @inlinable init(@ViewBuilder _ content: () -> Content) {
    self.content = content()
    }

    var body: some View {
    content
    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
    .background(Color.white.edgesIgnoringSafeArea(.all))
    }
    }

    struct Item<Content: View>: View {
    @EnvironmentObject var UIState: UIStateModel
    let cardWidth: CGFloat
    let cardHeight: CGFloat

    var _id: Int
    var content: Content

    @inlinable public init(
    _id: Int,
    spacing: CGFloat,
    widthOfHiddenCards: CGFloat,
    cardHeight: CGFloat,
    @ViewBuilder _ content: () -> Content
    ) {
    self.content = content()
    self.cardWidth = UIScreen.main.bounds.width - (widthOfHiddenCards*2) - (spacing*2) //279
    self.cardHeight = cardHeight
    self._id = _id
    }

    var body: some View {
    content
    .frame(width: cardWidth, height: _id == UIState.activeCard ? cardHeight : cardHeight - 60, alignment: .center)
    }
    }

    struct SnapCarousel_Previews: PreviewProvider {
    static var previews: some View {
    SnapCarousel()
    }
    }