Skip to content

Instantly share code, notes, and snippets.

@shayonj
Last active March 21, 2017 17:45
Show Gist options
  • Save shayonj/93851c5af6ddac169e9f to your computer and use it in GitHub Desktop.
Save shayonj/93851c5af6ddac169e9f to your computer and use it in GitHub Desktop.
Swift implementation of https://github.com/cwRichardKim/TinderSimpleSwipeCards . Note: Code still requires a lot of improvements, especially Object Oriented Design changes. (Started out with iOS a few weeks backs, so still learning :) (It wont work with the new release of Xcode / Swift beta 2.0 (Still a WIP))
//
// DraggableView.swift
// Swipe
//
// Created by Shayon Mukherjee on 11/5/14.
// Copyright (c) 2014 Swipe. All rights reserved.
//
import Foundation
import UIKit
let ACTION_MARGIN = 120 //%%% distance from center where the action applies. Higher = swipe further in order for the action to be called
let SCALE_STRENGTH = 4 //%%% how quickly the card shrinks. Higher = slower shrinking
let SCALE_MAX : CGFloat = 0.93 //%%% upper bar for how much the card shrinks. Higher = shrinks less
let ROTATION_MAX = 1 //%%% the maximum rotation allowed in radians. Higher = card can keep rotating longer
let ROTATION_STRENGTH = 320 //%%% strength of rotation. Higher = weaker rotation
let ROTATION_ANGLE = M_PI/8 //%%% Higher = stronger rotation angle
class DraggableView : UIView {
var xFromCenter : CGFloat
var yFromCenter : CGFloat
var panGestureRecognizer : UIPanGestureRecognizer
var originalPoint : CGPoint
var overlayView : OverlayView
var information : UILabel
required init(coder aDecoder: NSCoder) {
xFromCenter = 0.0
yFromCenter = 0.0
panGestureRecognizer = UIPanGestureRecognizer()
originalPoint = CGPoint()
overlayView = OverlayView(coder: aDecoder)
information = UILabel()
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
xFromCenter = 0.0
yFromCenter = 0.0
panGestureRecognizer = UIPanGestureRecognizer()
originalPoint = CGPoint()
overlayView = OverlayView(coder: NSCoder())
information = UILabel()
super.init(frame: frame)
information = UILabel(frame: CGRectMake(0, 50, self.frame.size.width, 100))
information.text = "no info given"
information.textAlignment = NSTextAlignment.Center
information.textColor = UIColor.blackColor()
self.backgroundColor = UIColor(red:72/255, green:145/255,blue:206/255,alpha:1)
panGestureRecognizer = UIPanGestureRecognizer(target: self, action: "beingDragged:")
self.addGestureRecognizer(panGestureRecognizer)
self.addSubview(information)
overlayView = OverlayView(frame: CGRectMake(self.frame.size.width/2-100, 0, 100, 100))
overlayView.alpha = 0
self.addSubview(overlayView)
self.superview
}
func setupView() {
self.layer.cornerRadius = 4
self.layer.shadowRadius = 3
self.layer.shadowOpacity = 0.2
self.layer.shadowOffset = CGSizeMake(1, 1)
}
//%%% called when you move your finger across the screen.
// called many times a second
func beingDragged(gestureRecognizer: UIPanGestureRecognizer){
//%%% this extracts the coordinate data from your swipe movement. (i.e. How much did you move?)
//%%% positive for right swipe, negative for left
xFromCenter = gestureRecognizer.translationInView(self).x
//%%% positive for up, negative for down
yFromCenter = gestureRecognizer.translationInView(self).y
//%%% checks what state the gesture is in. (are you just starting, letting go, or in the middle of a swipe?)
switch (gestureRecognizer.state) {
//%%% just started swiping
case UIGestureRecognizerState.Began:
self.originalPoint = self.center
break
//%%% in the middle of a swipe
case UIGestureRecognizerState.Changed:
//%%% dictates rotation (see ROTATION_MAX and ROTATION_STRENGTH for details)
var rotationStrength = min(CGFloat(xFromCenter) / CGFloat(ROTATION_STRENGTH), CGFloat(ROTATION_MAX)) as CGFloat
//%%% degree change in radians
var rotationAngel : CGFloat = (CGFloat(ROTATION_ANGLE) * rotationStrength)
//%%% amount the height changes when you move the card up to a certain point
var scale = max((CGFloat(1) - CGFloat(rotationStrength) / CGFloat(SCALE_STRENGTH)), SCALE_MAX) as CGFloat
//%%% move the object's center by center + gesture coordinate
self.center = CGPointMake(self.originalPoint.x + xFromCenter, self.originalPoint.y + yFromCenter)
//%%% rotate by certain amount
var transform : CGAffineTransform = CGAffineTransformMakeRotation(rotationAngel)
//%%% scale by certain amount
var scaleTransform : CGAffineTransform = CGAffineTransformScale(transform, scale, scale)
//%%% apply transformations
self.transform = scaleTransform
self.updateOverlay(xFromCenter)
break
//%%% let go of the card
case UIGestureRecognizerState.Ended:
self.afterSwipeAction()
break;
case UIGestureRecognizerState.Possible:break;
case UIGestureRecognizerState.Cancelled:break;
case UIGestureRecognizerState.Failed:break;
}
}
//%%% checks to see if you are moving right or left and applies the correct overlay image
func updateOverlay(distance: CGFloat){
if (distance > 0) {
overlayView.mode = GGOverlayViewMode.GGOverlayViewModeRight;
} else {
overlayView.mode = GGOverlayViewMode.GGOverlayViewModeLeft;
}
overlayView.alpha = min(CGFloat(distance)/100, 0.4);
}
//%%% called when the card is let go
func afterSwipeAction() {
if(xFromCenter > CGFloat(ACTION_MARGIN))
{
self.rightAction()
}
else if(xFromCenter < CGFloat(-ACTION_MARGIN))
{
self.leftAction()
}
else
{
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.center = self.originalPoint
self.transform = CGAffineTransformMakeRotation(0)
self.overlayView.alpha = 0
})
}
}
func rightAction() {
var finishPoint : CGPoint = CGPointMake(500, 2*yFromCenter + self.originalPoint.y)
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.center = finishPoint
}) { (complete) -> Void in
self.removeFromSuperview()
}
println("YES")
}
func leftAction() {
var finishPoint : CGPoint = CGPointMake(-500, 2*yFromCenter + self.originalPoint.y)
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.center = finishPoint
}) { (complete) -> Void in
self.removeFromSuperview()
}
println("NO")
}
func rightClickAction() {
var finishPoint : CGPoint = CGPointMake(600, self.center.y)
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.center = finishPoint
self.transform = CGAffineTransformMakeRotation(1)
}) { (complete) -> Void in
self.removeFromSuperview()
}
println("YES")
}
func leftClickAction() {
var finishPoint : CGPoint = CGPointMake(-600, self.center.y)
UIView.animateWithDuration(0.3, animations: { () -> Void in
self.center = finishPoint
self.transform = CGAffineTransformMakeRotation(-1)
}) { (complete) -> Void in
self.removeFromSuperview()
}
println("No")
}
}
//
// DraggableViewBackground.swift
// Swipe
//
// Created by Shayon Mukherjee on 11/5/14.
// Copyright (c) 2014 Swipe. All rights reserved.
//
import Foundation
import UIKit
class DraggableViewBackground : UIView {
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//this makes it so only two cards are loaded at a time to
//avoid performance and memory costs
let MAX_BUFFER_SIZE = 5 //%%% max number of cards loaded at any given time, must be greater than 1
let CARD_HEIGHT : CGFloat = 386 //%%% height of the draggable card
let CARD_WIDTH : CGFloat = 290 //%%% width of the draggable card
var cardsLoadedIndex : Int = 0 //%%% the index of the card you have loaded into the loadedCards array last
var loadedCards = [DraggableView]() //%%% the array of card loaded (change max_buffer_size to increase or decrease the number of cards this holds)
var menuButton : UIButton
var messageButton : UIButton
var checkButton : UIButton
var xButton : UIButton
var exampleCardLabels : AnyObject = [] //%%% all the labels I'm using as example data at the moment
var allCards = [DraggableView]() //%%% all the cards
override init(frame: CGRect) {
menuButton = UIButton()
messageButton = UIButton()
checkButton = UIButton()
xButton = UIButton()
super.init(frame: frame)
self.setupView()
exampleCardLabels = ["first", "second", "third", "fourth", "fifth"] //%%% placeholder for card-specific information
cardsLoadedIndex = 0;
self.loadCards()
}
// Frontend UI styling happening here
func setupView() {
// Styling background
self.backgroundColor = UIColor.whiteColor()
// Styling menuButton
menuButton = UIButton(frame: CGRectMake(17, 34, 22, 15))
menuButton.setImage(UIImage(named: "menuButton"), forState: UIControlState.Normal)
// Styling messageButton
messageButton = UIButton(frame: CGRectMake(284, 34, 18, 18))
messageButton.setImage(UIImage(named: "messageButton"), forState: UIControlState.Normal)
// Styling xButton
xButton = UIButton(frame: CGRectMake(60, 485, 59, 59))
xButton.setImage(UIImage(named: "xButton"), forState: UIControlState.Normal)
// Add target for checkButton (Swipe Left)
xButton.addTarget(self, action: "swipeLeft", forControlEvents: UIControlEvents.TouchUpInside)
// Styling checkButton
checkButton = UIButton(frame: CGRectMake(200, 485, 59, 59))
checkButton.setImage(UIImage(named: "checkButton"), forState: UIControlState.Normal)
// Add target for checkButton (Swipe Right)
checkButton.addTarget(self, action: "swipeRight", forControlEvents: UIControlEvents.TouchUpInside)
// Add subviews
self.addSubview(menuButton)
self.addSubview(messageButton)
self.addSubview(xButton)
self.addSubview(checkButton)
}
func loadCards() {
if(exampleCardLabels.count > 0){
//%%% if the buffer size is greater than the data size, there will be an array error, so this makes sure that doesn't happen
var numLoadedCardsCap = exampleCardLabels.count > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : exampleCardLabels.count
//%%% loops through the exampleCardsLabels array to create a card for each label. This should be customized by removing "exampleCardLabels" with your own array of data
for (var i = 0; i < exampleCardLabels.count; i++) {
var newCard : DraggableView = self.createDraggableViewWithDataAtIndex(i)
allCards.append(newCard)
if(i < numLoadedCardsCap) {
//%%% adds a small number of cards to be loaded
loadedCards.append(newCard)
}
}
}
//%%% displays the small number of loaded cards dictated by MAX_BUFFER_SIZE so that not all the cards
// are showing at once and clogging a ton of data
for (var i = 0; i < loadedCards.count; i++) {
if (i > 0) {
self.insertSubview(loadedCards[i] as UIView, belowSubview: loadedCards[i-1] as UIView)
} else {
self.addSubview(loadedCards[i] as UIView)
}
cardsLoadedIndex++; //%%% we loaded a card into loaded cards, so we have to increment
}
}
//%%% action called when the card goes to the left.
// This should be customized with your own action
func cardSwipedLeft(card: UIView) {
//do whatever you want with the card that was swiped
// DraggableView *c = (DraggableView *)card;
loadedCards.removeAtIndex(0) //%%% card was swiped, so it's no longer a "loaded card"
//%%% if we haven't reached the end of all cards, put another into the loaded cards
if(cardsLoadedIndex < allCards.count) {
loadedCards.append(allCards[cardsLoadedIndex])
//%%% loaded a card, so have to increment count
cardsLoadedIndex++
self.insertSubview(loadedCards[MAX_BUFFER_SIZE-1] as UIView, belowSubview: loadedCards[MAX_BUFFER_SIZE-2] as UIView)
}
}
//%%% action called when the card goes to the left.
// This should be customized with your own action
func cardSwipedRight(card: UIView) {
//do whatever you want with the card that was swiped
// DraggableView *c = (DraggableView *)card;
loadedCards.removeAtIndex(0) //%%% card was swiped, so it's no longer a "loaded card"
//%%% if we haven't reached the end of all cards, put another into the loaded cards
if(cardsLoadedIndex < allCards.count) {
loadedCards.append(allCards[cardsLoadedIndex])
//%%% loaded a card, so have to increment count
cardsLoadedIndex++
self.insertSubview(loadedCards[MAX_BUFFER_SIZE-1] as UIView, belowSubview: loadedCards[MAX_BUFFER_SIZE-2] as UIView)
}
}
//%%% when you hit the right button, this is called and substitutes the swipe
func swipeRight() {
var dragView : DraggableView = loadedCards.first!
dragView.overlayView.mode = GGOverlayViewMode.GGOverlayViewModeRight
UIView.animateWithDuration(0.2, animations: { () -> Void in
dragView.overlayView.alpha = 1
})
// dragView.rightClickAction
}
func swipeLeft() {
var dragView : DraggableView = loadedCards.first!
dragView.overlayView.mode = GGOverlayViewMode.GGOverlayViewModeLeft
UIView.animateWithDuration(0.2, animations: { () -> Void in
dragView.overlayView.alpha = 1
})
// dragView.leftClickAction
}
//%%% creates a card and returns it. This should be customized to fit your needs.
// use "index" to indicate where the information should be pulled. If this doesn't apply to you, feel free
// to get rid of it (eg: if you are building cards from data from the internet)
func createDraggableViewWithDataAtIndex(index: Int) -> DraggableView{
var draggableView : DraggableView = DraggableView(frame: CGRectMake((self.frame.size.width - CARD_WIDTH)/2, (self.frame.size.height - CARD_HEIGHT)/2, CARD_WIDTH, CARD_HEIGHT))
//%%% placeholder for card-specific information
draggableView.information.text = exampleCardLabels.objectAtIndex(index) as? String
return draggableView
}
}
//
// OverlayView.swift
// Swipe
//
// Created by Shayon Mukherjee on 11/5/14.
// Copyright (c) 2014 Swipe. All rights reserved.
//
import Foundation
import UIKit
enum GGOverlayViewMode : Int {
case GGOverlayViewModeLeft
case GGOverlayViewModeRight
}
class OverlayView : UIView {
var mode : GGOverlayViewMode
var imageView : UIImageView
override init(frame: CGRect) {
mode = GGOverlayViewMode.GGOverlayViewModeRight // tmp init
imageView = UIImageView()
super.init(frame: frame)
self.backgroundColor = UIColor.whiteColor()
imageView = UIImageView(image: UIImage(named: "noButton"))
self.addSubview(imageView)
}
required init(coder aDecoder: (NSCoder!)) {
mode = GGOverlayViewMode.GGOverlayViewModeRight // tmp init
imageView = UIImageView()
super.init()
}
func setMode(mode: GGOverlayViewMode){
// if (_ mode == mode) {
// return
// }
//
// _mode = mode;
if(mode == GGOverlayViewMode.GGOverlayViewModeLeft) {
imageView.image = UIImage(named: "noButton")
} else {
imageView.image = UIImage(named: "yesButton")
}
}
override func layoutSubviews() {
super.layoutSubviews()
imageView.frame = CGRectMake(50, 50, 100, 100)
imageView.backgroundColor = UIColor(red:72/255, green:145/255,blue:206/255,alpha:1)
}
}
//
// TimelineViewController.swift
// Swipe
//
// Created by Shayon Mukherjee on 11/4/14.
// Copyright (c) 2014 Swipe. All rights reserved.
//
import Foundation
import UIKit
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var draggableBackground = DraggableViewBackground(frame: self.view.frame)
self.view.addSubview(draggableBackground)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment