Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements Card Delegate, Up Down, State #12

Closed
clsource opened this issue Mar 25, 2015 · 7 comments
Closed

Improvements Card Delegate, Up Down, State #12

clsource opened this issue Mar 25, 2015 · 7 comments

Comments

@clsource
Copy link

Hello I made several Improvements.
But I changed a lot of code so I prefer to send this as an issue.

I made a delegate, card states and up down detection.
👍

CardView.h

#import <UIKit/UIKit.h>


@protocol CardDelegate;

typedef NS_ENUM(NSUInteger, kCardState) {
    kCardStateIdle = 0,
    kCardStateMoving = 1,
    kCardStateGone = 2
};

// The Position of the Card
typedef NS_ENUM(NSInteger, kCardPosition) {
    kCardPositionTop = 0,
    kCardPositionBack = 1
};

/*!
 *    Implements the Views inside the Cards
 */
@interface CardView : UIView

#pragma mark - Properties

/*!
 *    Defines the current state of the card
 */
@property (nonatomic) kCardState state;

/*!
 *    The Weight of the Card indicates its position in the stack
 */
@property (nonatomic) kCardPosition position;

/*!
 * The delegate that will listen to the notifications
 * on created on pan gesture recognizers
 */
@property (nonatomic, weak) id <CardDelegate> delegate;

/*!
 *    Where in the array is located the  view
 */
@property (nonatomic) NSInteger index;

#pragma mark - Methods

#pragma mark Init

/*!
 * Designated Initializer
 *
 * This method inits the view
 * and set the  to fetch
 * also renders the view with 's data
 *
 * @param CGRect frame for view's bounds
 * @param ViewDelegate delegate to send events
 * @param  *  to render the view info
 */
- (void) setFrame:(CGRect)frame delegate:(id<CardDelegate>) delegate;

#pragma mark Instance

/*!
 * Swipes the view to the left
 * programatically
 */
- (void) swipeLeft;

/*!
 * Swipes the view to the right
 * programatically
 */
- (void) swipeRight;

/*!
 * Swipes the view up
 * programatically
 */
- (void) swipeUp;

/*!
 * Swipes the view down
 * programatically
 */
- (void) swipeDown;

/*!
 *    Cancels a Swipe
 */
- (void) cancelSwipe;

@end


@protocol CardDelegate <NSObject>

#pragma mark - Delegate Methods

/*!
 * Method called when the view will begin pan gesture
 * @param Card * Card
 */
- (void) willBeginSwipeInCard : (CardView *) card;

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

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

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

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

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

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

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

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

/*!
 *    Ask the delegate if the card should move
 *
 *    @param Card the card
 *
 *    @return YES if the card should move
 */
- (BOOL) shouldMoveCard: (CardView *) card;

@end

CardView.m

#import "CardView.h"

// 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.

#define ACTION_MARGIN 120

// This constant is the distance from the center. But vertically

#define Y_ACTION_MARGIN 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

#define SCALE_QUICKNESS 4

// This constant represent how much the view shrinks.
// A Higher value means less shrinking

#define SCALE_MAX .93

// This constant represent the rotation angle

#define ROTATION_ANGLE M_PI / 8

// This constant represent the maximum rotation angle
// allowed in radiands.
// A higher value enables more rotation for the view

#define ROTATION_MAX 1

// This constant defines how fast
// the rotation should be.
// A higher values means a faster rotation.

#define ROTATION_QUICKNESS 320

@interface CardView()

// Internal Variables
@property (nonatomic) CGFloat xFromCenter;

@property (nonatomic) CGFloat yFromCenter;

@property (nonatomic) CGPoint originalPoint;

@end

@implementation CardView

@synthesize state = _state;

- (kCardState) state {
    if (!_state) {
        _state = kCardStateIdle;
    }

    return _state;
}

- (void) setState:(kCardState) state {

    if (_state != state) {
        _state = state;
        [self.delegate didChangeStateInCard:self];
    }
}

- (kCardPosition) position {
    if (!_position) {
        _position = kCardPositionTop;
    }

    return _position;
}

#pragma mark - Init

/*!
 * Designated Initializer
 *
 * This method inits the view
 * and set the feed to fetch
 * also renders the view with feed's data
 *
 * @param CGRect frame for view's bounds
 * @param CardDelegate delegate to send events
 */

- (void) setFrame:(CGRect)frame delegate:(id<CardDelegate>) delegate {

    self.frame = frame;

    self.delegate = delegate;

    [self setupView];
    [self registerSwipeGestures];
}

#pragma mark Init Helper Methods

/*!
 * Round corners of the view
 * and draw a shadow
 * Do another view related
 * actions required on init
 */
- (void) setupView {

    // 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);

}

/*!
 * Register Pan Gesture
 * and delegates
 */
- (void) registerSwipeGestures {

    UIPanGestureRecognizer * panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];

    [self addGestureRecognizer:panRecognizer];

}


#pragma mark Pan Gesture Recognizer Handlers

/*!
 * This is the main method for motion detection
 * its called several times a second when the
 * fingers are moved across the screen.
 */
- (void) handlePanGesture: (UIPanGestureRecognizer *) panRecognizer {

    // This extracts the coordinate data from the swipe movement.
    // how much did fingers move.

    // We need to know the X position.
    // A positive value means movement to the right.
    // A negative value means movement to the left.

    self.xFromCenter = [panRecognizer translationInView:self].x;

    // We need to know the Y position.
    // A positive value means up movement.
    // A negative value means down movement.

    self.yFromCenter = [panRecognizer translationInView:self].y;

    // Now we check on wich state our swipe is
    // if is its starting, in the middle or ended
    // the swiping.

    switch (panRecognizer.state) {

            // Swiping just started
        case UIGestureRecognizerStateBegan: {

            // We will save the original point
            // when we started
            self.originalPoint = self.center;

            if (self.delegate) {

                if ([self.delegate shouldMoveCard:self]) {

                    // And tell the delegate
                    // that the movement just started
                    [self.delegate willBeginSwipeInCard:self];

                }

            }


            break;
        }

            // Swiping is in course
        case UIGestureRecognizerStateChanged: {

            // Animate the view

            if (self.delegate) {

                // If delegate is present
                // ask if it should move the card
                if ([self.delegate shouldMoveCard:self]) {

                    [self animateView];
                }

            } else {
                [self animateView];
            }

            break;
        }

        // Swiping ended
        case UIGestureRecognizerStateEnded: {

            if (self.delegate) {

                if ([self.delegate shouldMoveCard:self]) {

                    [self detectSwipeDirection];
                }

            } else {
                [self detectSwipeDirection];
            }

            break;
        }

        default:
            break;
    }
}


#pragma mark Helper Methods

/*!
 * Rotates the view
 * and changes its scale and position
 */
- (void) animateView {

    // Do some black magic math
    // for rotating and scale

    // Gets the rotation quickness
    // see constants.

    CGFloat rotationQuickness = MIN(self.xFromCenter / ROTATION_QUICKNESS, ROTATION_MAX);

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

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

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


    // rotate by the angle
    CGAffineTransform rotateTransform = CGAffineTransformMakeRotation(rotationAngle);

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

    // apply transformations
    self.transform = scaleTransform;

    // Change state
    [self changeStateToMoving];
}

/*!
 * With all the values fetched
 * from the pan gesture
 * gets the direction of the swipe
 * when the swipe is done
 */
- (void) detectSwipeDirection {

    if (self.xFromCenter > ACTION_MARGIN) {

        [self performRightAnimation];

    } else if (self.xFromCenter < - ACTION_MARGIN) {

        [self performLeftAnimation];

    } else if(self.yFromCenter < - Y_ACTION_MARGIN) {

        [self performUpAnimation];

    } else if(self.yFromCenter >  Y_ACTION_MARGIN) {

        [self performDownAnimation];

    } else {

        [self performCenterAnimation];

    }

    // And tell the delegate
    // that the swipe just finished
    [self.delegate didEndSwipeInCard:self];
}

- (void) changeStateToIdle {

    // Idle state indicates that the card
    // is showing in the view, but not moving.
    self.state = kJMPTFeedCardStateIdle;
}

- (void) changeStateToGone {

    // Gone state indicates that the card
    // was removed from the view
    self.state = kJMPTFeedCardStateGone;
}

- (void) changeStateToMoving {

    self.state = kJMPTFeedCardStateMoving;

    // Cancel Swipe if Moving but not should
    if (self.delegate && ![self.delegate shouldMoveCard:self]) {
        [self performCenterAnimation];
    }
}

#pragma mark Animation Methods

/*!
 * The view will go to the right
 */
- (void) performRightAnimation {

    CGPoint finishPoint = CGPointMake(500, 2 * self.yFromCenter + self.originalPoint.y);

    [UIView animateWithDuration:0.3  delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{

        self.center = finishPoint;

    } completion:^(BOOL finished) {

        [self removeFromSuperview];

        [self changeStateToGone];

        [self.delegate swipedRightInCard:self];
    }];

}

/*!
 * The view will got to the left
 */
- (void) performLeftAnimation {

    CGPoint finishPoint = CGPointMake(-500, 2 * self.yFromCenter + self.originalPoint.y);

    [UIView animateWithDuration:0.3  delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{

        self.center = finishPoint;

    } completion:^(BOOL finished) {

        [self removeFromSuperview];

        [self changeStateToGone];

        [self.delegate swipedLeftInCard:self];
    }];

}

/*!
 * The view will go up
 * do not remove from view
 * just perfom some goofy moves
 */
- (void) performUpAnimation {

    [UIView animateWithDuration:0.7
                          delay:0
         usingSpringWithDamping:0.56
          initialSpringVelocity:0.0
                        options:UIViewAnimationOptionBeginFromCurrentState
                     animations:^{

                         self.center = self.originalPoint;

                         self.transform = CGAffineTransformMakeRotation(0);

                     } completion:^(BOOL finished) {
                         [self changeStateToIdle];

                         [self.delegate swipedUpInCard:self];
                     }];
}

/*!
 * The view will go down
 * do not remove from view
 * just perfom some goofy moves
 */
- (void) performDownAnimation {

    [UIView animateWithDuration:0.7
                          delay:0
         usingSpringWithDamping:0.56
          initialSpringVelocity:0.0
                        options:UIViewAnimationOptionBeginFromCurrentState
                     animations:^{

                         self.center = self.originalPoint;

                         self.transform = CGAffineTransformMakeRotation(0);

                     } completion:^(BOOL finished) {
                         [self changeStateToIdle];
                         [self.delegate swipedDownInCard:self];
                     }];

}

/*!
 * The view will go to the center
 * (cancel swipe) and reset the values
 */
- (void) performCenterAnimation {

    [UIView animateWithDuration:0.7
                          delay:0
         usingSpringWithDamping:0.56
          initialSpringVelocity:0.0
                        options:UIViewAnimationOptionBeginFromCurrentState
                     animations:^{

                         self.center = self.originalPoint;

                         self.transform = CGAffineTransformMakeRotation(0);

                     } completion:^(BOOL finished) {

                         [self changeStateToIdle];
                         [self.delegate didCancelSwipeInCard:self];
                     }];
}

#pragma mark - Programatically Swipe Methods

- (void) swipeLeft {

    // The same animation but with a delay
    CGPoint finishPoint = CGPointMake(-500, 2 * self.yFromCenter + self.originalPoint.y);

    [self changeStateToMoving];

    [UIView animateWithDuration:0.3  delay:0.3 options:UIViewAnimationOptionBeginFromCurrentState animations:^{

        self.center = finishPoint;

    } completion:^(BOOL finished) {

        if (finished) {
            [self removeFromSuperview];
            [self changeStateToGone];
            [self.delegate swipedLeftInCard:self];
        }

    }];
}

- (void) swipeRight {

    // The same animation, but with a delay
    CGPoint finishPoint = CGPointMake(500, 2 * self.yFromCenter + self.originalPoint.y);

    [self changeStateToMoving];

    [UIView animateWithDuration:0.3  delay:0.3 options:UIViewAnimationOptionBeginFromCurrentState animations:^{

        self.center = finishPoint;

    } completion:^(BOOL finished) {

        if (finished) {
            [self removeFromSuperview];
            [self changeStateToGone];
            [self.delegate swipedRightInCard:self];
        }
    }];
}

- (void) swipeUp {
    //TODO: Implement this
}

- (void) swipeDown {
    //TODO: Implement this
}


- (void) cancelSwipe {
    [self performCenterAnimation];
}

#pragma mark - IBActions

- (IBAction) cardButton: (id)sender {
    [self.delegate wasTouchedDownInCard:self];
}

@end
@cwRichardKim
Copy link
Owner

I've made this a note at the bottom of the readme for anyone wanting to expand in these ways. Thanks a bunch

@jaymathew
Copy link

Am I missing something?
How would you incorporate this code into TinderSimpleSwipeCards?

Thanks for any help.

@clsource
Copy link
Author

I followed the base code in the repo.
but made several hacks and stuff thats mainly what I have copied here.

These are for reference so you can achieve something similar 👍

@jaymathew
Copy link

Thank you for the reply.
Is your code somewhere to download?
I would love to see it.

Thanks

@assisjeferson
Copy link

@clsource I translate this code for Swift 2.x, I tested in iPhone 6s running iOS 9.2

@clsource
Copy link
Author

clsource commented Feb 4, 2016

@assisjeferson 👍

@jrmgx
Copy link

jrmgx commented Feb 19, 2016

@assisjeferson Do you plan to put your code somewhere?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants