3

I have a SwiftUI app that behaves similarly to Tinder (built using this guide - or see source code). You just swipe a stack of cards left and right. I have a "thumbs up" and "thumbs down" button as alternatives to swiping, but when they are pressed, I'd like it to swipe the card away for the user as if they had swiped it themselves.

Are there any ways to do this in SwiftUI without UIKit?

ktom
  • 125
  • 8

1 Answers1

3

If your thumbs up and down buttons were part of the CardView then it would be quite simple to achieve: just repeat whatever code executes on gesture inside a button action. Something along these lines:

                   // CardView.swift

                   Button (action: {
                        // hardcoded for simplicity
                        self.translation = .init(width: 100, height: 0)
                        self.swipeStatus = .like
                        // a hacky way to "wait" for animation to complete
                        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3, execute: {
                            self.onRemove(self.user)
                        })
                    }) {
                        Image(systemName: "hand.thumbsup")
                            .foregroundColor(.green)
                    }

But, if your buttons reside outside of the CardView, I think you'll have to re-architect your views a little bit. I would start by converting swipeStatus from @State into a @Binding, so its value can be passed down from the parent view. Then you will need to declare a state variable in your ContentView, which you can then tie together with swipeStatus binding. Something like:

                                    // ContentView.swift

                                    @State var currentSwipeStatus: LikeDislike = .none
                                    ....
                                    CardView(
                                        swipeStatus: self.users.last == user
                                        ? $currentSwipeStatus
                                        : .constant(.none),
                                         user: user,
                                         onRemove: { removedUser in
                                        self.users.removeAll { $0.id == removedUser.id}
                                        self.swipeStatus = .none
                                    })

Within your card view you'll need to react to the bound value changing. I just appended onChange to the main VStack:

.onChange(of: swipeStatus) { newState in
                if newState == .like {
                        self.translation = .init(width: 100, height: 0)
                        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3, execute: {
                            self.onRemove(self.user)
                        })
                }
            }

I feel like there's a more elegant way of achieving this. I'm not a big fan of the onRemove callback, asyncAfter hack, and the fact that they render all cards at once. But at least that's a start.

Dmitriy
  • 1,852
  • 4
  • 15
  • 33