React Native: Swipe Cards
Swipe cards are all the rage in mobile UI. In this Platypost we make a swipe card component in React Native using PanResponder and the Animated API.
Tinder really changed the game with it’s card swiping interface. It’s simple, visually appealing, and just plain fun.
So what’s behind these addictive cards? Let’s find out!
Create the App and Set Initial Structure
In the interest of space, and keeping things cleaner, all styles will be kept out of this write up but live in the GitHub repository. That out of the way, let’s begin.
Open a terminal window, navigate to your folder of choice, and invoke the React Native CLI to create a new application named “RNCardStack”. Once the application is set up, navigate into the root folder.
# Create a new React Native App
react-native init RNCardStack
As a final bit here, create a new directory called “components” and duplicate “index.ios.js” as a new file called “App.js” in that directory. Then import that file in both the “index.ios.js” and “index.android.js” files.
This will set up the application to be cross platform from the start, and organizes the components in a convenient folder.
Now is a good time to check that everything is working as desired. Spin up the applicaiton in either an iOS or Android simulator.
# Open an iOS simulator
# Open an Android simulator
You should get a screen just like this.
Great! Everything works. Moving on.
Initial Card Setup
We’re going to start by creating a card structure and build up from there.
Create a new file named “Card.js” in the components folder, import the necessary modules and establish the “Card” class.
A simple Tinder-like card is composed of a picture, and two lines of text. That in mind, add an “Image” component and then two “Text” components along with corresponding styles to the “Card” render function.
Then import the new “Card” component into the “App.js” file and add it to the main “View”. Refresh the simulator and a basic card structure should render.
Animated and PanResponder APIs
Two APIs included with React Native will handle the movement actions for the cards: Animated and PanResponder. Combined these will track user touch gestures and translate them into data used to translate the cards on-screen.
Start by adding “Animated” and “PanResponder” to the list of imports from “react-native” at the top of the “Cards.js” file.
Set up the PanResponder’s structure next. It will be wrapped inside of the “componentWillMount” function for the “Card” class so that’s it’s set to run every time before the card is rendered.
Also wrap the card with an Animated.view and link it with the newly created PanResponder’s handler events.
Here’s what’s happening in this bit.
First off, we’re creating a PanResponder instance, that part is pretty obvious.
Next, “onMoveShouldSetResponderCapture” and “onMoveShouldSetPanResponderCapture” are set to “true” letting the framework know that on touch and drag events the responder should become active and these gestures should be tracked.
From there, “onPanResponderGrant” gets called when the gestures are OK’d by the previous two parameters.
That brings us to the “onPanResponderMove” method which gets invoked whenever the object in question is moved, or in this case dragged.
Lastly, there’s “onPanResponderRelease” which, like it sounds, comes into play when the object is let go.
To have all of these affect the “Card”, it must be wrapped in an “Animated.View” which in turn must be linked to the “PanResponder”. The responder now knows which view it should be taking cues from.
Before anything can be done with the “PanResponder”, there needs to be a place to store the values it generates. To handle that, set up a constructor for the “Card” component and inside the “State” create a new variable “pan”. It’s of the type “Animated.ValueXY” which allows it to store vectors and be interpolated, something that will come in handy later on.
With that established, it’s time to add methods allowing the “PanResponder” to change the value of “pan”.
The first change to “pan” sets the initial values to the (X, Y) coordinates (0,0) or the center of the screen given the current setup. The second sets the delta, or change, values for x and y to their respective pan values as the view is dragged.
Now to be thorough here, we should add methods to remove the “PanResponder” listeners on component dismount.
The “PanResponder” is set up for the “Animated.View” surrounding the “Card” and movement values are being recorded in the state. However, notice upon refreshing the app that the “Card” does not respond to drag gestures yet.
To change that, create a function called “getMainCardStyle()” that will take in and process the “pan” values to transform the “Animated.View” wrapping the card.
Upon refresh, the card can now be dragged around the screen. However, you’ll notice something strange on the second drag. Each subsequent time the card is moved, it resets to the center of the screen with a quick jump. Not the desired behavior.
To set the card up for a smooth return to the center of the screen, we return to the “Animated” API and the “onPanResponderRelease” helper.
“Animated.spring” takes in an initial value for the “pan”, and then a desired outcome value, which in this case will be 0 for both x and y. It then calculates out the values needed in between to make a smooth transition to that value with a slight “spring” or bounce effect as it settles.
Passing that into the “onPanResponderRelease” helper will take care of the card return animation.
Now the card will smoothly spring back to zero when released. However, there are still a couple more transformations to be set up. Namely, changing the card rotation and opacity.
In the “getMainCardStyle()” function, use the built in “interpolate” function to generate values for these based on the “pan” event. Then pass them into the returned style object, placing the rotation calculation in the transformation clause, and opacity into its own category.
Refresh the app to find that the “Card” will now rotate, and fade out when dragged then bounce back to the center when released. Beyond being fun to fling around the screen though, functionality is somewhat lacking at this point.
Now that the “Card” component is ready, it’s time to set up a Stack.
Start by creating a new file “CardStack.js” in the “components” directory. In it, import the FlatList component from “react-native” along with the “Styles” and “Card” components from their respective files. Finally, import the new “CardStack” component into “App.js” and have it replace “Card” in the render function.
Refreshing the app will reveal an empty screen. Not to worry. Before rendering cards with the FlatList, it needs some data first.
Start with a state constructor to hold the user objects.
To do that, create a function “handleAdd()” to fetch user objects via calls to the api then push them to the state.
With “handleAdd()” pulling and storing user objects, it’s time to pass them to the “FlatList”. While doing so, pass all of the user data through to the “Card” as props.
Upon refreshing the app there is now a “Card” for each user stored in the state and these cards are added to the FlatList as necessary. However at this point there is only a single user, and the “Card” being rendered is still generic.
Modify the “Card” with the user object values being passed through the props. “Age” and “Job Description” aren’t included in the user object so use “Last Name” and “Email” instead.
The “Card” now shows user data, moves from side to side, rotates, changes opacity, and springs back to center when released. However, it’s still just a single card.
Add more cards into the stack by using an iterator in the “componentWillMount” call. Set up three cards to start things out.
Refreshing the app, there are now three cards. However, they’re displaying as different rows instead of stacking one atop the other. Not ideal.
Add in an absolute positioning with “top” and “left” values of negative half the card’s width and length to the “getMainCardStyle()” function of the “Card” component. This will center the cards on screen and place them one atop the other.
The FlatList renders cards according to their order in the data array. When collapsed, it renders the last user in the array as the top card which is why we prepend new users to the beginning of the array instead of appending them to the end.
The cards are now stacked as they should be and the first can be dragged around the screen. At this point though, there’s no way to move through the cards at all.
To do that add a way to remove cards. In the “CardStack” component add a “handleRemove()” function that will edit the list of users and removing the user at the index passed in. Also set it to trigger the “handleRemove()” function so that a new card is added to the array as one is removed.
Now hook the “handleRemove()” function up to the swiping.
The first step is passing it on to the “Card” component. Do this by creating a new onSwipe prop and then passing the function through there.
Now that the function is passed through to the “Card” component, define a way to trigger it.
Set this up with a series of conditional statements in the “onPanResponderRelease” helper. They will pull in the current x value of pan and key off the removal function if it’s greater or lesser than a set threshold.
Refresh the app and find that the stack of cards is now be fully swipeable.
Swipe a card left or right past the threshold and it will be popped from the user array, removing it from the stack. Simultaneously, a new user will be added to the array and along with it a new card.
Now there’s an infinite stack of cards to swipe through.
That’s as far as this post is going to cover.
There’s a good deal more that can be done here though. Set up a function for swiping cards vertically, add animations to smooth out the removal process, etc. Play around, try new things, and let us know what you come up with.
Thanks for reading! Join us next time for more development tutorials, tips, and tricks.
Follow us on Twitter to get updated when a new Platypost is released.