NewsBlur-viq/clients/ios/Other Sources/MCSwipeTableViewCell/MCSwipeTableViewCell.m

571 lines
21 KiB
Mathematica
Raw Normal View History

//
// MCSwipeTableViewCell.m
// MCSwipeTableViewCell
//
// Created by Ali Karagoz on 24/02/13.
// Copyright (c) 2013 Mad Castle. All rights reserved.
//
#import "MCSwipeTableViewCell.h"
2013-09-30 10:26:19 -07:00
static CGFloat const kMCStop1 = 0.20; // Percentage limit to trigger the first action
static CGFloat const kMCStop2 = 0.75; // Percentage limit to trigger the second action
static CGFloat const kMCBounceAmplitude = 20.0; // Maximum bounce amplitude when using the MCSwipeTableViewCellModeSwitch mode
static NSTimeInterval const kMCBounceDuration1 = 0.2; // Duration of the first part of the bounce animation
static NSTimeInterval const kMCBounceDuration2 = 0.1; // Duration of the second part of the bounce animation
static NSTimeInterval const kMCDurationLowLimit = 0.25; // Lowest duration when swipping the cell because we try to simulate velocity
static NSTimeInterval const kMCDurationHightLimit = 0.1; // Highest duration when swipping the cell because we try to simulate velocity
@interface MCSwipeTableViewCell () <UIGestureRecognizerDelegate>
@property (nonatomic, assign) MCSwipeTableViewCellDirection direction;
@property (nonatomic, assign) CGFloat currentPercentage;
@property (nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer;
@property (nonatomic, strong) UIImageView *slidingImageView;
@property (nonatomic, strong) NSString *currentImageName;
@property (nonatomic, strong) UIView *colorIndicatorView;
@end
@implementation MCSwipeTableViewCell
@synthesize shouldDrag;
#pragma mark - Initialization
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self initializer];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self initializer];
}
return self;
}
- (id)init {
self = [super init];
if (self) {
[self initializer];
}
return self;
}
#pragma mark - Custom Initializer
- (id)initWithStyle:(UITableViewCellStyle)style
reuseIdentifier:(NSString *)reuseIdentifier
firstStateIconName:(NSString *)firstIconName
firstColor:(UIColor *)firstColor
secondStateIconName:(NSString *)secondIconName
secondColor:(UIColor *)secondColor
thirdIconName:(NSString *)thirdIconName
thirdColor:(UIColor *)thirdColor
fourthIconName:(NSString *)fourthIconName
fourthColor:(UIColor *)fourthColor {
self = [self initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
[self setFirstStateIconName:firstIconName
firstColor:firstColor
secondStateIconName:secondIconName
secondColor:secondColor
thirdIconName:thirdIconName
thirdColor:thirdColor
fourthIconName:fourthIconName
fourthColor:fourthColor];
}
return self;
}
- (void)initializer {
_mode = MCSwipeTableViewCellModeNone;
_colorIndicatorView = [[UIView alloc] initWithFrame:self.bounds];
[_colorIndicatorView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
[_colorIndicatorView setBackgroundColor:(self.defaultColor ? self.defaultColor : [UIColor clearColor])];
[self insertSubview:_colorIndicatorView atIndex:0];
_slidingImageView = [[UIImageView alloc] init];
[_slidingImageView setContentMode:UIViewContentModeCenter];
[_colorIndicatorView addSubview:_slidingImageView];
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGestureRecognizer:)];
[self addGestureRecognizer:_panGestureRecognizer];
[_panGestureRecognizer setDelegate:self];
_isDragging = NO;
// By default the cells are draggable
shouldDrag = YES;
// By default the icons are animating
_shouldAnimatesIcons = YES;
// Set state modes
_modeForState1 = MCSwipeTableViewCellModeNone;
_modeForState2 = MCSwipeTableViewCellModeNone;
_modeForState3 = MCSwipeTableViewCellModeNone;
_modeForState4 = MCSwipeTableViewCellModeNone;
}
#pragma mark - Setter
- (void)setFirstStateIconName:(NSString *)firstIconName
firstColor:(UIColor *)firstColor
secondStateIconName:(NSString *)secondIconName
secondColor:(UIColor *)secondColor
thirdIconName:(NSString *)thirdIconName
thirdColor:(UIColor *)thirdColor
fourthIconName:(NSString *)fourthIconName
fourthColor:(UIColor *)fourthColor {
[self setFirstIconName:firstIconName];
[self setSecondIconName:secondIconName];
[self setThirdIconName:thirdIconName];
[self setFourthIconName:fourthIconName];
[self setFirstColor:firstColor];
[self setSecondColor:secondColor];
[self setThirdColor:thirdColor];
[self setFourthColor:fourthColor];
}
#pragma mark - Prepare reuse
- (void)prepareForReuse {
[super prepareForReuse];
// Clearing before presenting back the cell to the user
[_colorIndicatorView setBackgroundColor:[UIColor clearColor]];
// clearing the dragging flag
_isDragging = NO;
// Before reuse we need to reset it's state
// _shouldDrag = YES;
_shouldAnimatesIcons = NO;
_mode = MCSwipeTableViewCellModeNone;
_modeForState1 = MCSwipeTableViewCellModeNone;
_modeForState2 = MCSwipeTableViewCellModeNone;
_modeForState3 = MCSwipeTableViewCellModeNone;
_modeForState4 = MCSwipeTableViewCellModeNone;
}
#pragma mark - Handle Gestures
- (void)handlePanGestureRecognizer:(UIPanGestureRecognizer *)gesture {
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if (shouldDrag) {
shouldDrag = [prefs boolForKey:@"enable_feed_cell_swipe"];
}
// The user do not want you to be dragged!
if (!shouldDrag) return;
UIGestureRecognizerState state = [gesture state];
CGPoint translation = [gesture translationInView:self];
CGPoint velocity = [gesture velocityInView:self];
CGFloat percentage = [self percentageWithOffset:CGRectGetMinX(self.contentView.frame) relativeToWidth:CGRectGetWidth(self.bounds)];
NSTimeInterval animationDuration = [self animationDurationWithVelocity:velocity];
_direction = [self directionWithPercentage:percentage];
if (state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged) {
_isDragging = YES;
CGPoint center = {self.contentView.center.x + translation.x, self.contentView.center.y};
[self.contentView setCenter:center];
[self animateWithOffset:CGRectGetMinX(self.contentView.frame)];
[gesture setTranslation:CGPointZero inView:self];
// Notifying the delegate that we are dragging with an offset percentage
if ([_delegate respondsToSelector:@selector(swipeTableViewCell:didSwipWithPercentage:)]) {
[_delegate swipeTableViewCell:self didSwipWithPercentage:percentage];
}
}
else if (state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled) {
_isDragging = NO;
_currentImageName = [self imageNameWithPercentage:percentage];
_currentPercentage = percentage;
// Current state
MCSwipeTableViewCellState cellState = [self stateWithPercentage:percentage];
// Current mode
MCSwipeTableViewCellMode cellMode;
if (cellState == MCSwipeTableViewCellState1 && self.modeForState1 != MCSwipeTableViewCellModeNone) {
cellMode = self.modeForState1;
} else if (cellState == MCSwipeTableViewCellState2 && self.modeForState2 != MCSwipeTableViewCellModeNone) {
cellMode = self.modeForState2;
} else if (cellState == MCSwipeTableViewCellState3 && self.modeForState3 != MCSwipeTableViewCellModeNone) {
cellMode = self.modeForState3;
} else if (cellState == MCSwipeTableViewCellState4 && self.modeForState4 != MCSwipeTableViewCellModeNone) {
cellMode = self.modeForState4;
} else {
cellMode = self.mode;
}
if (cellMode == MCSwipeTableViewCellModeExit && _direction != MCSwipeTableViewCellDirectionCenter && [self validateState:cellState])
[self moveWithDuration:animationDuration andDirection:_direction];
else
[self bounceToOrigin];
}
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([gestureRecognizer class] == [UIPanGestureRecognizer class]) {
UIPanGestureRecognizer *g = (UIPanGestureRecognizer *)gestureRecognizer;
CGPoint point = [g velocityInView:self];
if (fabs(point.x) > fabs(point.y) ) {
// We notify the delegate that we just started dragging
if ([_delegate respondsToSelector:@selector(swipeTableViewCellDidStartSwiping:)]) {
[_delegate swipeTableViewCellDidStartSwiping:self];
}
return YES;
}
}
return NO;
}
#pragma mark - Utils
- (CGFloat)offsetWithPercentage:(CGFloat)percentage relativeToWidth:(CGFloat)width {
CGFloat offset = percentage * width;
if (offset < -width) offset = -width;
else if (offset > width) offset = width;
return offset;
}
- (CGFloat)percentageWithOffset:(CGFloat)offset relativeToWidth:(CGFloat)width {
CGFloat percentage = offset / width;
if (percentage < -1.0) percentage = -1.0;
else if (percentage > 1.0) percentage = 1.0;
return percentage;
}
- (NSTimeInterval)animationDurationWithVelocity:(CGPoint)velocity {
CGFloat width = CGRectGetWidth(self.bounds);
NSTimeInterval animationDurationDiff = kMCDurationHightLimit - kMCDurationLowLimit;
CGFloat horizontalVelocity = velocity.x;
if (horizontalVelocity < -width) horizontalVelocity = -width;
else if (horizontalVelocity > width) horizontalVelocity = width;
return (kMCDurationHightLimit + kMCDurationLowLimit) - fabs(((horizontalVelocity / width) * animationDurationDiff));
}
- (MCSwipeTableViewCellDirection)directionWithPercentage:(CGFloat)percentage {
if (percentage < 0)
return MCSwipeTableViewCellDirectionLeft;
else if (percentage > 0)
return MCSwipeTableViewCellDirectionRight;
else
return MCSwipeTableViewCellDirectionCenter;
}
- (NSString *)imageNameWithPercentage:(CGFloat)percentage {
NSString *imageName;
// Image
if (percentage >= 0 && percentage < kMCStop2)
imageName = _firstIconName;
else if (percentage >= kMCStop2)
imageName = _secondIconName;
else if (percentage < 0 && percentage > -kMCStop2)
imageName = _thirdIconName;
else if (percentage <= -kMCStop2)
imageName = _fourthIconName;
return imageName;
}
- (CGFloat)imageAlphaWithPercentage:(CGFloat)percentage {
CGFloat alpha;
if (percentage >= 0 && percentage < kMCStop1)
alpha = percentage / kMCStop1;
else if (percentage < 0 && percentage > -kMCStop1)
alpha = fabs(percentage / kMCStop1);
else alpha = 1.0;
return alpha;
}
- (UIColor *)colorWithPercentage:(CGFloat)percentage {
UIColor *color;
// Background Color
if (percentage >= kMCStop1 && percentage < kMCStop2)
color = _firstColor;
else if (percentage >= kMCStop2)
color = _secondColor;
else if (percentage < -kMCStop1 && percentage > -kMCStop2)
color = _thirdColor;
else if (percentage <= -kMCStop2)
color = _fourthColor;
else
color = self.defaultColor ? self.defaultColor : [UIColor clearColor];
return color;
}
- (MCSwipeTableViewCellState)stateWithPercentage:(CGFloat)percentage {
MCSwipeTableViewCellState state;
state = MCSwipeTableViewCellStateNone;
if (percentage >= kMCStop1 && [self validateState:MCSwipeTableViewCellState1])
state = MCSwipeTableViewCellState1;
if (percentage >= kMCStop2 && [self validateState:MCSwipeTableViewCellState2])
state = MCSwipeTableViewCellState2;
if (percentage <= -kMCStop1 && [self validateState:MCSwipeTableViewCellState3])
state = MCSwipeTableViewCellState3;
if (percentage <= -kMCStop2 && [self validateState:MCSwipeTableViewCellState4])
state = MCSwipeTableViewCellState4;
return state;
}
- (BOOL)validateState:(MCSwipeTableViewCellState)state {
BOOL isValid = YES;
switch (state) {
case MCSwipeTableViewCellStateNone: {
isValid = NO;
}
break;
case MCSwipeTableViewCellState1: {
if (!_firstColor && !_firstIconName)
isValid = NO;
}
break;
case MCSwipeTableViewCellState2: {
if (!_secondColor && !_secondIconName)
isValid = NO;
}
break;
case MCSwipeTableViewCellState3: {
if (!_thirdColor && !_thirdIconName)
isValid = NO;
}
break;
case MCSwipeTableViewCellState4: {
if (!_fourthColor && !_fourthIconName)
isValid = NO;
}
break;
default:
break;
}
return isValid;
}
#pragma mark - Movement
- (void)animateWithOffset:(CGFloat)offset {
CGFloat percentage = [self percentageWithOffset:offset relativeToWidth:CGRectGetWidth(self.bounds)];
// Image Name
NSString *imageName = [self imageNameWithPercentage:percentage];
// Image Position
if (imageName != nil) {
[_slidingImageView setImage:[UIImage imageNamed:imageName]];
[_slidingImageView setAlpha:[self imageAlphaWithPercentage:percentage]];
[self slideImageWithPercentage:percentage imageName:imageName isDragging:self.shouldAnimatesIcons];
}
// Color
UIColor *color = [self colorWithPercentage:percentage];
if (color != nil) {
[_colorIndicatorView setBackgroundColor:color];
}
}
- (void)slideImageWithPercentage:(CGFloat)percentage imageName:(NSString *)imageName isDragging:(BOOL)isDragging {
UIImage *slidingImage = [UIImage imageNamed:imageName];
CGSize slidingImageSize = slidingImage.size;
CGRect slidingImageRect;
CGPoint position = CGPointZero;
position.y = CGRectGetHeight(self.bounds) / 2.0;
// I'm sorry, I just assaulted this function. If you ever need multiple
// types of gestures on a cell, go back in time.
// NSLog(@"Percentage: %f (%f)", percentage, kMCStop1);
if (percentage > -kMCStop1 && percentage < kMCStop1) {
// if (percentage >= 0 && percentage < kMCStop1) {
// position.x = [self offsetWithPercentage:(kMCStop1 / 2) relativeToWidth:CGRectGetWidth(self.bounds)];
// }
if ((percentage > 0 && percentage < kMCStop1) ||
(percentage == 0 && _direction == MCSwipeTableViewCellDirectionRight)) {
position.x = [self offsetWithPercentage:percentage - (kMCStop1 / 2) relativeToWidth:CGRectGetWidth(self.bounds)];
}
// else if (percentage < 0 && percentage >= -kMCStop1) {
// position.x = CGRectGetWidth(self.bounds) - [self offsetWithPercentage:(kMCStop1 / 2) relativeToWidth:CGRectGetWidth(self.bounds)];
// }
//
else if ((percentage < 0 && percentage > -kMCStop1) ||
(percentage == 0 && _direction == MCSwipeTableViewCellDirectionLeft)) {
position.x = CGRectGetWidth(self.bounds) + [self offsetWithPercentage:percentage + (kMCStop1 / 2) relativeToWidth:CGRectGetWidth(self.bounds)];
}
}
else {
if (_direction == MCSwipeTableViewCellDirectionRight) {
position.x = [self offsetWithPercentage:(kMCStop1 / 2) relativeToWidth:CGRectGetWidth(self.bounds)];
}
else if (_direction == MCSwipeTableViewCellDirectionLeft) {
position.x = CGRectGetWidth(self.bounds) - [self offsetWithPercentage:(kMCStop1 / 2) relativeToWidth:CGRectGetWidth(self.bounds)];
}
else {
return;
}
}
slidingImageRect = CGRectMake(position.x - slidingImageSize.width / 2.0,
position.y - slidingImageSize.height / 2.0,
slidingImageSize.width,
slidingImageSize.height);
slidingImageRect = CGRectIntegral(slidingImageRect);
[_slidingImageView setFrame:slidingImageRect];
}
- (void)moveWithDuration:(NSTimeInterval)duration andDirection:(MCSwipeTableViewCellDirection)direction {
CGFloat origin;
if (direction == MCSwipeTableViewCellDirectionLeft)
origin = -CGRectGetWidth(self.bounds);
else
origin = CGRectGetWidth(self.bounds);
CGFloat percentage = [self percentageWithOffset:origin relativeToWidth:CGRectGetWidth(self.bounds)];
CGRect rect = self.contentView.frame;
rect.origin.x = origin;
// Color
UIColor *color = [self colorWithPercentage:_currentPercentage];
if (color != nil) {
[_colorIndicatorView setBackgroundColor:color];
}
// Image
if (_currentImageName != nil) {
[_slidingImageView setImage:[UIImage imageNamed:_currentImageName]];
}
[UIView animateWithDuration:duration
delay:0.0
options:(UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction)
animations:^{
[self.contentView setFrame:rect];
2020-08-27 15:08:46 -07:00
[self->_slidingImageView setAlpha:0];
[self slideImageWithPercentage:percentage imageName:self->_currentImageName isDragging:self.shouldAnimatesIcons];
}
completion:^(BOOL finished) {
[self notifyDelegate];
}];
}
- (void)bounceToOrigin {
CGFloat bounceDistance = kMCBounceAmplitude * _currentPercentage;
[UIView animateWithDuration:kMCBounceDuration1
delay:0
options:(UIViewAnimationOptionCurveEaseOut)
animations:^{
CGRect frame = self.contentView.frame;
frame.origin.x = -bounceDistance;
[self.contentView setFrame:frame];
2020-08-27 15:08:46 -07:00
[self->_slidingImageView setAlpha:0.0];
[self slideImageWithPercentage:0 imageName:self->_currentImageName isDragging:NO];
// Setting back the color to the default
2020-08-27 15:08:46 -07:00
self->_colorIndicatorView.backgroundColor = self.defaultColor;
}
completion:^(BOOL finished1) {
[UIView animateWithDuration:kMCBounceDuration2
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
CGRect frame = self.contentView.frame;
frame.origin.x = 0;
[self.contentView setFrame:frame];
// Clearing the indicator view
2020-08-27 15:08:46 -07:00
self->_colorIndicatorView.backgroundColor = [UIColor clearColor];
}
completion:^(BOOL finished2) {
[self notifyDelegate];
}];
}];
}
#pragma mark - Delegate Notification
- (void)notifyDelegate {
MCSwipeTableViewCellState state = [self stateWithPercentage:_currentPercentage];
MCSwipeTableViewCellMode mode = self.mode;
if (mode == MCSwipeTableViewCellModeNone) {
switch (state) {
case MCSwipeTableViewCellState1: {
mode = self.modeForState1;
} break;
case MCSwipeTableViewCellState2: {
mode = self.modeForState2;
} break;
case MCSwipeTableViewCellState3: {
mode = self.modeForState3;
} break;
case MCSwipeTableViewCellState4: {
mode = self.modeForState4;
} break;
default:
break;
}
}
if (state != MCSwipeTableViewCellStateNone) {
if ([_delegate respondsToSelector:@selector(swipeTableViewCell:didEndSwipingSwipingWithState:mode:)]) {
[_delegate swipeTableViewCell:self didEndSwipingSwipingWithState:state mode:mode];
}
}
}
@end