The Tinder Swipe
The central part of Tinder’s design is the swiping mechanism. The feature allows people to focus on one match at a time and make a decision before moving on to the next match. Good for spending more time on matches and good for reducing the speed of data consumption.
I wanted to learn about animations, so this weekend I hacked together a prototype of the feature.
Demo
All the code can be found here along with a video demo. The code is very dirty, but that’s topic for another week.
Process
Step 1. Look up how to do it.
The next step in my TinderClone project is to make the swipeable images. Richard Kim has a really cool blog entry about how to make this feature three different ways. Since I’m taking on this project to learn to code, I took the “build it from scratch” way and tried to follow Nimrod Gutman’s blog. I totally failed. Twice.
It turns out that I didn’t really understand how UIView and UIImageViews worked. So, I decided to play around with them.
Step 2. Read up on UIViews
I read thought through the Apple View Programming Guide. in particular, the view and window architecture. I didn’t really understand bounds very well, so I started an Xcode project called playingWithBounds, that ended up being my project.
Take-aways:
- Views are made on top of Core Animation layers (CALayer). Views have animation, but go to CALayer to one layer deeper for more control.
- Subviews are arranged in an array in the superview.
- UIContentMode is useful for sizing images. I played around with this later.
- I thought the bounds property might have something to do with Box Sizing in CSS, but it’s much simpler. The bounds change the frame of the box.
UIView properties
- frame- used for making objects. CGRectMake()
- bounds- changes the size of the view, but not the center.
- center- very important for moving objects around.
- transform- used to rotate, move and scale objects. It’s the finishing touch. The object is drawn and then transformed. When an object is rotated, it remembers what it’s heigh and width were when it wasn’t rotated.
- alpha- 1 is opaque and 0 is fully transparent. Great for
- backgroundColor- useful for seeing where the rectangle is.
Step 3. Playing around with the UIView properties
I made single view project where I drew boxes and changed the bounds, and so on to see what would happen.
Step 4. Playing around with CGAffineTranform
When I got to Coordinate System Transformations, I started playing around with rotating and moving boxes.
I made a slider to rotate an UIImageView.
//slider
CGRect slideFrame = CGRectMake(100, 400, 200, 20);
self.pictureRotationAngle = [[UISlider alloc] initWithFrame:slideFrame];
[self.view addSubview:self.pictureRotationAngle];
self.pictureRotationAngle.minimumValue = 0;
self.pictureRotationAngle.maximumValue = 2*M_PI;
self.pictureRotationAngle.value = M_PI/2.0;
[self.pictureRotationAngle addTarget:self action:@selector(rotateImage) forControlEvents:UIControlEventValueChanged];
self.picture.transform = CGAffineTransformMakeRotation(self.pictureRotationAngle.value);
Step 5. Finding the Event Handling methods
Where other solutions mentioned at the top used UIGestureRecognizers, I imagined that these event handling methods could move my picture around too. They used UITouch objects.
I was touching my UIImageView and nothing was happening. Googling around, I found that UIImageViews are not user interactive by default. So, I had to set it.
self.picture.userInteractionEnabled = YES;
I also set the
What is UITouch?
I didn’t know, so I built the first three methods and NSLogged the touch and event objects. They were very descriptive.
2014-10-27 18:58:26.919 TinderSwipeFeature[65292:2410541] <UITouch: 0x7fdf015533a0> phase: Moved tap count: 1 window: <UIWindow: 0x7fdf01409040; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x7fdf01403730>; layer = <UIWindowLayer: 0x7fdf01414c50>> view: <UIImageView: 0x7fdf01684780; frame = (37.8671 64.9702; 402.124 402.124); transform = [0.99420459268393058, -0.10750454821160646, 0.10750454821160646, 0.99420459268393058, 0, 0]; clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x7fdf01684ac0>> location in window: {256, 332} previous location in window: {255, 332} location in view: {192.38019379786252, 249.92073845470227} previous location in view: {191.38598920517859, 249.81323390649064}
UITouch has a lot of really useful info:
- Size of the screen: 375 width and 667 points height.
frame = (0 0; 375 667)
- The view touched and it’s location
view: <UIImageView: 0x7fdf01684780; frame = (37.8671 64.9702; 402.124 402.124)
- Current touch location in view and window
location in window: {256, 332}
location in view: {192.38019379786252, 249.92073845470227}
- Previous touch location in view and window
previous location in window: {255, 332}
previous location in view: {191.38598920517859, 249.81323390649064}
- Timestamp
2014-10-27 18:58:26.919
I now had the velocity of touches and I just needed to update the view being touched with a new center value to move it.
That was the key to solving the problem.
I added the other Tinder style animations associated with swiping. All of these were based on using the x location of the touch divided by the width of the phone to calculate a factor that would modify the rotation of the image and the opacity of the overlaps.
Step 6. Playing with contentMode
When I put the picture in the UIImageView, it would not fit in the frame, so I had to set clipsToBounds to YES and change the contentMode from UIViewContentModeScaleToFill to UIViewContentModeScaleAspectFill. UIViewContentModeScaleAspectFit also works but you get white space on the sides.
self.picture.contentMode = UIViewContentModeScaleAspectFill;
self.picture.clipsToBounds = YES;
The other options such as UIViewContentModeTop maintain the size of the image, and align the listed edge to the corresponding edge in the box. For example,
- UIViewContentModeTop aligns the image to the top of the UIImageView.
- UIViewContentModeTopLeft aligns the image to the top and left of the UIImageView.
Step 7. Animation timing
For the touchesEnded method, I wanted to animate the image moving back into location.
[UIView animateWithDuration:0.4 delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
... reset image
} completion:nil];
CGAffineTransformIdentity removes all transforms (rotations, scaling, translations).
Step 8. Animation for the picture flying off the screen or springing back
To animate the picture flying off to the side, I set a swipe threshold of 30% of the width.
if (differenceInTouchLocationX > aTouch.window.frame.size.width*0.2) {
[UIView animateWithDuration:0.5 animations:^{
viewTouched.center = CGPointMake(self.view.frame.size.width*2, currentLocation.y + 0.5 * speedY) ;
}];
NSLog(@"%@", aTouch);
}else if (differenceInTouchLocationX < -aTouch.window.frame.size.width*0.3)
{
[UIView animateWithDuration:0.5 animations:^{
viewTouched.center = CGPointMake(-self.view.frame.size.width*2, currentLocation.y + 0.5 * speedY) ;
}];
}
else {
... //springs back to original location
}
Step 9. Add the overlap images (Like and Nope)
self.like = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 100, 50)];
[self.like setFont:[UIFont systemFontOfSize:100]];
self.like.text = @"L I K E";
self.like.adjustsFontSizeToFitWidth = YES;
[self.like setBaselineAdjustment:UIBaselineAdjustmentAlignCenters];
[self.like setTextColor:[UIColor colorWithRed:47/255.0f green:156/255.0f blue:28/255.0f alpha:1]];
// self.like.backgroundColor = [UIColor blueColor];
self.like.transform = CGAffineTransformMakeRotation(-20* M_PI/180);
self.like.alpha = 0;
Boy, I learned a lot by just reading the documentation and Googling. That was fun!