cwRichardKim / RKSwipeCards

The basics of a swipeable card interface inspired by Tinder
MIT License
2.17k stars 61 forks source link

Support Swift 2.0 #20

Open toygar-zzz opened 9 years ago

assisjeferson commented 8 years ago

I translate the class CardView by @clsource

I tested using iPhone 6s running iOS 9.2

import UIKit
import Darwin
import Swift

// MARK: CardViewDelegate
@objc protocol CardViewDelegate {
    /*!
    * Method called when the view will begin pan gesture
    * @param Card * Card
    */
    optional func willBeginSwipeInCard(card: CardView)

    /*!
    * Method called when the view did end pan gesture
    * @param Card * Card
    */
    optional func didEndSwipeInCard(card: CardView)

    /*!
    * Method called when the view did not reach a detected position
    * @param Card * Card
    */
    optional func didCancelSwipeInCard(card: CardView)

    /*!
    * Method called when the view was swiped left
    * @param Card * Card
    */
    optional func swipedLeftInCard(card: CardView)

    /*!
    * Method called when the view was swiped right
    * @param Card * Card
    */
    optional func swipedRightInCard(card: CardView)

    /*!
    * Method called when the view was swiped up
    * @param Card * Card
    */
    optional func swipedUpInCard(card: CardView)

    /*!
    * Method called when the view was swiped down
    * @param Card * Card
    */
    optional func swipedDownInCard(card: CardView)

    /*!
    * Method called when the view button was pressed
    * @param Card * Card;
    */
    optional func wasTouchedDownInCard(card: CardView)

    /*!
    *    Method called when the state was changed
    *
    *    @param  Card * Card;
    */
    optional func didChangeStateInCard(card: CardView)

    /*!
    *    Ask the delegate if the card should move
    *
    *    @param Card the card
    *
    *    @return YES if the card should move
    */
    func shouldMoveCard(card: CardView) -> Bool
}

// MARK: CardView Enums
enum CardState : Int {
    case Idle = 0, Moving, Gone
}

enum CardPosition : Int {
    case Top = 0, Back
}

// Constants Declaration

// This constant represent the distance from the center
// where the action applies. A higher value means that
// the user needs to draw the view further in order for
// the action to be executed.
let X_ACTION_MARGIN : CGFloat = 120

// This constant is the distance from the center. But vertically
let Y_ACTION_MARGIN : CGFloat = 100

// This constant represent how quickly the view shrinks.
// The view will shrink when is beign moved out the visible
// area.
// A Higher value means slower shrinking
let SCALE_QUICKNESS = 4

// This constant represent how much the view shrinks.
// A Higher value means less shrinking
let SCALE_MAX : Float = 0.93

// This constant represent the rotation angle
let ROTATION_ANGLE = M_PI / 8

// This constant represent the maximum rotation angle
// allowed in radiands.
// A higher value enables more rotation for the view
let ROTATION_MAX : CGFloat = 1

//// This constant defines how fast
// the rotation should be.
// A higher values means a faster rotation.
let ROTATION_QUICKNESS : CGFloat = 320

// MARK: CardView
class CardView: UIView {

    // MARK: Internal Variables
    var state : CardState = .Idle
    var position : CardPosition = .Top

    var xFromCenter : CGFloat = 0
    var yFromCenter : CGFloat = 0
    var originalPoint : CGPoint = CGPoint(x: 0, y: 0)

    var delegate : CardViewDelegate

    // MARK: Init
    init(frame: CGRect, delegate: CardViewDelegate) {
        self.delegate = delegate

        super.init(frame: frame)
        self.setup()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: Setup Method
    private func setup() {
        // Draw Shadow
        // And round the view
        self.layer.cornerRadius = 10;
        self.layer.shadowRadius = 3;
        self.layer.shadowOpacity = 0.2;
        self.layer.shadowOffset = CGSizeMake(1,1);
        self.backgroundColor = UIColor.whiteColor()

        //Register Pan Gesture and Delegates
        let panRecognizer = UIPanGestureRecognizer(target: self, action: "handlePanGesture:")
        self.addGestureRecognizer(panRecognizer)
    }

    // MARK: Pan Gesture Recognizer Handlers
    @objc private func handlePanGesture(panRecognizer: UIPanGestureRecognizer) {

        self.xFromCenter = panRecognizer.translationInView(self).x;
        self.yFromCenter = panRecognizer.translationInView(self).y;

        let isPossibleMove = self.delegate.shouldMoveCard(self)

        switch(panRecognizer.state) {
        case .Began:
            self.originalPoint = self.center

            if isPossibleMove {
                self.delegate.willBeginSwipeInCard?(self)
            }
            break;
        case .Changed:

            if isPossibleMove {
                self.animateView()
            }
            break;
        case .Ended:
            if isPossibleMove {
                self.detectSwipeDirection()
            }
            break;
        default:

            break;
        }
    }

    // MARK: Helper Methods

    /*!
    * Rotates the view
    * and changes its scale and position
    */
    private func animateView() {
        // Do some black magic math
        // for rotating and scale

        // Gets the rotation quickness
        // see constants.
        let rotationQuickness = min(self.xFromCenter / ROTATION_QUICKNESS, ROTATION_MAX)

        // Change the rotation in radians
        let rotationAngle = CGFloat(ROTATION_ANGLE) * rotationQuickness

        // the height will change when the view reaches certain point
        let scale = CGFloat(max(1 - fabsf(Float(rotationQuickness)) / Float(SCALE_QUICKNESS), SCALE_MAX))

        // move the object center depending on the coordinate
        self.center = CGPoint(x: self.originalPoint.x + self.xFromCenter, y: self.originalPoint.y + self.yFromCenter)

        // rotate by the angle
        let rotateTransform = CGAffineTransformMakeRotation(rotationAngle)

        // scale depending on the rotation
        let scaleTransform = CGAffineTransformScale(rotateTransform, scale, scale)

        // apply transformations
        self.transform = scaleTransform
    }

    /*!
    * With all the values fetched
    * from the pan gesture
    * gets the direction of the swipe
    * when the swipe is done
    */
    private func detectSwipeDirection(){
        if self.xFromCenter > X_ACTION_MARGIN {
            self.performRightAnimation()
        }
        else if self.xFromCenter < -X_ACTION_MARGIN {
            self.performLeftAnimation()
        }
        else if self.yFromCenter < -Y_ACTION_MARGIN {
            self.performUpAnimation()
        }
        else if self.yFromCenter > Y_ACTION_MARGIN {
            self.performCenterAnimation()
        }
        else {
            self.performCenterAnimation()
        }

        // And tell the delegate
        // that the swipe just finished

        self.delegate.didEndSwipeInCard?(self)
    }

    private func changeStateToIdle() {
        // Idle state indicates that the card
        // is showing in the view, but not moving.

        self.state = .Idle
    }

    private func changeStateToGone() {
        // Gone state indicates that the card
        // was removed from the view

        self.state = .Gone
    }

    private func changeStateToMoving() {
        self.state = .Moving

        // Cancel Swipe if Moving but not should
        if self.delegate.shouldMoveCard(self) {
            self.performCenterAnimation()
        }
    }

    // MARK: Animation Methods

    /*!
    * The view will go to the right
    */
    private func performRightAnimation() {
        let finishPoint = CGPoint(x: 500, y: 2 * self.yFromCenter + self.originalPoint.y)

        UIView.animateWithDuration(0.3,
            delay: 0,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                self.center = finishPoint
            }) { (finished) -> Void in
                self.removeFromSuperview()
                self.changeStateToGone()
                self.delegate.swipedRightInCard?(self)
        }
    }

    /*!
    * The view will got to the left
    */
    private func performLeftAnimation() {
        let finishPoint = CGPoint(x: -500, y: 2 * self.yFromCenter + self.originalPoint.y)

        UIView.animateWithDuration(0.3,
            delay: 0,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                self.center = finishPoint
            }) { (finished) -> Void in
                self.removeFromSuperview()
                self.changeStateToGone()
                self.delegate.swipedLeftInCard?(self)
        }
    }

    /*!
    * The view will go up
    * do not remove from view
    * just perfom some goofy moves
    */
    private func performUpAnimation() {
        UIView.animateWithDuration(0.7,
            delay: 0,
            usingSpringWithDamping: 0.56,
            initialSpringVelocity: 0.0,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                self.center = self.originalPoint
                self.transform = CGAffineTransformMakeRotation(0)
            }) { (finished) -> Void in
                self.changeStateToIdle()
                self.delegate.swipedUpInCard?(self)
        }
    }

    /*!
    * The view will go down
    * do not remove from view
    * just perfom some goofy moves
    */
    private func performDownAnimation() {
        UIView.animateWithDuration(0.7,
            delay: 0,
            usingSpringWithDamping: 0.56,
            initialSpringVelocity: 0.0,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                self.center = self.originalPoint
                self.transform = CGAffineTransformMakeRotation(0)
            }) { (finished) -> Void in
                self.changeStateToIdle()
                self.delegate.swipedDownInCard?(self)
        }
    }

    /*!
    * The view will go to the center
    * (cancel swipe) and reset the values
    */
    private func performCenterAnimation() {
        UIView.animateWithDuration(0.7,
            delay: 0,
            usingSpringWithDamping: 0.56,
            initialSpringVelocity: 0.0,
            options: .BeginFromCurrentState,
            animations: { () -> Void in
                self.center = self.originalPoint
                self.transform = CGAffineTransformMakeRotation(0)
            }) { (finished) -> Void in
                self.changeStateToIdle()
                self.delegate.didCancelSwipeInCard?(self)
        }
    }

    // MARK: Programatically Swipe Methods
    func swipeLeft() {

        self.changeStateToMoving()

        self.performLeftAnimation()
    }

    func swipeRight() {
        self.changeStateToMoving()

        self.performRightAnimation()
    }

    func swipeUp() {
        //TODO: Implement this
    }

    func swipeDown() {
        //TODO: Implement this
    }

    func cancelSwipe() {
        self.performCenterAnimation()
    }
}
toygar-zzz commented 8 years ago

Thanks @assisjeferson

jpsanabria commented 8 years ago

Awesome @assisjeferson