Slarkerino
Slarkerino

Reputation: 63

How to make a swipeable view with SwiftUI

I tried to make a SWIFTUI View that allows card Swipe like action by using gesture() method. But I can't figure out a way to make view swipe one by one. Currently when i swipe all the views are gone

import SwiftUI

struct EventView: View {
    @State private var offset: CGSize = .zero
    @ObservedObject var randomView: EventViewModel
    var body: some View {
        ZStack{
            ForEach(randomView.randomViews,id:\.id){ view in
            view
                .background(Color.randomColor)
                .cornerRadius(8)
                .shadow(radius: 10)
                .padding()
                .offset(x: self.offset.width, y: self.offset.height)
                .gesture(
                    DragGesture()
                        .onChanged { self.offset = $0.translation }
                        .onEnded {
                            if $0.translation.width < -100 {

                                self.offset = .init(width: -1000, height: 0)
                            } else if $0.translation.width > 100 {
                                self.offset = .init(width: 1000, height: 0)

                            } else {
                                self.offset = .zero
                            }
                    }
                )
                .animation(.spring())
            }
        }
    }
}

struct EventView_Previews: PreviewProvider {
    static var previews: some View {
        EventView(randomView: EventViewModel())
    }
}

struct PersonView: View {
    var id:Int = Int.random(in: 1...1000)
    var body: some View {
        VStack(alignment: .center) {
            Image("testBtn")
                .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)

            Text("Majid Jabrayilov")
                .font(.title)
                .accentColor(.white)

            Text("iOS Developer")
                .font(.body)
                .accentColor(.white)
        }.padding()
    }
}

With this piece of code, when i swipe the whole thing is gone

Upvotes: 2

Views: 8202

Answers (1)

glassomoss
glassomoss

Reputation: 795

Basically your code tells every view to follow offset, while actually you want only the top one move. So firstly I'd add a variable that'd hold current index of the card and a method to calculate it's offset:

@State private var currentCard = 0

func offset(for i: Int) -> CGSize {
   return i == currentCard ? offset : .zero
}

Secondly, I found out that if we just leave it like that, on the next touch view would get offset of the last one (-1000, 0) and only then jump to the correct location, so it looks just like previous card decided to return instead of the new one. In order to fix this I added a flag marking that card has just gone, so when we touch it again it gets right location initially. Normally, we'd do that in gesture's .began state, but we don't have an analog for that in swiftUI, so the only place to do it is in .onChanged:

@State private var didJustSwipe = false

DragGesture()
    .onChanged {
        if self.didJustSwipe {
            self.didJustSwipe = false
            self.currentCard += 1
            self.offset = .zero
        } else {
            self.offset = $0.translation
        }
    }

In .onEnded in the case of success we assign didJustSwipe = true

So now it works perfectly. Also I suggest you diving your code into smaller parts. It will not only improve readability, but also save some compile time. You didn't provide an implementation of EventViewModel and those randomViews so I used rectangles instead. Here's your code:

struct EventView: View {

    @State private var offset: CGSize = .zero
    @State private var currentCard = 0
    @State private var didJustSwipe = false

    var randomView: some View {
        return Rectangle()
            .foregroundColor(.green)
            .cornerRadius(20)
            .frame(width: 300, height: 400)
            .shadow(radius: 10)
            .padding()
            .opacity(0.3)
    }

    func offset(for i: Int) -> CGSize {
        return i == currentCard ? offset : .zero
    }

    var body: some View {
        ZStack{
            ForEach(currentCard..<5, id: \.self) { i in
                self.randomView
                    .offset(self.offset(for: i))
                    .gesture(self.gesture)
                    .animation(.spring())
            }
        }
    }

    var gesture: some Gesture {
        DragGesture()
            .onChanged {
                if self.didJustSwipe {
                    self.didJustSwipe = false
                    self.currentCard += 1
                    self.offset = .zero
                } else {
                    self.offset = $0.translation
                }
        }
            .onEnded {
                let w = $0.translation.width
                if abs(w) > 100 {
                    self.didJustSwipe = true
                    let x = w > 0 ? 1000 : -1000
                    self.offset = .init(width: x, height: 0)
                } else {
                    self.offset = .zero
                }
        }
    }
}

Upvotes: 5

Related Questions