Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions MDCSwipeToChoose/Internal/MDCGeometry.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,25 @@ CGFloat MDCDegreesToRadians(const CGFloat degrees) {
CGRect MDCCGRectExtendedOutOfBounds(const CGRect rect,
const CGRect bounds,
const CGPoint translation) {

if (CGPointEqualToPoint(translation, CGPointZero)) {
@throw [NSException exceptionWithName:NSInvalidArgumentException
reason:@"Cannot extend rect with zero translation"
userInfo:nil];
}

CGPoint increment = CGPointZero;
if (translation.x != 0) {
increment.x = (translation.x < 0) ? -1 : 1;
}
if (translation.y != 0) {
increment.y = (translation.y < 0) ? -1 : 1;
}

CGRect destination = rect;
while (!CGRectIsNull(CGRectIntersection(bounds, destination))) {
destination = CGRectMake(CGRectGetMinX(destination) + translation.x,
CGRectGetMinY(destination) + translation.y,
destination = CGRectMake(CGRectGetMinX(destination) + increment.x,
CGRectGetMinY(destination) + increment.y,
CGRectGetWidth(destination),
CGRectGetHeight(destination));
}
Expand Down
5 changes: 1 addition & 4 deletions MDCSwipeToChoose/Internal/State/MDCViewState.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,8 @@ extern const MDCRotationDirection MDCRotationTowardsCenter;

@interface MDCViewState : NSObject

/*!
* The center of the view when the pan gesture began.
*/
@property (nonatomic, assign) CGPoint originalCenter;
@property (nonatomic, assign) CATransform3D originalTransform;
@property (nonatomic, assign) CGPoint translation;

/*!
* When the pan gesture originates at the top half of the view, the view rotates
Expand Down
18 changes: 3 additions & 15 deletions MDCSwipeToChoose/Public/Options/MDCSwipeOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,9 @@ - (instancetype)init {
+ (MDCSwipeToChooseOnChosenBlock)exitScreenOnChosenWithDuration:(NSTimeInterval)duration
options:(UIViewAnimationOptions)options {
return ^(MDCSwipeResult *state) {
CGRect destination = MDCCGRectExtendedOutOfBounds(state.view.frame,
state.view.superview.bounds,
state.translation);
[UIView animateWithDuration:duration
delay:0.0
options:options
animations:^{
state.view.center = CGPointMake(CGRectGetMidX(destination),
CGRectGetMidY(destination));
} completion:^(BOOL finished) {
if (finished) {
[state.view removeFromSuperview];
state.onCompletion();
}
}];
[state.view mdc_exitSuperviewFromCurrentTranslationWithDuration:duration
options:options
completion:state.onCompletion];
};
}

Expand Down
9 changes: 9 additions & 0 deletions MDCSwipeToChoose/Public/Views/UIView+MDCSwipeToChoose.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,13 @@
*/
- (void)mdc_swipe:(MDCSwipeDirection)direction;

/*!
* Continue animating the view in the direction it has been swiped, until
* it is completely out of bounds. Then remove it from the view hierarchy.
*
* This is a low-level method intended to be called as part of an onChosen block.
*/
- (void)mdc_exitSuperviewFromCurrentTranslationWithDuration:(NSTimeInterval)duration
options:(UIViewAnimationOptions)options
completion:(void(^)(void))completion;
@end
102 changes: 69 additions & 33 deletions MDCSwipeToChoose/Public/Views/UIView+MDCSwipeToChoose.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ @implementation UIView (MDCSwipeToChoose)
- (void)mdc_swipeToChooseSetup:(MDCSwipeOptions *)options {
self.mdc_options = options ? options : [MDCSwipeOptions new];
self.mdc_viewState = [MDCViewState new];
self.mdc_viewState.originalCenter = self.center;
self.mdc_viewState.originalTransform = self.layer.transform;
self.mdc_viewState.translation = CGPointZero;

if (options.swipeEnabled) {
[self mdc_setupPanGestureRecognizer];
Expand All @@ -60,9 +60,10 @@ - (void)mdc_swipe:(MDCSwipeDirection)direction {
void (^animations)(void) = ^{
CGPoint translation = [self mdc_translationExceedingThreshold:self.mdc_options.threshold
direction:direction];
self.center = MDCCGPointAdd(self.center, translation);
[self mdc_rotateForTranslation:translation
rotationDirection:MDCRotationAwayFromCenter];

self.mdc_viewState.translation = translation;
self.mdc_viewState.rotationDirection = MDCRotationAwayFromCenter;
[self mdc_applyLayerTransform];
[self mdc_executeOnPanBlockForTranslation:translation];
};

Expand All @@ -78,6 +79,39 @@ - (void)mdc_swipe:(MDCSwipeDirection)direction {
completion:completion];
}

- (void)mdc_exitSuperviewFromCurrentTranslationWithDuration:(NSTimeInterval)duration
options:(UIViewAnimationOptions)options
completion:(void(^)(void))completion
{
CGPoint translation = self.mdc_viewState.translation;
if (translation.x == 0) {
return;
}

//We aren't supposed to access self.frame while a transform is active
CGSize size = self.bounds.size;
CGRect currentRect = CGRectMake(self.center.x - (size.width/2), self.center.y - (size.height/2),
size.width, size.height);

CGRect destination = MDCCGRectExtendedOutOfBounds(currentRect,
self.superview.bounds,
translation);
CGPoint difference = MDCCGPointSubtract(destination.origin, currentRect.origin);
self.mdc_viewState.translation = MDCCGPointAdd(translation, difference);

[UIView animateWithDuration:duration
delay:0.0
options:options
animations:^{
[self mdc_applyLayerTransform];
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
completion();
}
}];
}

#pragma mark - Internal Methods

- (void)setMdc_options:(MDCSwipeOptions *)options {
Expand Down Expand Up @@ -123,9 +157,7 @@ - (void)mdc_finalizePositionForDirection:(MDCSwipeDirection)direction {
switch (direction) {
case MDCSwipeDirectionRight:
case MDCSwipeDirectionLeft: {
CGPoint translation = MDCCGPointSubtract(self.center,
self.mdc_viewState.originalCenter);
[self mdc_exitSuperviewFromTranslation:translation];
[self mdc_exitSuperviewFromTranslation:self.mdc_viewState.translation];
break;
}
case MDCSwipeDirectionNone:
Expand All @@ -141,7 +173,6 @@ - (void)mdc_returnToOriginalCenter {
options:self.mdc_options.swipeCancelledAnimationOptions
animations:^{
self.layer.transform = self.mdc_viewState.originalTransform;
self.center = self.mdc_viewState.originalCenter;
} completion:^(BOOL finished) {
id<MDCSwipeToChooseDelegate> delegate = self.mdc_options.delegate;
if ([delegate respondsToSelector:@selector(viewDidCancelSwipe:)]) {
Expand All @@ -153,24 +184,27 @@ - (void)mdc_returnToOriginalCenter {
- (void)mdc_exitSuperviewFromTranslation:(CGPoint)translation {
MDCSwipeDirection direction = [self mdc_directionOfExceededThreshold];
id<MDCSwipeToChooseDelegate> delegate = self.mdc_options.delegate;
void (^onChoose)(void) = ^ {
MDCSwipeResult *state = [MDCSwipeResult new];
state.view = self;
state.translation = translation;
state.direction = direction;
state.onCompletion = ^{
if ([delegate respondsToSelector:@selector(view:wasChosenWithDirection:)]) {
[delegate view:self wasChosenWithDirection:direction];
}
};
self.mdc_options.onChosen(state);
};
if ([delegate respondsToSelector:@selector(view:shouldBeChosenWithDirection:yes:no:)]) {
[delegate view:self shouldBeChosenWithDirection:direction yes:^{
MDCSwipeResult *state = [MDCSwipeResult new];
state.view = self;
state.translation = translation;
state.direction = direction;
state.onCompletion = ^{
if ([delegate respondsToSelector:@selector(view:wasChosenWithDirection:)]) {
[delegate view:self wasChosenWithDirection:direction];
}
};
self.mdc_options.onChosen(state);
} no:^{
[delegate view:self shouldBeChosenWithDirection:direction yes:onChoose no:^{
[self mdc_returnToOriginalCenter];
if (self.mdc_options.onCancel != nil){
self.mdc_options.onCancel(self);
}
}];
} else {
onChoose();
}
}

Expand All @@ -193,12 +227,16 @@ - (void)mdc_executeOnPanBlockForTranslation:(CGPoint)translation {
}
}

#pragma mark Rotation

- (void)mdc_rotateForTranslation:(CGPoint)translation
rotationDirection:(MDCRotationDirection)rotationDirection {
- (void)mdc_applyLayerTransform {
CATransform3D t = self.mdc_viewState.originalTransform;

CGPoint translation = self.mdc_viewState.translation;
t = CATransform3DTranslate(t, translation.x, translation.y, 0);

CGFloat rotation = MDCDegreesToRadians(translation.x/100 * self.mdc_options.rotationFactor);
self.layer.transform = CATransform3DMakeRotation(rotationDirection * rotation, 0.0, 0.0, 1.0);
t = CATransform3DRotate(t, self.mdc_viewState.rotationDirection * rotation, 0, 0, 1);

self.layer.transform = t;
}

#pragma mark Threshold
Expand All @@ -221,9 +259,9 @@ - (CGPoint)mdc_translationExceedingThreshold:(CGFloat)threshold
}

- (MDCSwipeDirection)mdc_directionOfExceededThreshold {
if (self.center.x > self.mdc_viewState.originalCenter.x + self.mdc_options.threshold) {
if (self.mdc_viewState.translation.x > self.mdc_options.threshold) {
return MDCSwipeDirectionRight;
} else if (self.center.x < self.mdc_viewState.originalCenter.x - self.mdc_options.threshold) {
} else if (self.mdc_viewState.translation.x < -self.mdc_options.threshold) {
return MDCSwipeDirectionLeft;
} else {
return MDCSwipeDirectionNone;
Expand All @@ -236,8 +274,8 @@ - (void)mdc_onSwipeToChoosePanGestureRecognizer:(UIPanGestureRecognizer *)panGes
UIView *view = panGestureRecognizer.view;

if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) {
self.mdc_viewState.originalCenter = view.center;
self.mdc_viewState.originalTransform = view.layer.transform;
self.mdc_viewState.translation = CGPointZero;

// If the pan gesture originated at the top half of the view, rotate the view
// away from the center. Otherwise, rotate towards the center.
Expand All @@ -254,11 +292,9 @@ - (void)mdc_onSwipeToChoosePanGestureRecognizer:(UIPanGestureRecognizer *)panGes
} else {
// Update the position and transform. Then, notify any listeners of
// the updates via the pan block.
CGPoint translation = [panGestureRecognizer translationInView:view];
view.center = MDCCGPointAdd(self.mdc_viewState.originalCenter, translation);
[self mdc_rotateForTranslation:translation
rotationDirection:self.mdc_viewState.rotationDirection];
[self mdc_executeOnPanBlockForTranslation:translation];
self.mdc_viewState.translation = [panGestureRecognizer translationInView:view];
[self mdc_applyLayerTransform];
[self mdc_executeOnPanBlockForTranslation:self.mdc_viewState.translation];
}
}

Expand Down