Last active
March 21, 2017 17:45
-
-
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))
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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