Created
June 7, 2023 07:54
-
-
Save glukianets/e77abea51af5418d845cff3d64565d59 to your computer and use it in GitHub Desktop.
RDSwipeView
This file contains hidden or 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
// | |
// RDSwipeView.h | |
// RDSwipeView | |
// | |
// Created by Gleb Lukianets on 31/03/16. | |
// Copyright © 2016 Gleb Lukianets. All rights reserved. | |
// | |
#import <UIKit/UIKit.h> | |
typedef NS_OPTIONS(NSUInteger, RDSwipeViewEdges) { | |
RDSwipeViewEdgesNone = 0, | |
RDSwipeViewEdgesTop = (1 << 0), | |
RDSwipeViewEdgesLeft = (1 << 1), | |
RDSwipeViewEdgesBottom = (1 << 2), | |
RDSwipeViewEdgesRight = (1 << 3), | |
RDSwipeViewEdgesAll = RDSwipeViewEdgesTop | RDSwipeViewEdgesLeft | RDSwipeViewEdgesBottom | RDSwipeViewEdgesRight, | |
}; | |
typedef NS_ENUM(NSUInteger, RDSwipeViewEdge) { | |
RDSwipeViewEdgeTop = RDSwipeViewEdgesTop, | |
RDSwipeViewEdgeLeft = RDSwipeViewEdgesLeft, | |
RDSwipeViewEdgeBottom = RDSwipeViewEdgesBottom, | |
RDSwipeViewEdgeRight = RDSwipeViewEdgesRight, | |
}; | |
#pragma mark - <RDSwipeActionItem> | |
@protocol RDSwipeActionItem <NSObject> | |
- (UIView *)view; | |
- (void)trigger; | |
@end | |
#pragma mark - RDSwipeActionItem | |
@interface RDSwipeActionItem : NSObject<RDSwipeActionItem> | |
- (instancetype)initWithColor:(UIColor *)color | |
image:(UIImage *)image | |
title:(NSString *)title; | |
@property (nonatomic) RDSwipeViewEdge layoutEdge; | |
@property (nonatomic) UIImage *image; | |
@property (nonatomic) NSString *title; | |
@property (nonatomic) UIColor *color; | |
@property (nonatomic) UIColor *supplementaryColor; | |
@property (nonatomic, copy) void (^actionBlock)(RDSwipeActionItem *); | |
@end | |
#pragma mark - RDSwipeView | |
@interface RDSwipeView : UIView | |
@property (nonatomic) RDSwipeViewEdges fastActionEdges; | |
- (void)setActionItems:(NSArray<id<RDSwipeActionItem>> *)items forEdge:(RDSwipeViewEdge)edge; | |
- (NSArray<id<RDSwipeActionItem>> *)actionItemsForEdge:(RDSwipeViewEdge)edge; | |
@end |
This file contains hidden or 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
// | |
// RDSwipeView.m | |
// RDSwipeView | |
// | |
// Created by Gleb Lukianets on 31/03/16. | |
// Copyright © 2016 Gleb Lukianets. All rights reserved. | |
// | |
#define AUTO(NAME, VALUE) __typeof(VALUE) NAME = (VALUE); | |
#import "RDSwipeView.h" | |
static CGFloat const kItemSize = 64.f; | |
static CGFloat const kFastActionOverscrollThreshold = 64.f; | |
static CGFloat const kOverscrollModifier = 0.75f; | |
static CGFloat const kPanResistanceModifier = 0.25f; | |
static NSTimeInterval const kCollapseAnimationDuration = 0.22f; | |
static NSTimeInterval const kFastActionExpandAnimationDuration = 0.22f; | |
#pragma mark Utility | |
static inline BOOL RDSwipeViewEdgeIsVertical(RDSwipeViewEdge edge) { | |
switch (edge) { | |
case RDSwipeViewEdgeTop: | |
case RDSwipeViewEdgeBottom: | |
return YES; | |
case RDSwipeViewEdgeLeft: | |
case RDSwipeViewEdgeRight: | |
return NO; | |
} | |
} | |
static inline RDSwipeViewEdge RDSwipeViewEdgeOpposite(RDSwipeViewEdge edge) { | |
switch (edge) { | |
case RDSwipeViewEdgeTop: | |
return RDSwipeViewEdgeBottom; | |
case RDSwipeViewEdgeLeft: | |
return RDSwipeViewEdgeRight; | |
case RDSwipeViewEdgeBottom: | |
return RDSwipeViewEdgeTop; | |
case RDSwipeViewEdgeRight: | |
return RDSwipeViewEdgeLeft; | |
} | |
} | |
static inline CGFloat UIEdgeInsetsValueAtEdge(UIEdgeInsets insets, RDSwipeViewEdge edge) { | |
switch (edge) { | |
case RDSwipeViewEdgeTop: | |
return insets.top; | |
case RDSwipeViewEdgeLeft: | |
return insets.bottom; | |
case RDSwipeViewEdgeBottom: | |
return insets.left; | |
case RDSwipeViewEdgeRight: | |
return insets.right; | |
} | |
} | |
static inline UIEdgeInsets UIEdgeInsetsInsetEdge(UIEdgeInsets insets, RDSwipeViewEdge edge, CGFloat value) { | |
switch (edge) { | |
case RDSwipeViewEdgeTop: | |
insets.top = value; | |
break; | |
case RDSwipeViewEdgeLeft: | |
insets.bottom = value; | |
break; | |
case RDSwipeViewEdgeBottom: | |
insets.left = value; | |
break; | |
case RDSwipeViewEdgeRight: | |
insets.right = value; | |
} | |
return insets; | |
} | |
#pragma mark - RDSwipeActionItemViewDelegate | |
@class RDSwipeActionItemView; | |
@protocol RDSwipeActionItemViewDelegate <NSObject> | |
- (void)swipeActionViewDidActivate:(RDSwipeActionItemView *)view; | |
@end | |
#pragma mark - RDSwipeActionItemView | |
@interface RDSwipeActionItemView : UIView | |
@property (nonatomic) RDSwipeViewEdge layoutEdge; | |
@property (nonatomic) UIImageView *imageView; | |
@property (nonatomic) UILabel *label; | |
@property (nonatomic, weak) id<RDSwipeActionItemViewDelegate> delegate; | |
@end | |
@implementation RDSwipeActionItemView | |
- (instancetype)initWithFrame:(CGRect)frame { | |
self = [super initWithFrame:frame]; | |
if(self) { | |
_imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; | |
self.imageView.contentMode = UIViewContentModeScaleAspectFit; | |
[self addSubview:self.imageView]; | |
_label = [[UILabel alloc] initWithFrame:CGRectZero]; | |
self.label.textAlignment = NSTextAlignmentCenter; | |
self.label.font = [UIFont systemFontOfSize:14.f]; | |
[self addSubview:self.label]; | |
[self updateLabelColor]; | |
[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didRecognizeTap:)]]; | |
} | |
return self; | |
} | |
- (void)layoutSubviews { | |
[super layoutSubviews]; | |
CGSize baseSize = self.bounds.size; | |
CGRect imageFrame = CGRectMake(0.f, 0.f, kItemSize, baseSize.height); | |
if(self.layoutEdge == RDSwipeViewEdgeLeft) { | |
imageFrame.origin.x = baseSize.width - imageFrame.size.width; | |
} | |
self.imageView.frame = imageFrame; | |
CGSize labelBestFittingSize = [self.label sizeThatFits:CGSizeMake(CGFLOAT_MAX, baseSize.height)]; | |
CGRect labelFrame = CGRectMake(imageFrame.size.width, 0.f, labelBestFittingSize.width, baseSize.height); | |
if(self.layoutEdge == RDSwipeViewEdgeLeft) { | |
labelFrame.origin.x = baseSize.width - imageFrame.size.width - labelFrame.size.width; | |
} | |
self.label.frame = labelFrame; | |
} | |
#pragma mark properties | |
- (void)setLayoutEdge:(RDSwipeViewEdge)layoutEdge { | |
if(_layoutEdge != layoutEdge) { | |
_layoutEdge = layoutEdge; | |
[self setNeedsLayout]; | |
} | |
} | |
- (void)setBackgroundColor:(UIColor *)backgroundColor { | |
[super setBackgroundColor:backgroundColor]; | |
[self updateLabelColor]; | |
} | |
- (void)updateLabelColor { | |
CGFloat r, g, b; | |
[self.backgroundColor getRed:&r green:&g blue:&b alpha:NULL]; | |
CGFloat y = 0.299 * r + 0.587 * g + 0.114 * b; | |
self.label.textColor = y > 0.5 ? [UIColor blackColor] : [UIColor whiteColor]; | |
} | |
#pragma mark Gestures | |
- (void)didRecognizeTap:(UITapGestureRecognizer *)recognizer { | |
[self.delegate swipeActionViewDidActivate:self]; | |
} | |
@end | |
#pragma mark - RDSwipeActionItem | |
@interface RDSwipeActionItem() <RDSwipeActionItemViewDelegate> | |
@property (nonatomic, strong) RDSwipeActionItemView *view; | |
@end | |
@implementation RDSwipeActionItem | |
- (instancetype)initWithColor:(UIColor *)color | |
image:(UIImage *)image | |
title:(NSString *)title | |
{ | |
self = [super init]; | |
if(self) { | |
_view = [RDSwipeActionItemView new]; | |
self.view.imageView.image = image; | |
self.view.label.text = title; | |
self.view.backgroundColor = color; | |
self.view.delegate = self; | |
} | |
return self; | |
} | |
#pragma mark Properties | |
- (void)setLayoutEdge:(RDSwipeViewEdge)layoutEdge { | |
self.view.layoutEdge = layoutEdge; | |
} | |
- (RDSwipeViewEdge)layoutEdge { | |
return self.view.layoutEdge; | |
} | |
#pragma mark <RDSwipeActionItemViewDelegate> | |
- (void)swipeActionViewDidActivate:(RDSwipeActionItemView *)view { | |
[self trigger]; | |
} | |
#pragma mark <RDSwipeActionItem> | |
- (void)trigger { | |
if(self.actionBlock != nil) | |
self.actionBlock(self); | |
} | |
@end | |
#pragma mark - RDSwipeViewInset | |
@interface RDSwipeViewInset : UIView | |
@property (nonatomic) RDSwipeViewEdge layoutEdge; | |
@property (nonatomic) CGFloat expandedSize; | |
@property (nonatomic, getter=isFastActionDisplayed) BOOL fastActionDisplayed; | |
@property (nonatomic) CGFloat overscrollStretchingModifier; | |
@end | |
@implementation RDSwipeViewInset | |
- (instancetype)initWithFrame:(CGRect)frame { | |
self = [super initWithFrame:frame]; | |
if(self) { | |
self.clipsToBounds = YES; | |
} | |
return self; | |
} | |
- (void)layoutSubviews { | |
[super layoutSubviews]; | |
NSInteger subviewsCount = self.subviews.count; | |
for(NSInteger subviewIndex = 0u; subviewIndex < subviewsCount - 1; ++subviewIndex) { | |
self.subviews[subviewIndex].frame = [self frameForSubviewAtIndex:subviewIndex]; | |
} | |
if(self.isFastActionDisplayed) | |
self.subviews.lastObject.frame = [self frameForExpadedSubview]; | |
else | |
self.subviews.lastObject.frame = [self frameForSubviewAtIndex:subviewsCount - 1]; | |
} | |
- (CGRect)frameForSubviewAtIndex:(NSUInteger)subviewIndex { | |
NSUInteger subviewsCount = self.subviews.count; | |
CGSize selfSize = self.bounds.size; | |
CGFloat overscrollModifier = self.overscrollStretchingModifier; | |
CGSize baseSize = CGSizeMake(selfSize.width, selfSize.height); | |
CGSize size; | |
if(RDSwipeViewEdgeIsVertical(self.layoutEdge)) { | |
baseSize.height -= MAX(selfSize.height - self.expandedSize, 0) * overscrollModifier; | |
size = CGSizeMake(baseSize.width, | |
MAX(self.expandedSize, baseSize.height) / subviewsCount); | |
} else { | |
baseSize.width -= MAX(selfSize.width - self.expandedSize, 0) * overscrollModifier; | |
size = CGSizeMake(MAX(self.expandedSize, baseSize.width) / subviewsCount, | |
baseSize.height); | |
} | |
CGPoint origin = CGPointZero; | |
switch (self.layoutEdge) { | |
case RDSwipeViewEdgeTop: | |
origin.y = (baseSize.height) * (subviewsCount - subviewIndex) / subviewsCount - size.height; | |
break; | |
case RDSwipeViewEdgeLeft: | |
origin.x = (baseSize.width) * (subviewsCount - subviewIndex) / subviewsCount - size.width; | |
break; | |
case RDSwipeViewEdgeBottom: | |
origin.y = (baseSize.height) * subviewIndex / subviewsCount + (selfSize.width - baseSize.width); | |
break; | |
case RDSwipeViewEdgeRight: | |
origin.x = (baseSize.width) * subviewIndex / subviewsCount + (selfSize.width - baseSize.width); | |
break; | |
} | |
CGRect viewFrame = CGRectMake(origin.x, origin.y, size.width, size.height); | |
return viewFrame; | |
} | |
- (CGRect)frameForExpadedSubview { | |
CGRect viewFrame = self.bounds; | |
if(self.layoutEdge == RDSwipeViewEdgeRight) | |
viewFrame.size.width += 32.f; //To prevent edge flickering; | |
return viewFrame; | |
} | |
#pragma mark Properties | |
- (void)setFastActionDisplayed:(BOOL)fastActionDisplayed { | |
if(_fastActionDisplayed == fastActionDisplayed) | |
return; | |
_fastActionDisplayed = fastActionDisplayed; | |
UIView *view = self.subviews.lastObject; | |
CGRect viewFrame = fastActionDisplayed ? [self frameForExpadedSubview] : [self frameForSubviewAtIndex:self.subviews.count - 1]; | |
[UIView animateWithDuration:kFastActionExpandAnimationDuration | |
delay:0.f | |
options:UIViewAnimationOptionBeginFromCurrentState | |
animations:^{ | |
view.frame = viewFrame; | |
[view layoutIfNeeded]; | |
} completion:nil]; | |
} | |
@end | |
#pragma mark - RDSwipeView | |
@interface RDSwipeView () | |
@property (nonatomic) UIPanGestureRecognizer *panRecognizer; | |
@property (nonatomic) UIView *contentView; | |
@property (nonatomic) NSDictionary<NSNumber *, RDSwipeViewInset *> *insetViewsByEdges; | |
@property (nonatomic) CGPoint initialPanTranslation; | |
@property (nonatomic) CGPoint initialPanPosition; | |
@property (nonatomic) CGPoint panPosition; | |
@property (nonatomic, getter=isPanDirectionLocked) BOOL panDirectionLocked; | |
@property (nonatomic) RDSwipeViewEdge panDirectionLock; | |
@property (nonatomic) UIEdgeInsets panLimits; | |
@property (nonatomic) NSDictionary<NSNumber *, NSMutableArray<id<RDSwipeActionItem>> *> *actionItemsByEdges; | |
@end | |
@implementation RDSwipeView | |
- (instancetype)initWithFrame:(CGRect)frame { | |
self = [super initWithFrame:frame]; | |
if(self) { | |
[self rd_swipeViewDesignatedInit]; | |
} | |
return self; | |
} | |
- (void)awakeFromNib { | |
[super awakeFromNib]; | |
[self rd_swipeViewDesignatedInit]; | |
} | |
- (void)rd_swipeViewDesignatedInit { | |
self.clipsToBounds = YES; | |
_panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didRecognizePan:)]; | |
[self addGestureRecognizer:_panRecognizer]; | |
NSArray<NSNumber *> *edges = @[@(RDSwipeViewEdgeTop), | |
@(RDSwipeViewEdgeLeft), | |
@(RDSwipeViewEdgeBottom), | |
@(RDSwipeViewEdgeRight)]; | |
_actionItemsByEdges = ({ | |
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; | |
for(NSNumber *edge in edges) | |
dictionary[edge] = [NSMutableArray array]; | |
dictionary.copy; | |
}); | |
_insetViewsByEdges = ({ | |
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; | |
for(NSNumber *edge in edges) { | |
RDSwipeViewInset *insetView = [RDSwipeViewInset new]; | |
insetView.translatesAutoresizingMaskIntoConstraints = NO; | |
insetView.layoutEdge = edge.unsignedIntegerValue; | |
[self addSubview:insetView]; | |
dictionary[edge] = insetView; | |
} | |
dictionary.copy; | |
}); | |
[self updateInsetViewsVisibility]; | |
} | |
- (void)layoutSubviews { | |
[super layoutSubviews]; | |
CGSize size = self.bounds.size; | |
CGPoint offset = [self preferredContentPositionForPanPosition:self.panPosition]; | |
for(NSNumber *edge in self.insetViewsByEdges) { | |
RDSwipeViewInset *insetView = self.insetViewsByEdges[edge]; | |
switch (edge.unsignedIntegerValue) { | |
case RDSwipeViewEdgeTop: | |
insetView.frame = CGRectMake(MAX(offset.x, 0), | |
0.f, | |
MAX(size.width - fabs(offset.x), 0), | |
MAX(offset.y, 0.f)); | |
break; | |
case RDSwipeViewEdgeLeft: | |
insetView.frame = CGRectMake(0.f, | |
MAX(offset.y, 0), | |
MAX(offset.x, 0.f), | |
MAX(size.height - fabs(offset.y), 0)); | |
break; | |
case RDSwipeViewEdgeBottom: | |
insetView.frame = CGRectMake(MAX(offset.x, 0), | |
MIN(size.height + offset.y, size.height), | |
MAX(size.width - fabs(offset.x), 0), | |
MAX(-offset.y, 0.f)); | |
break; | |
case RDSwipeViewEdgeRight: | |
insetView.frame = CGRectMake(MIN(size.width + offset.x, size.width), | |
MAX(offset.y, 0), | |
MAX(-offset.x, 0.f), | |
MAX(size.height - fabs(offset.y), 0)); | |
break; | |
} | |
} | |
CGAffineTransform transform = CGAffineTransformMakeTranslation(offset.x, offset.y); | |
for(UIView *view in self.subviews) | |
if(![view isKindOfClass:[RDSwipeViewInset class]]) | |
view.transform = transform; | |
} | |
- (CGPoint)preferredPanPositionForTouchReleasedAt:(CGPoint)pan withVelocity:(CGPoint)velocity { | |
UIEdgeInsets panLimits = self.panLimits; | |
CGPoint target = pan; | |
AUTO(preferred, (^CGFloat (CGFloat value, CGFloat boundValue, RDSwipeViewEdge positiveEdge) | |
{ | |
RDSwipeViewEdge negativeEdge = RDSwipeViewEdgeOpposite(positiveEdge); | |
BOOL canExpandNegative = [self canExpandEdge:negativeEdge]; | |
BOOL canExpandPositive = [self canExpandEdge:positiveEdge]; | |
if(canExpandPositive && [self isFastActionTriggeredAtEdge:negativeEdge withPanPosition:target]) { | |
return -boundValue; | |
} else if(canExpandNegative && [self isFastActionTriggeredAtEdge:positiveEdge withPanPosition:target]) { | |
return boundValue; | |
} else { | |
CGFloat guides[] = { | |
canExpandPositive ? -UIEdgeInsetsValueAtEdge(panLimits, negativeEdge) : 0.f, | |
0.f, | |
canExpandNegative ? UIEdgeInsetsValueAtEdge(panLimits, positiveEdge) : 0.f, | |
}; | |
CGFloat nearest = 0.f; | |
CGFloat lowestDistance = CGFLOAT_MAX; | |
for(NSUInteger i = 0.f; i < 3; ++i) { | |
CGFloat guideValue = guides[i]; | |
CGFloat distance = fabs(guideValue - value); | |
if(distance < lowestDistance) { | |
nearest = guideValue; | |
lowestDistance = distance; | |
} | |
} | |
return nearest; | |
} | |
})); | |
CGFloat preferredX = preferred(target.x, self.bounds.size.width, RDSwipeViewEdgeRight); | |
CGFloat preferredY = preferred(target.y, self.bounds.size.height, RDSwipeViewEdgeBottom); | |
return CGPointMake(preferredX, preferredY); | |
} | |
- (CGPoint)preferredContentPositionForPanPosition:(CGPoint)pan { | |
UIEdgeInsets panLimits = self.panLimits; | |
RDSwipeViewEdges fastActionEdges = self.fastActionEdges; | |
CGFloat x = pan.x; | |
CGFloat y = pan.y; | |
if(![self canExpandEdge:RDSwipeViewEdgeBottom] && y < 0.f) | |
y = 0.f; | |
else if(!(fastActionEdges & RDSwipeViewEdgesTop) && y < -panLimits.top) | |
y = -panLimits.top; | |
if(![self canExpandEdge:RDSwipeViewEdgeRight] && x < 0.f) | |
x = 0.f; | |
else if(!(fastActionEdges & RDSwipeViewEdgeLeft) && x < -panLimits.left) | |
x = -panLimits.left; | |
if(![self canExpandEdge:RDSwipeViewEdgeTop] && y > 0.f) | |
y = 0.f; | |
if(!(fastActionEdges & RDSwipeViewEdgesBottom) && y > panLimits.bottom) | |
y = panLimits.bottom; | |
if(![self canExpandEdge:RDSwipeViewEdgeLeft] && x > 0.f) | |
x = 0.f; | |
else if(!(fastActionEdges & RDSwipeViewEdgeRight) && x > panLimits.right) | |
x = panLimits.right; | |
x += (pan.x - x) * kPanResistanceModifier; | |
y += (pan.y - y) * kPanResistanceModifier; | |
return CGPointMake(-x, -y); | |
} | |
- (BOOL)isFastActionTriggeredAtEdge:(RDSwipeViewEdge)edge withPanPosition:(CGPoint)position { | |
if(!(self.fastActionEdges & edge)) | |
return NO; | |
switch (edge) { | |
case RDSwipeViewEdgeTop: | |
return position.y < -self.panLimits.top - kFastActionOverscrollThreshold; | |
case RDSwipeViewEdgeLeft: | |
return position.x < -self.panLimits.left - kFastActionOverscrollThreshold; | |
case RDSwipeViewEdgeBottom: | |
return position.y > self.panLimits.bottom + kFastActionOverscrollThreshold; | |
case RDSwipeViewEdgeRight: | |
return position.x > self.panLimits.right + kFastActionOverscrollThreshold; | |
} | |
} | |
- (BOOL)canExpandEdge:(RDSwipeViewEdge)edge { | |
return (!self.isPanDirectionLocked || self.panDirectionLock == edge) && self.actionItemsByEdges[@(edge)].count > 0u; | |
} | |
- (void)updateInsetViewsVisibility { | |
for(NSNumber *edge in self.insetViewsByEdges) { | |
RDSwipeViewInset *insetView = self.insetViewsByEdges[edge]; | |
insetView.hidden = !self.isPanDirectionLocked || edge.unsignedIntegerValue != RDSwipeViewEdgeOpposite(self.panDirectionLock); | |
} | |
} | |
- (void)triggerFastActionForEdge:(RDSwipeViewEdge)edge { | |
if(!(self.fastActionEdges & edge)) | |
return; | |
[self.actionItemsByEdges[@(edge)].lastObject trigger]; | |
} | |
#pragma mark Callbacks | |
- (void)didRecognizePan:(UIPanGestureRecognizer *)recognizer { | |
NSParameterAssert(recognizer); | |
CGPoint panTranslation = [recognizer translationInView:self]; | |
CGPoint velocity = [recognizer velocityInView:self]; | |
UIGestureRecognizerState recognizerState = recognizer.state; | |
if(recognizerState == UIGestureRecognizerStateBegan) { | |
self.initialPanPosition = self.panPosition; | |
self.initialPanTranslation = panTranslation; | |
if(!self.panDirectionLocked) { | |
self.panDirectionLocked = YES; | |
if(fabs(velocity.x) > fabs(velocity.y)) | |
self.panDirectionLock = velocity.x < 0 ? RDSwipeViewEdgeLeft : RDSwipeViewEdgeRight; | |
else | |
self.panDirectionLock = velocity.y < 0? RDSwipeViewEdgeTop : RDSwipeViewEdgeBottom; | |
[self updateInsetViewsVisibility]; | |
} | |
} | |
CGFloat x = 0.f, y = 0.f; | |
if(self.isPanDirectionLocked && !RDSwipeViewEdgeIsVertical(self.panDirectionLock)) | |
x = -panTranslation.x + self.initialPanTranslation.x + self.initialPanPosition.x; | |
else | |
y = -panTranslation.y + self.initialPanTranslation.y + self.initialPanPosition.y; | |
CGPoint expectedPanPosition = CGPointMake(x, y); | |
if(recognizerState == UIGestureRecognizerStateEnded || recognizerState == UIGestureRecognizerStateCancelled ) { | |
RDSwipeViewEdge fastActionEdge = RDSwipeViewEdgeOpposite(self.panDirectionLock); | |
if([self isFastActionTriggeredAtEdge:fastActionEdge withPanPosition:expectedPanPosition]) | |
[self triggerFastActionForEdge:fastActionEdge]; | |
self.panPosition = [self preferredPanPositionForTouchReleasedAt:expectedPanPosition withVelocity:velocity]; | |
if(CGPointEqualToPoint(self.panPosition, CGPointZero)) | |
self.panDirectionLocked = NO; | |
[UIView animateWithDuration:kCollapseAnimationDuration delay:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:^{ | |
[self layoutIfNeeded]; | |
} completion:^(BOOL finished) { | |
if(finished) { | |
[self updateInsetViewsVisibility]; | |
} | |
}]; | |
} else { | |
self.panPosition = expectedPanPosition; | |
[self layoutIfNeeded]; | |
} | |
} | |
#pragma mark Properties | |
- (void)setPanPosition:(CGPoint)panPosition { | |
if(!CGPointEqualToPoint(_panPosition, panPosition)) { | |
_panPosition = panPosition; | |
for(NSNumber *edge in self.insetViewsByEdges) | |
self.insetViewsByEdges[edge].fastActionDisplayed = [self isFastActionTriggeredAtEdge:edge.unsignedIntegerValue | |
withPanPosition:panPosition]; | |
[self setNeedsLayout]; | |
} | |
} | |
- (NSArray<id<RDSwipeActionItem>> *)actionItemsForEdge:(RDSwipeViewEdge)edge { | |
return self.actionItemsByEdges[@(edge)]; | |
} | |
- (void)setActionItems:(NSArray<id<RDSwipeActionItem>> *)items forEdge:(RDSwipeViewEdge)edge { | |
RDSwipeViewInset *insetView = self.insetViewsByEdges[@(edge)]; | |
for(UIView *view in insetView.subviews) | |
[view removeFromSuperview]; | |
NSMutableArray<id<RDSwipeActionItem>> *actionItems = self.actionItemsByEdges[@(edge)]; | |
[actionItems removeAllObjects]; | |
[actionItems addObjectsFromArray:items]; | |
for(id<RDSwipeActionItem> actionItem in actionItems) | |
[insetView addSubview:[actionItem view]]; | |
CGFloat maxOffset = actionItems.count * kItemSize; | |
insetView.expandedSize = maxOffset; | |
insetView.overscrollStretchingModifier = items.count == 1 ? kOverscrollModifier : 0.f; | |
self.panLimits = UIEdgeInsetsInsetEdge(self.panLimits, edge, maxOffset); | |
[self setNeedsLayout]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment