diff options
author | Marino Faggiana <ios@nextcloud.com> | 2017-06-26 13:52:33 +0300 |
---|---|---|
committer | Marino Faggiana <ios@nextcloud.com> | 2017-06-26 13:52:33 +0300 |
commit | ff01d8033bf4ed6b3bb93109b534a3f68ddd4381 (patch) | |
tree | e978154b4a5b0256f57a52f6f7ea95b7c94cc230 /Libraries external/MGSwipeTableCell | |
parent | 8e03f2411806cc8ff78273df0a5c9741bc2cf977 (diff) |
Add library
Diffstat (limited to 'Libraries external/MGSwipeTableCell')
-rwxr-xr-x | Libraries external/MGSwipeTableCell/MGSwipeButton.h | 51 | ||||
-rwxr-xr-x | Libraries external/MGSwipeTableCell/MGSwipeButton.m | 159 | ||||
-rwxr-xr-x | Libraries external/MGSwipeTableCell/MGSwipeTableCell.h | 276 | ||||
-rwxr-xr-x | Libraries external/MGSwipeTableCell/MGSwipeTableCell.m | 1389 |
4 files changed, 1875 insertions, 0 deletions
diff --git a/Libraries external/MGSwipeTableCell/MGSwipeButton.h b/Libraries external/MGSwipeTableCell/MGSwipeButton.h new file mode 100755 index 000000000..1da612637 --- /dev/null +++ b/Libraries external/MGSwipeTableCell/MGSwipeButton.h @@ -0,0 +1,51 @@ +/* + * MGSwipeTableCell is licensed under MIT license. See LICENSE.md file for more information. + * Copyright (c) 2016 Imanol Fernandez @MortimerGoro + */ + +#import <UIKit/UIKit.h> + + +@class MGSwipeTableCell; + +/** + * This is a convenience class to create MGSwipeTableCell buttons + * Using this class is optional because MGSwipeTableCell is button agnostic and can use any UIView for that purpose + * Anyway, it's recommended that you use this class because is totally tested and easy to use ;) + */ +@interface MGSwipeButton : UIButton + +/** + * Convenience block callback for developers lazy to implement the MGSwipeTableCellDelegate. + * @return Return YES to autohide the swipe view + */ +typedef BOOL(^ MGSwipeButtonCallback)(MGSwipeTableCell * _Nonnull cell); +@property (nonatomic, strong, nullable) MGSwipeButtonCallback callback; + +/** A width for the expanded buttons. Defaults to 0, which means sizeToFit will be called. */ +@property (nonatomic, assign) CGFloat buttonWidth; + +/** + * Convenience static constructors + */ ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title backgroundColor:(nullable UIColor *) color; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title backgroundColor:(nullable UIColor *) color padding:(NSInteger) padding; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title backgroundColor:(nullable UIColor *) color insets:(UIEdgeInsets) insets; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title backgroundColor:(nullable UIColor *) color callback:(nullable MGSwipeButtonCallback) callback; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title backgroundColor:(nullable UIColor *) color padding:(NSInteger) padding callback:(nullable MGSwipeButtonCallback) callback; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title backgroundColor:(nullable UIColor *) color insets:(UIEdgeInsets) insets callback:(nullable MGSwipeButtonCallback) callback; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title icon:(nullable UIImage*) icon backgroundColor:(nullable UIColor *) color; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title icon:(nullable UIImage*) icon backgroundColor:(nullable UIColor *) color padding:(NSInteger) padding; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title icon:(nullable UIImage*) icon backgroundColor:(nullable UIColor *) color insets:(UIEdgeInsets) insets; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title icon:(nullable UIImage*) icon backgroundColor:(nullable UIColor *) color callback:(nullable MGSwipeButtonCallback) callback; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title icon:(nullable UIImage*) icon backgroundColor:(nullable UIColor *) color padding:(NSInteger) padding callback:(nullable MGSwipeButtonCallback) callback; ++(nonnull instancetype) buttonWithTitle:(nonnull NSString *) title icon:(nullable UIImage*) icon backgroundColor:(nullable UIColor *) color insets:(UIEdgeInsets) insets callback:(nullable MGSwipeButtonCallback) callback; + +-(void) setPadding:(CGFloat) padding; +-(void) setEdgeInsets:(UIEdgeInsets)insets; +-(void) centerIconOverText; +-(void) centerIconOverTextWithSpacing: (CGFloat) spacing; +-(void) iconTintColor:(nullable UIColor *)tintColor; + + +@end diff --git a/Libraries external/MGSwipeTableCell/MGSwipeButton.m b/Libraries external/MGSwipeTableCell/MGSwipeButton.m new file mode 100755 index 000000000..1c005452a --- /dev/null +++ b/Libraries external/MGSwipeTableCell/MGSwipeButton.m @@ -0,0 +1,159 @@ +/* + * MGSwipeTableCell is licensed under MIT license. See LICENSE.md file for more information. + * Copyright (c) 2016 Imanol Fernandez @MortimerGoro + */ + +#import "MGSwipeButton.h" + +@class MGSwipeTableCell; + +@implementation MGSwipeButton + ++(instancetype) buttonWithTitle:(NSString *) title backgroundColor:(UIColor *) color +{ + return [self buttonWithTitle:title icon:nil backgroundColor:color]; +} + ++(instancetype) buttonWithTitle:(NSString *) title backgroundColor:(UIColor *) color padding:(NSInteger) padding +{ + return [self buttonWithTitle:title icon:nil backgroundColor:color insets:UIEdgeInsetsMake(0, padding, 0, padding)]; +} + ++(instancetype) buttonWithTitle:(NSString *) title backgroundColor:(UIColor *) color insets:(UIEdgeInsets) insets +{ + return [self buttonWithTitle:title icon:nil backgroundColor:color insets:insets]; +} + ++(instancetype) buttonWithTitle:(NSString *) title backgroundColor:(UIColor *) color callback:(MGSwipeButtonCallback) callback +{ + return [self buttonWithTitle:title icon:nil backgroundColor:color callback:callback]; +} + ++(instancetype) buttonWithTitle:(NSString *) title backgroundColor:(UIColor *) color padding:(NSInteger) padding callback:(MGSwipeButtonCallback) callback +{ + return [self buttonWithTitle:title icon:nil backgroundColor:color insets:UIEdgeInsetsMake(0, padding, 0, padding) callback:callback]; +} + ++(instancetype) buttonWithTitle:(NSString *) title backgroundColor:(UIColor *) color insets:(UIEdgeInsets) insets callback:(MGSwipeButtonCallback) callback +{ + return [self buttonWithTitle:title icon:nil backgroundColor:color insets:insets callback:callback]; +} + ++(instancetype) buttonWithTitle:(NSString *) title icon:(UIImage*) icon backgroundColor:(UIColor *) color +{ + return [self buttonWithTitle:title icon:icon backgroundColor:color callback:nil]; +} + ++(instancetype) buttonWithTitle:(NSString *) title icon:(UIImage*) icon backgroundColor:(UIColor *) color padding:(NSInteger) padding +{ + return [self buttonWithTitle:title icon:icon backgroundColor:color insets:UIEdgeInsetsMake(0, padding, 0, padding) callback:nil]; +} + ++(instancetype) buttonWithTitle:(NSString *) title icon:(UIImage*) icon backgroundColor:(UIColor *) color insets:(UIEdgeInsets) insets +{ + return [self buttonWithTitle:title icon:icon backgroundColor:color insets:insets callback:nil]; +} + ++(instancetype) buttonWithTitle:(NSString *) title icon:(UIImage*) icon backgroundColor:(UIColor *) color callback:(MGSwipeButtonCallback) callback +{ + return [self buttonWithTitle:title icon:icon backgroundColor:color padding:10 callback:callback]; +} + ++(instancetype) buttonWithTitle:(NSString *) title icon:(UIImage*) icon backgroundColor:(UIColor *) color padding:(NSInteger) padding callback:(MGSwipeButtonCallback) callback +{ + return [self buttonWithTitle:title icon:icon backgroundColor:color insets:UIEdgeInsetsMake(0, padding, 0, padding) callback:callback]; +} + ++(instancetype) buttonWithTitle:(NSString *) title icon:(UIImage*) icon backgroundColor:(UIColor *) color insets:(UIEdgeInsets) insets callback:(MGSwipeButtonCallback) callback +{ + MGSwipeButton * button = [self buttonWithType:UIButtonTypeCustom]; + button.backgroundColor = color; + button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping; + button.titleLabel.textAlignment = NSTextAlignmentCenter; + [button setTitle:title forState:UIControlStateNormal]; + [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; + [button setImage:icon forState:UIControlStateNormal]; + button.callback = callback; + [button setEdgeInsets:insets]; + return button; +} + +-(BOOL) callMGSwipeConvenienceCallback: (MGSwipeTableCell *) sender +{ + if (_callback) { + return _callback(sender); + } + return NO; +} + +-(void) centerIconOverText +{ + [self centerIconOverTextWithSpacing: 3.0]; +} + +-(void) centerIconOverTextWithSpacing: (CGFloat) spacing { + CGSize size = self.imageView.image.size; + + if ([UIDevice currentDevice].systemVersion.floatValue >= 9.0 && [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { + self.titleEdgeInsets = UIEdgeInsetsMake(0.0, + 0.0, + -(size.height + spacing), + -size.width); + size = [self.titleLabel.text sizeWithAttributes:@{ NSFontAttributeName: self.titleLabel.font }]; + self.imageEdgeInsets = UIEdgeInsetsMake(-(size.height + spacing), + -size.width, + 0.0, + 0.0); + } + else + { + self.titleEdgeInsets = UIEdgeInsetsMake(0.0, + -size.width, + -(size.height + spacing), + 0.0); + size = [self.titleLabel.text sizeWithAttributes:@{ NSFontAttributeName: self.titleLabel.font }]; + self.imageEdgeInsets = UIEdgeInsetsMake(-(size.height + spacing), + 0.0, + 0.0, + -size.width); + } +} + +-(void) setPadding:(CGFloat) padding +{ + self.contentEdgeInsets = UIEdgeInsetsMake(0, padding, 0, padding); + [self sizeToFit]; +} + +- (void)setButtonWidth:(CGFloat)buttonWidth +{ + _buttonWidth = buttonWidth; + if (_buttonWidth > 0) + { + CGRect frame = self.frame; + frame.size.width = _buttonWidth; + self.frame = frame; + } + else + { + [self sizeToFit]; + } +} + +-(void) setEdgeInsets:(UIEdgeInsets)insets +{ + self.contentEdgeInsets = insets; + [self sizeToFit]; +} + +-(void) iconTintColor:(UIColor *)tintColor +{ + UIImage *currentIcon = self.imageView.image; + if (currentIcon.renderingMode != UIImageRenderingModeAlwaysTemplate) { + currentIcon = [currentIcon imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [self setImage:currentIcon forState:UIControlStateNormal]; + } + self.tintColor = tintColor; +} + +@end diff --git a/Libraries external/MGSwipeTableCell/MGSwipeTableCell.h b/Libraries external/MGSwipeTableCell/MGSwipeTableCell.h new file mode 100755 index 000000000..1a9a3889d --- /dev/null +++ b/Libraries external/MGSwipeTableCell/MGSwipeTableCell.h @@ -0,0 +1,276 @@ +/* + * MGSwipeTableCell is licensed under MIT license. See LICENSE.md file for more information. + * Copyright (c) 2016 Imanol Fernandez @MortimerGoro + */ + +#import <UIKit/UIKit.h> +#import "MGSwipeButton.h" + + +/** Transition types */ +typedef NS_ENUM(NSInteger, MGSwipeTransition) { + MGSwipeTransitionBorder = 0, + MGSwipeTransitionStatic, + MGSwipeTransitionDrag, + MGSwipeTransitionClipCenter, + MGSwipeTransitionRotate3D +}; + +/** Compatibility with older versions */ +#define MGSwipeTransition3D MGSwipeTransitionRotate3D +#define MGSwipeStateSwippingLeftToRight MGSwipeStateSwipingLeftToRight +#define MGSwipeStateSwippingRightToLeft MGSwipeStateSwipingRightToLeft + +/** Swipe directions */ +typedef NS_ENUM(NSInteger, MGSwipeDirection) { + MGSwipeDirectionLeftToRight = 0, + MGSwipeDirectionRightToLeft +}; + +/** Swipe state */ +typedef NS_ENUM(NSInteger, MGSwipeState) { + MGSwipeStateNone = 0, + MGSwipeStateSwipingLeftToRight, + MGSwipeStateSwipingRightToLeft, + MGSwipeStateExpandingLeftToRight, + MGSwipeStateExpandingRightToLeft, +}; + +/** Swipe state */ +typedef NS_ENUM(NSInteger, MGSwipeExpansionLayout) { + MGSwipeExpansionLayoutBorder = 0, + MGSwipeExpansionLayoutCenter, + MGSwipeExpansionLayoutNone +}; + +/** Swipe Easing Function */ +typedef NS_ENUM(NSInteger, MGSwipeEasingFunction) { + MGSwipeEasingFunctionLinear = 0, + MGSwipeEasingFunctionQuadIn, + MGSwipeEasingFunctionQuadOut, + MGSwipeEasingFunctionQuadInOut, + MGSwipeEasingFunctionCubicIn, + MGSwipeEasingFunctionCubicOut, + MGSwipeEasingFunctionCubicInOut, + MGSwipeEasingFunctionBounceIn, + MGSwipeEasingFunctionBounceOut, + MGSwipeEasingFunctionBounceInOut +}; + +/** + * Swipe animation settings + **/ +@interface MGSwipeAnimation : NSObject +/** Animation duration in seconds. Default value 0.3 */ +@property (nonatomic, assign) CGFloat duration; +/** Animation easing function. Default value EaseOutBounce */ +@property (nonatomic, assign) MGSwipeEasingFunction easingFunction; +/** Override this method to implement custom easing functions */ +-(CGFloat) value:(CGFloat) elapsed duration:(CGFloat) duration from:(CGFloat) from to:(CGFloat) to; + +@end + +/** + * Swipe settings + **/ +@interface MGSwipeSettings: NSObject +/** Transition used while swiping buttons */ +@property (nonatomic, assign) MGSwipeTransition transition; +/** Size proportional threshold to hide/keep the buttons when the user ends swiping. Default value 0.5 */ +@property (nonatomic, assign) CGFloat threshold; +/** Optional offset to change the swipe buttons position. Relative to the cell border position. Default value: 0 + ** For example it can be used to avoid cropped buttons when sectionIndexTitlesForTableView is used in the UITableView + **/ +@property (nonatomic, assign) CGFloat offset; +/** Top margin of the buttons relative to the contentView */ +@property (nonatomic, assign) CGFloat topMargin; +/** Bottom margin of the buttons relative to the contentView */ +@property (nonatomic, assign) CGFloat bottomMargin; +/** Distance between the buttons. Default value : 0 */ +@property (nonatomic, assign) CGFloat buttonsDistance; + +/** Animation settings when the swipe buttons are shown */ +@property (nonatomic, strong, nonnull) MGSwipeAnimation * showAnimation; +/** Animation settings when the swipe buttons are hided */ +@property (nonatomic, strong, nonnull) MGSwipeAnimation * hideAnimation; +/** Animation settings when the cell is stretched from the swipe buttons */ +@property (nonatomic, strong, nonnull) MGSwipeAnimation * stretchAnimation; + +/** Property to read or change swipe animation durations. Default value 0.3 */ +@property (nonatomic, assign) CGFloat animationDuration DEPRECATED_ATTRIBUTE; + +/** If true the buttons are kept swiped when the threshold is reached and the user ends the gesture + * If false, the buttons are always hidden when the user ends the swipe gesture + */ +@property (nonatomic, assign) BOOL keepButtonsSwiped; + +/** If true the table cell is not swiped, just the buttons **/ +@property (nonatomic, assign) BOOL onlySwipeButtons; + +/** If NO the swipe bounces will be disabled, the swipe motion will stop right after the button */ +@property (nonatomic, assign) BOOL enableSwipeBounces; + +/** Coefficient applied to cell movement in bounce zone. Set to value between 0.0 and 1.0 + to make the cell 'resist' swiping after buttons are revealed. Default is 1.0 */ +@property (nonatomic, assign) CGFloat swipeBounceRate; + +@end + + +/** + * Expansion settings to make expandable buttons + * Swipe button are not expandable by default + **/ +@interface MGSwipeExpansionSettings: NSObject +/** index of the expandable button (in the left or right buttons arrays) */ +@property (nonatomic, assign) NSInteger buttonIndex; +/** if true the button fills the cell on trigger, else it bounces back to its initial position */ +@property (nonatomic, assign) BOOL fillOnTrigger; +/** Size proportional threshold to trigger the expansion button. Default value 1.5 */ +@property (nonatomic, assign) CGFloat threshold; +/** Optional expansion color. Expanded button's background color is used by default **/ +@property (nonatomic, strong, nullable) UIColor * expansionColor; +/** Defines the layout of the expanded button **/ +@property (nonatomic, assign) MGSwipeExpansionLayout expansionLayout; +/** Animation settings when the expansion is triggered **/ +@property (nonatomic, strong, nonnull) MGSwipeAnimation * triggerAnimation; + +/** Property to read or change expansion animation durations. Default value 0.2 + * The target animation is the change of a button from normal state to expanded state + */ +@property (nonatomic, assign) CGFloat animationDuration; +@end + + +/** helper forward declaration */ +@class MGSwipeTableCell; + +/** + * Optional delegate to configure swipe buttons or to receive triggered actions. + * Buttons can be configured inline when the cell is created instead of using this delegate, + * but using the delegate improves memory usage because buttons are only created in demand + */ +@protocol MGSwipeTableCellDelegate <NSObject> + +@optional +/** + * Delegate method to enable/disable swipe gestures + * @return YES if swipe is allowed + **/ +-(BOOL) swipeTableCell:(nonnull MGSwipeTableCell*) cell canSwipe:(MGSwipeDirection) direction fromPoint:(CGPoint) point; +-(BOOL) swipeTableCell:(nonnull MGSwipeTableCell*) cell canSwipe:(MGSwipeDirection) direction DEPRECATED_ATTRIBUTE; //backwards compatibility + +/** + * Delegate method invoked when the current swipe state changes + @param state the current Swipe State + @param gestureIsActive YES if the user swipe gesture is active. No if the uses has already ended the gesture + **/ +-(void) swipeTableCell:(nonnull MGSwipeTableCell*) cell didChangeSwipeState:(MGSwipeState) state gestureIsActive:(BOOL) gestureIsActive; + +/** + * Called when the user clicks a swipe button or when a expandable button is automatically triggered + * @return YES to autohide the current swipe buttons + **/ +-(BOOL) swipeTableCell:(nonnull MGSwipeTableCell*) cell tappedButtonAtIndex:(NSInteger) index direction:(MGSwipeDirection)direction fromExpansion:(BOOL) fromExpansion; +/** + * Delegate method to setup the swipe buttons and swipe/expansion settings + * Buttons can be any kind of UIView but it's recommended to use the convenience MGSwipeButton class + * Setting up buttons with this delegate instead of using cell properties improves memory usage because buttons are only created in demand + * @param cell the UITableViewCell to configure. You can get the indexPath using [tableView indexPathForCell:cell] + * @param direction The swipe direction (left to right or right to left) + * @param swipeSettings instance to configure the swipe transition and setting (optional) + * @param expansionSettings instance to configure button expansions (optional) + * @return Buttons array + **/ +-(nullable NSArray<UIView*>*) swipeTableCell:(nonnull MGSwipeTableCell*) cell swipeButtonsForDirection:(MGSwipeDirection)direction + swipeSettings:(nonnull MGSwipeSettings*) swipeSettings expansionSettings:(nonnull MGSwipeExpansionSettings*) expansionSettings; + +/** + * Called when the user taps on a swiped cell + * @return YES to autohide the current swipe buttons + **/ +-(BOOL) swipeTableCell:(nonnull MGSwipeTableCell *)cell shouldHideSwipeOnTap:(CGPoint) point; + +/** + * Called when the cell will begin swiping + * Useful to make cell changes that only are shown after the cell is swiped open + **/ +-(void) swipeTableCellWillBeginSwiping:(nonnull MGSwipeTableCell *) cell; + +/** + * Called when the cell will end swiping + **/ +-(void) swipeTableCellWillEndSwiping:(nonnull MGSwipeTableCell *) cell; + +@end + + +/** + * Swipe Cell class + * To implement swipe cells you have to override from this class + * You can create the cells programmatically, using xibs or storyboards + */ +@interface MGSwipeTableCell : UITableViewCell + +/** optional delegate (not retained) */ +@property (nonatomic, weak, nullable) id<MGSwipeTableCellDelegate> delegate; + +/** optional to use contentView alternative. Use this property instead of contentView to support animated views while swiping */ +@property (nonatomic, strong, readonly, nonnull) UIView * swipeContentView; + +/** + * Left and right swipe buttons and its settings. + * Buttons can be any kind of UIView but it's recommended to use the convenience MGSwipeButton class + */ +@property (nonatomic, copy, nonnull) NSArray<UIView*> * leftButtons; +@property (nonatomic, copy, nonnull) NSArray<UIView*> * rightButtons; +@property (nonatomic, strong, nonnull) MGSwipeSettings * leftSwipeSettings; +@property (nonatomic, strong, nonnull) MGSwipeSettings * rightSwipeSettings; + +/** Optional settings to allow expandable buttons */ +@property (nonatomic, strong, nonnull) MGSwipeExpansionSettings * leftExpansion; +@property (nonatomic, strong, nonnull) MGSwipeExpansionSettings * rightExpansion; + +/** Readonly property to fetch the current swipe state */ +@property (nonatomic, readonly) MGSwipeState swipeState; +/** Readonly property to check if the user swipe gesture is currently active */ +@property (nonatomic, readonly) BOOL isSwipeGestureActive; + +// default is NO. Controls whether multiple cells can be swiped simultaneously +@property (nonatomic) BOOL allowsMultipleSwipe; +// default is NO. Controls whether buttons with different width are allowed. Buttons are resized to have the same size by default. +@property (nonatomic) BOOL allowsButtonsWithDifferentWidth; +//default is YES. Controls whether swipe gesture is allowed when the touch starts into the swiped buttons +@property (nonatomic) BOOL allowsSwipeWhenTappingButtons; +//default is YES. Controls whether swipe gesture is allowed in opposite directions. NO value disables swiping in opposite direction once started in one direction +@property (nonatomic) BOOL allowsOppositeSwipe; +// default is NO. Controls whether the cell selection/highlight status is preserved when expansion occurs +@property (nonatomic) BOOL preservesSelectionStatus; +/* default is NO. Controls whether dismissing a swiped cell when tapping outside of the cell generates a real touch event on the other cell. + Default behaviour is the same as the Mail app on iOS. Enable it if you want to allow to start a new swipe while a cell is already in swiped in a single step. */ +@property (nonatomic) BOOL touchOnDismissSwipe; + +/** Optional background color for swipe overlay. If not set, its inferred automatically from the cell contentView */ +@property (nonatomic, strong, nullable) UIColor * swipeBackgroundColor; +/** Property to read or change the current swipe offset programmatically */ +@property (nonatomic, assign) CGFloat swipeOffset; + +/** Utility methods to show or hide swipe buttons programmatically */ +-(void) hideSwipeAnimated: (BOOL) animated; +-(void) hideSwipeAnimated: (BOOL) animated completion:(nullable void(^)(BOOL finished)) completion; +-(void) showSwipe: (MGSwipeDirection) direction animated: (BOOL) animated; +-(void) showSwipe: (MGSwipeDirection) direction animated: (BOOL) animated completion:(nullable void(^)(BOOL finished)) completion; +-(void) setSwipeOffset:(CGFloat)offset animated: (BOOL) animated completion:(nullable void(^)(BOOL finished)) completion; +-(void) setSwipeOffset:(CGFloat)offset animation: (nullable MGSwipeAnimation *) animation completion:(nullable void(^)(BOOL finished)) completion; +-(void) expandSwipe: (MGSwipeDirection) direction animated: (BOOL) animated; + +/** Refresh method to be used when you want to update the cell contents while the user is swiping */ +-(void) refreshContentView; +/** Refresh method to be used when you want to dynamically change the left or right buttons (add or remove) + * If you only want to change the title or the backgroundColor of a button you can change it's properties (get the button instance from leftButtons or rightButtons arrays) + * @param usingDelegate if YES new buttons will be fetched using the MGSwipeTableCellDelegate. Otherwise new buttons will be fetched from leftButtons/rightButtons properties. + */ +-(void) refreshButtons: (BOOL) usingDelegate; + +@end + diff --git a/Libraries external/MGSwipeTableCell/MGSwipeTableCell.m b/Libraries external/MGSwipeTableCell/MGSwipeTableCell.m new file mode 100755 index 000000000..267f14b2b --- /dev/null +++ b/Libraries external/MGSwipeTableCell/MGSwipeTableCell.m @@ -0,0 +1,1389 @@ +/* + * MGSwipeTableCell is licensed under MIT license. See LICENSE.md file for more information. + * Copyright (c) 2016 Imanol Fernandez @MortimerGoro + */ + +#import "MGSwipeTableCell.h" + +#pragma mark Input Overlay Helper Class +/** Used to capture table input while swipe buttons are visible*/ +@interface MGSwipeTableInputOverlay : UIView +@property (nonatomic, weak) MGSwipeTableCell * currentCell; +@end + +@implementation MGSwipeTableInputOverlay + +-(id) initWithFrame:(CGRect)frame +{ + if (self = [super initWithFrame:frame]) { + self.backgroundColor = [UIColor clearColor]; + self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + } + return self; +} + +-(UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event +{ + if (event == nil) { + return nil; + } + if (!_currentCell) { + [self removeFromSuperview]; + return nil; + } + CGPoint p = [self convertPoint:point toView:_currentCell]; + if (_currentCell && (_currentCell.hidden || CGRectContainsPoint(_currentCell.bounds, p))) { + return nil; + } + BOOL hide = YES; + if (_currentCell && _currentCell.delegate && [_currentCell.delegate respondsToSelector:@selector(swipeTableCell:shouldHideSwipeOnTap:)]) { + hide = [_currentCell.delegate swipeTableCell:_currentCell shouldHideSwipeOnTap:p]; + } + if (hide) { + [_currentCell hideSwipeAnimated:YES]; + } + return _currentCell.touchOnDismissSwipe ? nil : self; +} + +@end + +#pragma mark Button Container View and transitions + +@interface MGSwipeButtonsView : UIView +@property (nonatomic, weak) MGSwipeTableCell * cell; +@property (nonatomic, strong) UIColor * backgroundColorCopy; +@end + +@implementation MGSwipeButtonsView +{ + NSArray * _buttons; + UIView * _container; + BOOL _fromLeft; + UIView * _expandedButton; + UIView * _expandedButtonAnimated; + UIView * _expansionBackground; + UIView * _expansionBackgroundAnimated; + CGRect _expandedButtonBoundsCopy; + MGSwipeExpansionLayout _expansionLayout; + CGFloat _expansionOffset; + CGFloat _buttonsDistance; + BOOL _autoHideExpansion; +} + +#pragma mark Layout + +-(instancetype) initWithButtons:(NSArray*) buttonsArray direction:(MGSwipeDirection) direction differentWidth:(BOOL) differentWidth buttonsDistance:(CGFloat) buttonsDistance +{ + CGFloat containerWidth = 0; + CGSize maxSize = CGSizeZero; + UIView* lastButton = [buttonsArray lastObject]; + for (UIView * button in buttonsArray) { + containerWidth += button.bounds.size.width + (lastButton == button ? 0 : buttonsDistance); + maxSize.width = MAX(maxSize.width, button.bounds.size.width); + maxSize.height = MAX(maxSize.height, button.bounds.size.height); + } + if (!differentWidth) { + containerWidth = maxSize.width * buttonsArray.count + buttonsDistance * (buttonsArray.count - 1); + } + + if (self = [super initWithFrame:CGRectMake(0, 0, containerWidth, maxSize.height)]) { + _fromLeft = direction == MGSwipeDirectionLeftToRight; + _buttonsDistance = buttonsDistance; + _container = [[UIView alloc] initWithFrame:self.bounds]; + _container.clipsToBounds = YES; + _container.backgroundColor = [UIColor clearColor]; + [self addSubview:_container]; + _buttons = _fromLeft ? buttonsArray: [[buttonsArray reverseObjectEnumerator] allObjects]; + for (UIView * button in _buttons) { + if ([button isKindOfClass:[UIButton class]]) { + UIButton * btn = (UIButton*)button; + [btn removeTarget:nil action:@selector(mgButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; //Remove all targets to avoid problems with reused buttons among many cells + [btn addTarget:self action:@selector(mgButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; + } + if (!differentWidth) { + button.frame = CGRectMake(0, 0, maxSize.width, maxSize.height); + } + button.autoresizingMask = UIViewAutoresizingFlexibleHeight; + [_container insertSubview:button atIndex: _fromLeft ? 0: _container.subviews.count]; + } + [self resetButtons]; + } + return self; +} + +-(void) dealloc +{ + for (UIView * button in _buttons) { + if ([button isKindOfClass:[UIButton class]]) { + [(UIButton *)button removeTarget:self action:@selector(mgButtonClicked:) forControlEvents:UIControlEventTouchUpInside]; + } + } +} + +-(void) resetButtons +{ + CGFloat offsetX = 0; + UIView* lastButton = [_buttons lastObject]; + for (UIView * button in _buttons) { + button.frame = CGRectMake(offsetX, 0, button.bounds.size.width, self.bounds.size.height); + button.autoresizingMask = UIViewAutoresizingFlexibleHeight; + offsetX += button.bounds.size.width + (lastButton == button ? 0 : _buttonsDistance); + } +} + +-(void) layoutExpansion: (CGFloat) offset +{ + _expansionOffset = offset; + _container.frame = CGRectMake(_fromLeft ? 0: self.bounds.size.width - offset, 0, offset, self.bounds.size.height); + if (_expansionBackgroundAnimated && _expandedButtonAnimated) { + _expansionBackgroundAnimated.frame = [self expansionBackgroundRect:_expandedButtonAnimated]; + } +} + +-(void) layoutSubviews +{ + [super layoutSubviews]; + if (_expandedButton) { + [self layoutExpansion:_expansionOffset]; + } + else { + _container.frame = self.bounds; + } +} + +-(CGRect) expansionBackgroundRect: (UIView *) button +{ + CGFloat extra = 100.0f; //extra size to avoid expansion background size issue on iOS 7.0 + if (_fromLeft) { + return CGRectMake(-extra, 0, button.frame.origin.x + extra, _container.bounds.size.height); + } + else { + return CGRectMake(button.frame.origin.x + button.bounds.size.width, 0, + _container.bounds.size.width - (button.frame.origin.x + button.bounds.size.width ) + extra + ,_container.bounds.size.height); + } + +} + +-(void) expandToOffset:(CGFloat) offset settings:(MGSwipeExpansionSettings*) settings +{ + if (settings.buttonIndex < 0 || settings.buttonIndex >= _buttons.count) { + return; + } + if (!_expandedButton) { + _expandedButton = [_buttons objectAtIndex: _fromLeft ? settings.buttonIndex : _buttons.count - settings.buttonIndex - 1]; + CGRect previusRect = _container.frame; + [self layoutExpansion:offset]; + [self resetButtons]; + if (!_fromLeft) { //Fix expansion animation for right buttons + for (UIView * button in _buttons) { + CGRect frame = button.frame; + frame.origin.x += _container.bounds.size.width - previusRect.size.width; + button.frame = frame; + } + } + _expansionBackground = [[UIView alloc] initWithFrame:[self expansionBackgroundRect:_expandedButton]]; + _expansionBackground.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + if (settings.expansionColor) { + _backgroundColorCopy = _expandedButton.backgroundColor; + _expandedButton.backgroundColor = settings.expansionColor; + } + _expansionBackground.backgroundColor = _expandedButton.backgroundColor; + if (UIColor.clearColor == _expandedButton.backgroundColor) { + // Provides access to more complex content for display on the background + _expansionBackground.layer.contents = _expandedButton.layer.contents; + } + [_container addSubview:_expansionBackground]; + _expansionLayout = settings.expansionLayout; + + CGFloat duration = _fromLeft ? _cell.leftExpansion.animationDuration : _cell.rightExpansion.animationDuration; + [UIView animateWithDuration: duration delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + _expandedButton.hidden = NO; + + if (_expansionLayout == MGSwipeExpansionLayoutCenter) { + _expandedButtonBoundsCopy = _expandedButton.bounds; + _expandedButton.layer.mask = nil; + _expandedButton.layer.transform = CATransform3DIdentity; + _expandedButton.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; + [_expandedButton.superview bringSubviewToFront:_expandedButton]; + _expandedButton.frame = _container.bounds; + _expansionBackground.frame = [self expansionBackgroundRect:_expandedButton]; + } + else if (_expansionLayout == MGSwipeExpansionLayoutNone) { + [_expandedButton.superview bringSubviewToFront:_expandedButton]; + _expansionBackground.frame = _container.bounds; + } + else if (_fromLeft) { + _expandedButton.frame = CGRectMake(_container.bounds.size.width - _expandedButton.bounds.size.width, 0, _expandedButton.bounds.size.width, _expandedButton.bounds.size.height); + _expandedButton.autoresizingMask|= UIViewAutoresizingFlexibleLeftMargin; + _expansionBackground.frame = [self expansionBackgroundRect:_expandedButton]; + } + else { + _expandedButton.frame = CGRectMake(0, 0, _expandedButton.bounds.size.width, _expandedButton.bounds.size.height); + _expandedButton.autoresizingMask|= UIViewAutoresizingFlexibleRightMargin; + _expansionBackground.frame = [self expansionBackgroundRect:_expandedButton]; + } + + } completion:^(BOOL finished) { + }]; + return; + } + [self layoutExpansion:offset]; +} + +-(void) endExpansionAnimated:(BOOL) animated +{ + if (_expandedButton) { + _expandedButtonAnimated = _expandedButton; + if (_expansionBackgroundAnimated && _expansionBackgroundAnimated != _expansionBackground) { + [_expansionBackgroundAnimated removeFromSuperview]; + } + _expansionBackgroundAnimated = _expansionBackground; + _expansionBackground = nil; + _expandedButton = nil; + if (_backgroundColorCopy) { + _expansionBackgroundAnimated.backgroundColor = _backgroundColorCopy; + _expandedButtonAnimated.backgroundColor = _backgroundColorCopy; + _backgroundColorCopy = nil; + } + CGFloat duration = _fromLeft ? _cell.leftExpansion.animationDuration : _cell.rightExpansion.animationDuration; + [UIView animateWithDuration: animated ? duration : 0.0 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ + _container.frame = self.bounds; + if (_expansionLayout == MGSwipeExpansionLayoutCenter) { + _expandedButtonAnimated.frame = _expandedButtonBoundsCopy; + } + [self resetButtons]; + _expansionBackgroundAnimated.frame = [self expansionBackgroundRect:_expandedButtonAnimated]; + } completion:^(BOOL finished) { + [_expansionBackgroundAnimated removeFromSuperview]; + }]; + } + else if (_expansionBackground) { + [_expansionBackground removeFromSuperview]; + _expansionBackground = nil; + } +} + +-(UIView*) getExpandedButton +{ + return _expandedButton; +} + +#pragma mark Trigger Actions + +-(BOOL) handleClick: (id) sender fromExpansion:(BOOL) fromExpansion +{ + bool autoHide = false; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wundeclared-selector" + if ([sender respondsToSelector:@selector(callMGSwipeConvenienceCallback:)]) { + //call convenience block callback if exits (usage of MGSwipeButton class is not compulsory) + autoHide = [sender performSelector:@selector(callMGSwipeConvenienceCallback:) withObject:_cell]; + } +#pragma clang diagnostic pop + + if (_cell.delegate && [_cell.delegate respondsToSelector:@selector(swipeTableCell:tappedButtonAtIndex:direction:fromExpansion:)]) { + NSInteger index = [_buttons indexOfObject:sender]; + if (!_fromLeft) { + index = _buttons.count - index - 1; //right buttons are reversed + } + autoHide|= [_cell.delegate swipeTableCell:_cell tappedButtonAtIndex:index direction:_fromLeft ? MGSwipeDirectionLeftToRight : MGSwipeDirectionRightToLeft fromExpansion:fromExpansion]; + } + + if (fromExpansion && autoHide) { + _expandedButton = nil; + _cell.swipeOffset = 0; + } + else if (autoHide) { + [_cell hideSwipeAnimated:YES]; + } + + return autoHide; + +} +//button listener +-(void) mgButtonClicked: (id) sender +{ + [self handleClick:sender fromExpansion:NO]; +} + + +#pragma mark Transitions + +-(void) transitionStatic:(CGFloat) t +{ + const CGFloat dx = self.bounds.size.width * (1.0 - t); + CGFloat offsetX = 0; + + UIView* lastButton = [_buttons lastObject]; + for (UIView *button in _buttons) { + CGRect frame = button.frame; + frame.origin.x = offsetX + (_fromLeft ? dx : -dx); + button.frame = frame; + offsetX += frame.size.width + (button == lastButton ? 0 : _buttonsDistance); + } +} + +-(void) transitionDrag:(CGFloat) t +{ + //No Op, nothing to do ;) +} + +-(void) transitionClip:(CGFloat) t +{ + CGFloat selfWidth = self.bounds.size.width; + CGFloat offsetX = 0; + + UIView* lastButton = [_buttons lastObject]; + for (UIView *button in _buttons) { + CGRect frame = button.frame; + CGFloat dx = roundf(frame.size.width * 0.5 * (1.0 - t)) ; + frame.origin.x = _fromLeft ? (selfWidth - frame.size.width - offsetX) * (1.0 - t) + offsetX + dx : offsetX * t - dx; + button.frame = frame; + + if (_buttons.count > 1) { + CAShapeLayer *maskLayer = [CAShapeLayer new]; + CGRect maskRect = CGRectMake(dx - 0.5, 0, frame.size.width - 2 * dx + 1.5, frame.size.height); + CGPathRef path = CGPathCreateWithRect(maskRect, NULL); + maskLayer.path = path; + CGPathRelease(path); + button.layer.mask = maskLayer; + } + + offsetX += frame.size.width + (button == lastButton ? 0 : _buttonsDistance); + } +} + +-(void) transtitionFloatBorder:(CGFloat) t +{ + CGFloat selfWidth = self.bounds.size.width; + CGFloat offsetX = 0; + + UIView* lastButton = [_buttons lastObject]; + for (UIView *button in _buttons) { + CGRect frame = button.frame; + frame.origin.x = _fromLeft ? (selfWidth - frame.size.width - offsetX) * (1.0 - t) + offsetX : offsetX * t; + button.frame = frame; + offsetX += frame.size.width + (button == lastButton ? 0 : _buttonsDistance); + } +} + +-(void) transition3D:(CGFloat) t +{ + const CGFloat invert = _fromLeft ? 1.0 : -1.0; + const CGFloat angle = M_PI_2 * (1.0 - t) * invert; + CATransform3D transform = CATransform3DIdentity; + transform.m34 = -1.0/400.0f; //perspective 1/z + const CGFloat dx = -_container.bounds.size.width * 0.5 * invert; + const CGFloat offset = dx * 2 * (1.0 - t); + transform = CATransform3DTranslate(transform, dx - offset, 0, 0); + transform = CATransform3DRotate(transform, angle, 0.0, 1.0, 0.0); + transform = CATransform3DTranslate(transform, -dx, 0, 0); + _container.layer.transform = transform; +} + +-(void) transition:(MGSwipeTransition) mode percent:(CGFloat) t +{ + switch (mode) { + case MGSwipeTransitionStatic: [self transitionStatic:t]; break; + case MGSwipeTransitionDrag: [self transitionDrag:t]; break; + case MGSwipeTransitionClipCenter: [self transitionClip:t]; break; + case MGSwipeTransitionBorder: [self transtitionFloatBorder:t]; break; + case MGSwipeTransitionRotate3D: [self transition3D:t]; break; + } + if (_expandedButtonAnimated && _expansionBackgroundAnimated) { + _expansionBackgroundAnimated.frame = [self expansionBackgroundRect:_expandedButtonAnimated]; + } +} + +@end + +#pragma mark Settings Classes +@implementation MGSwipeSettings +-(instancetype) init +{ + if (self = [super init]) { + self.transition = MGSwipeTransitionBorder; + self.threshold = 0.5; + self.offset = 0; + self.keepButtonsSwiped = YES; + self.enableSwipeBounces = YES; + self.swipeBounceRate = 1.0; + self.showAnimation = [[MGSwipeAnimation alloc] init]; + self.hideAnimation = [[MGSwipeAnimation alloc] init]; + self.stretchAnimation = [[MGSwipeAnimation alloc] init]; + } + return self; +} + +-(void) setAnimationDuration:(CGFloat)duration +{ + _showAnimation.duration = duration; + _hideAnimation.duration = duration; + _stretchAnimation.duration = duration; +} + +-(CGFloat) animationDuration { + return _showAnimation.duration; +} + +@end + +@implementation MGSwipeExpansionSettings +-(instancetype) init +{ + if (self = [super init]) { + self.buttonIndex = -1; + self.threshold = 1.3; + self.animationDuration = 0.2; + self.triggerAnimation = [[MGSwipeAnimation alloc] init]; + } + return self; +} +@end + +@interface MGSwipeAnimationData : NSObject +@property (nonatomic, assign) CGFloat from; +@property (nonatomic, assign) CGFloat to; +@property (nonatomic, assign) CFTimeInterval duration; +@property (nonatomic, assign) CFTimeInterval start; +@property (nonatomic, strong) MGSwipeAnimation * animation; + +@end + +@implementation MGSwipeAnimationData +@end + + +#pragma mark Easing Functions and MGSwipeAnimation + +static inline CGFloat mgEaseLinear(CGFloat t, CGFloat b, CGFloat c) { + return c*t + b; +} + +static inline CGFloat mgEaseInQuad(CGFloat t, CGFloat b, CGFloat c) { + return c*t*t + b; +} +static inline CGFloat mgEaseOutQuad(CGFloat t, CGFloat b, CGFloat c) { + return -c*t*(t-2) + b; +} +static inline CGFloat mgEaseInOutQuad(CGFloat t, CGFloat b, CGFloat c) { + if ((t*=2) < 1) return c/2*t*t + b; + --t; + return -c/2 * (t*(t-2) - 1) + b; +} +static inline CGFloat mgEaseInCubic(CGFloat t, CGFloat b, CGFloat c) { + return c*t*t*t + b; +} +static inline CGFloat mgEaseOutCubic(CGFloat t, CGFloat b, CGFloat c) { + --t; + return c*(t*t*t + 1) + b; +} +static inline CGFloat mgEaseInOutCubic(CGFloat t, CGFloat b, CGFloat c) { + if ((t*=2) < 1) return c/2*t*t*t + b; + t-=2; + return c/2*(t*t*t + 2) + b; +} +static inline CGFloat mgEaseOutBounce(CGFloat t, CGFloat b, CGFloat c) { + if (t < (1/2.75)) { + return c*(7.5625*t*t) + b; + } else if (t < (2/2.75)) { + t-=(1.5/2.75); + return c*(7.5625*t*t + .75) + b; + } else if (t < (2.5/2.75)) { + t-=(2.25/2.75); + return c*(7.5625*t*t + .9375) + b; + } else { + t-=(2.625/2.75); + return c*(7.5625*t*t + .984375) + b; + } +}; +static inline CGFloat mgEaseInBounce(CGFloat t, CGFloat b, CGFloat c) { + return c - mgEaseOutBounce (1.0 -t, 0, c) + b; +}; + +static inline CGFloat mgEaseInOutBounce(CGFloat t, CGFloat b, CGFloat c) { + if (t < 0.5) return mgEaseInBounce (t*2, 0, c) * .5 + b; + return mgEaseOutBounce (1.0 - t*2, 0, c) * .5 + c*.5 + b; +}; + +@implementation MGSwipeAnimation + +-(instancetype) init { + if (self = [super init]) { + _duration = 0.3; + _easingFunction = MGSwipeEasingFunctionCubicOut; + } + return self; +} + +-(CGFloat) value:(CGFloat)elapsed duration:(CGFloat)duration from:(CGFloat)from to:(CGFloat)to +{ + CGFloat t = MIN(elapsed/duration, 1.0f); + if (t == 1.0) { + return to; //precise last value + } + CGFloat (*easingFunction)(CGFloat t, CGFloat b, CGFloat c) = 0; + switch (_easingFunction) { + case MGSwipeEasingFunctionLinear: easingFunction = mgEaseLinear;break; + case MGSwipeEasingFunctionQuadIn: easingFunction = mgEaseInQuad;break; + case MGSwipeEasingFunctionQuadOut: easingFunction = mgEaseOutQuad;break; + case MGSwipeEasingFunctionQuadInOut: easingFunction = mgEaseInOutQuad;break; + case MGSwipeEasingFunctionCubicIn: easingFunction = mgEaseInCubic;break; + default: + case MGSwipeEasingFunctionCubicOut: easingFunction = mgEaseOutCubic;break; + case MGSwipeEasingFunctionCubicInOut: easingFunction = mgEaseInOutCubic;break; + case MGSwipeEasingFunctionBounceIn: easingFunction = mgEaseInBounce;break; + case MGSwipeEasingFunctionBounceOut: easingFunction = mgEaseOutBounce;break; + case MGSwipeEasingFunctionBounceInOut: easingFunction = mgEaseInOutBounce;break; + } + return (*easingFunction)(t, from, to - from); +} + +@end + +#pragma mark MGSwipeTableCell Implementation + + +@implementation MGSwipeTableCell +{ + UITapGestureRecognizer * _tapRecognizer; + UIPanGestureRecognizer * _panRecognizer; + CGPoint _panStartPoint; + CGFloat _panStartOffset; + CGFloat _targetOffset; + + UIView * _swipeOverlay; + UIImageView * _swipeView; + UIView * _swipeContentView; + MGSwipeButtonsView * _leftView; + MGSwipeButtonsView * _rightView; + bool _allowSwipeRightToLeft; + bool _allowSwipeLeftToRight; + __weak MGSwipeButtonsView * _activeExpansion; + + MGSwipeTableInputOverlay * _tableInputOverlay; + bool _overlayEnabled; + __weak UITableView * _cachedParentTable; + UITableViewCellSelectionStyle _previusSelectionStyle; + NSMutableSet * _previusHiddenViews; + BOOL _triggerStateChanges; + + MGSwipeAnimationData * _animationData; + void (^_animationCompletion)(BOOL finished); + CADisplayLink * _displayLink; + MGSwipeState _firstSwipeState; +} + +#pragma mark View creation & layout + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier +{ + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + [self initViews:YES]; + } + return self; +} + +- (id)initWithCoder:(NSCoder*)aDecoder +{ + if(self = [super initWithCoder:aDecoder]) { + [self initViews:YES]; + } + return self; +} + +-(void) awakeFromNib +{ + [super awakeFromNib]; + if (!_panRecognizer) { + [self initViews:YES]; + } +} + +-(void) dealloc +{ + [self hideSwipeOverlayIfNeeded]; +} + +-(void) initViews: (BOOL) cleanButtons +{ + if (cleanButtons) { + _leftButtons = [NSArray array]; + _rightButtons = [NSArray array]; + _leftSwipeSettings = [[MGSwipeSettings alloc] init]; + _rightSwipeSettings = [[MGSwipeSettings alloc] init]; + _leftExpansion = [[MGSwipeExpansionSettings alloc] init]; + _rightExpansion = [[MGSwipeExpansionSettings alloc] init]; + } + _animationData = [[MGSwipeAnimationData alloc] init]; + _panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panHandler:)]; + [self addGestureRecognizer:_panRecognizer]; + _panRecognizer.delegate = self; + _activeExpansion = nil; + _previusHiddenViews = [NSMutableSet set]; + _swipeState = MGSwipeStateNone; + _triggerStateChanges = YES; + _allowsSwipeWhenTappingButtons = YES; + _preservesSelectionStatus = NO; + _allowsOppositeSwipe = YES; + _firstSwipeState = MGSwipeStateNone; + +} + +-(void) cleanViews +{ + [self hideSwipeAnimated:NO]; + if (_displayLink) { + [_displayLink invalidate]; + _displayLink = nil; + } + if (_swipeOverlay) { + [_swipeOverlay removeFromSuperview]; + _swipeOverlay = nil; + } + _leftView = _rightView = nil; + if (_panRecognizer) { + _panRecognizer.delegate = nil; + [self removeGestureRecognizer:_panRecognizer]; + _panRecognizer = nil; + } +} + +-(BOOL) isRTLLocale +{ + if ([[UIView class] respondsToSelector:@selector(userInterfaceLayoutDirectionForSemanticContentAttribute:)]) { + return [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:self.semanticContentAttribute] == UIUserInterfaceLayoutDirectionRightToLeft; + } +#ifndef TARGET_IS_EXTENSION + else { + return [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; + } +#else + return NO; +#endif +} + +-(void) fixRegionAndAccesoryViews +{ + //Fix right to left layout direction for arabic and hebrew languagues + if (self.bounds.size.width != self.contentView.bounds.size.width && [self isRTLLocale]) { + _swipeOverlay.frame = CGRectMake(-self.bounds.size.width + self.contentView.bounds.size.width, 0, _swipeOverlay.bounds.size.width, _swipeOverlay.bounds.size.height); + } +} + +-(UIView *) swipeContentView +{ + if (!_swipeContentView) { + _swipeContentView = [[UIView alloc] initWithFrame:self.contentView.bounds]; + _swipeContentView.backgroundColor = [UIColor clearColor]; + _swipeContentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _swipeContentView.layer.zPosition = 9; + [self.contentView addSubview:_swipeContentView]; + } + return _swipeContentView; +} + +-(void) layoutSubviews +{ + [super layoutSubviews]; + if (_swipeContentView) { + _swipeContentView.frame = self.contentView.bounds; + } + if (_swipeOverlay) { + CGSize prevSize = _swipeView.bounds.size; + _swipeOverlay.frame = CGRectMake(0, 0, self.bounds.size.width, self.contentView.bounds.size.height); + [self fixRegionAndAccesoryViews]; + if (_swipeView.image && !CGSizeEqualToSize(prevSize, _swipeOverlay.bounds.size)) { + //refresh contentView in situations like layout change, orientation chage, table resize, etc. + [self refreshContentView]; + } + } +} + +-(void) fetchButtonsIfNeeded +{ + if (_leftButtons.count == 0 && _delegate && [_delegate respondsToSelector:@selector(swipeTableCell:swipeButtonsForDirection:swipeSettings:expansionSettings:)]) { + _leftButtons = [_delegate swipeTableCell:self swipeButtonsForDirection:MGSwipeDirectionLeftToRight swipeSettings:_leftSwipeSettings expansionSettings:_leftExpansion]; + } + if (_rightButtons.count == 0 && _delegate && [_delegate respondsToSelector:@selector(swipeTableCell:swipeButtonsForDirection:swipeSettings:expansionSettings:)]) { + _rightButtons = [_delegate swipeTableCell:self swipeButtonsForDirection:MGSwipeDirectionRightToLeft swipeSettings:_rightSwipeSettings expansionSettings:_rightExpansion]; + } +} + +-(void) createSwipeViewIfNeeded +{ + if (!_swipeOverlay) { + _swipeOverlay = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.contentView.bounds.size.height)]; + [self fixRegionAndAccesoryViews]; + _swipeOverlay.hidden = YES; + _swipeOverlay.backgroundColor = [self backgroundColorForSwipe]; + _swipeOverlay.layer.zPosition = 10; //force render on top of the contentView; + _swipeView = [[UIImageView alloc] initWithFrame:_swipeOverlay.bounds]; + _swipeView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _swipeView.contentMode = UIViewContentModeCenter; + _swipeView.clipsToBounds = YES; + [_swipeOverlay addSubview:_swipeView]; + [self.contentView addSubview:_swipeOverlay]; + } + + [self fetchButtonsIfNeeded]; + if (!_leftView && _leftButtons.count > 0) { + _leftView = [[MGSwipeButtonsView alloc] initWithButtons:_leftButtons direction:MGSwipeDirectionLeftToRight differentWidth:_allowsButtonsWithDifferentWidth buttonsDistance:_leftSwipeSettings.buttonsDistance]; + _leftView.cell = self; + _leftView.frame = CGRectMake(-_leftView.bounds.size.width, _leftSwipeSettings.topMargin, _leftView.bounds.size.width, _swipeOverlay.bounds.size.height - _leftSwipeSettings.topMargin - _leftSwipeSettings.bottomMargin); + _leftView.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight; + [_swipeOverlay addSubview:_leftView]; + } + if (!_rightView && _rightButtons.count > 0) { + _rightView = [[MGSwipeButtonsView alloc] initWithButtons:_rightButtons direction:MGSwipeDirectionRightToLeft differentWidth:_allowsButtonsWithDifferentWidth buttonsDistance:_rightSwipeSettings.buttonsDistance]; + _rightView.cell = self; + _rightView.frame = CGRectMake(_swipeOverlay.bounds.size.width, _rightSwipeSettings.topMargin, _rightView.bounds.size.width, _swipeOverlay.bounds.size.height - _rightSwipeSettings.topMargin - _rightSwipeSettings.bottomMargin); + _rightView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight; + [_swipeOverlay addSubview:_rightView]; + } +} + + +- (void) showSwipeOverlayIfNeeded +{ + if (_overlayEnabled) { + return; + } + _overlayEnabled = YES; + + if (!_preservesSelectionStatus) + self.selected = NO; + if (_swipeContentView) + [_swipeContentView removeFromSuperview]; + if (_delegate && [_delegate respondsToSelector:@selector(swipeTableCellWillBeginSwiping:)]) { + [_delegate swipeTableCellWillBeginSwiping:self]; + } + + // snapshot cell without separator + CGSize cropSize = CGSizeMake(self.bounds.size.width, self.contentView.bounds.size.height); + _swipeView.image = [self imageFromView:self cropSize:cropSize]; + + _swipeOverlay.hidden = NO; + if (_swipeContentView) + [_swipeView addSubview:_swipeContentView]; + + if (!_allowsMultipleSwipe) { + //input overlay on the whole table + UITableView * table = [self parentTable]; + if (_tableInputOverlay) { + [_tableInputOverlay removeFromSuperview]; + } + _tableInputOverlay = [[MGSwipeTableInputOverlay alloc] initWithFrame:table.bounds]; + _tableInputOverlay.currentCell = self; + [table addSubview:_tableInputOverlay]; + } + + _previusSelectionStyle = self.selectionStyle; + self.selectionStyle = UITableViewCellSelectionStyleNone; + [self setAccesoryViewsHidden:YES]; + + _tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapHandler:)]; + _tapRecognizer.cancelsTouchesInView = YES; + _tapRecognizer.delegate = self; + [self addGestureRecognizer:_tapRecognizer]; +} + +-(void) hideSwipeOverlayIfNeeded +{ + if (!_overlayEnabled) { + return; + } + _overlayEnabled = NO; + _swipeOverlay.hidden = YES; + _swipeView.image = nil; + if (_swipeContentView) { + [_swipeContentView removeFromSuperview]; + [self.contentView addSubview:_swipeContentView]; + } + + if (_tableInputOverlay) { + [_tableInputOverlay removeFromSuperview]; + _tableInputOverlay = nil; + } + + self.selectionStyle = _previusSelectionStyle; + NSArray * selectedRows = self.parentTable.indexPathsForSelectedRows; + if ([selectedRows containsObject:[self.parentTable indexPathForCell:self]]) { + self.selected = NO; //Hack: in some iOS versions setting the selected property to YES own isn't enough to force the cell to redraw the chosen selectionStyle + self.selected = YES; + } + [self setAccesoryViewsHidden:NO]; + + if (_delegate && [_delegate respondsToSelector:@selector(swipeTableCellWillEndSwiping:)]) { + [_delegate swipeTableCellWillEndSwiping:self]; + } + + if (_tapRecognizer) { + [self removeGestureRecognizer:_tapRecognizer]; + _tapRecognizer = nil; + } +} + +-(void) refreshContentView +{ + CGFloat currentOffset = _swipeOffset; + BOOL prevValue = _triggerStateChanges; + _triggerStateChanges = NO; + self.swipeOffset = 0; + self.swipeOffset = currentOffset; + _triggerStateChanges = prevValue; +} + +-(void) refreshButtons: (BOOL) usingDelegate +{ + if (usingDelegate) { + self.leftButtons = @[]; + self.rightButtons = @[]; + } + if (_leftView) { + [_leftView removeFromSuperview]; + _leftView = nil; + } + if (_rightView) { + [_rightView removeFromSuperview]; + _rightView = nil; + } + [self createSwipeViewIfNeeded]; + [self refreshContentView]; +} + +#pragma mark Handle Table Events + +-(void) willMoveToSuperview:(UIView *)newSuperview; +{ + if (newSuperview == nil) { //remove the table overlay when a cell is removed from the table + [self hideSwipeOverlayIfNeeded]; + } +} + +-(void) prepareForReuse +{ + [super prepareForReuse]; + [self cleanViews]; + if (_swipeState != MGSwipeStateNone) { + _triggerStateChanges = YES; + [self updateState:MGSwipeStateNone]; + } + BOOL cleanButtons = _delegate && [_delegate respondsToSelector:@selector(swipeTableCell:swipeButtonsForDirection:swipeSettings:expansionSettings:)]; + [self initViews:cleanButtons]; +} + +-(void) setEditing:(BOOL)editing animated:(BOOL)animated +{ + [super setEditing:editing animated:animated]; + if (editing) { //disable swipe buttons when the user sets table editing mode + self.swipeOffset = 0; + } +} + +-(void) setEditing:(BOOL)editing +{ + [super setEditing:YES]; + if (editing) { //disable swipe buttons when the user sets table editing mode + self.swipeOffset = 0; + } +} + +-(UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event +{ + if (!self.hidden && _swipeOverlay && !_swipeOverlay.hidden) { + //override hitTest to give swipe buttons a higher priority (diclosure buttons can steal input) + UIView * targets[] = {_leftView, _rightView}; + for (int i = 0; i< 2; ++i) { + UIView * target = targets[i]; + if (!target) continue; + + CGPoint p = [self convertPoint:point toView:target]; + if (CGRectContainsPoint(target.bounds, p)) { + return [target hitTest:p withEvent:event]; + } + } + } + return [super hitTest:point withEvent:event]; +} + +#pragma mark Some utility methods + +- (UIImage *)imageFromView:(UIView *)view cropSize:(CGSize)cropSize{ + UIGraphicsBeginImageContextWithOptions(cropSize, NO, [[UIScreen mainScreen] scale]); + [view.layer renderInContext:UIGraphicsGetCurrentContext()]; + UIImage * image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +-(void) setAccesoryViewsHidden: (BOOL) hidden +{ + if (self.accessoryView) { + self.accessoryView.hidden = hidden; + } + for (UIView * view in self.contentView.superview.subviews) { + if (view != self.contentView && ([view isKindOfClass:[UIButton class]] || [NSStringFromClass(view.class) rangeOfString:@"Disclosure"].location != NSNotFound)) { + view.hidden = hidden; + } + } + + for (UIView * view in self.contentView.subviews) { + if (view == _swipeOverlay || view == _swipeContentView) continue; + if (hidden && !view.hidden) { + view.hidden = YES; + [_previusHiddenViews addObject:view]; + } + else if (!hidden && [_previusHiddenViews containsObject:view]) { + view.hidden = NO; + } + } + + if (!hidden) { + [_previusHiddenViews removeAllObjects]; + } +} + +-(UIColor *) backgroundColorForSwipe +{ + if (_swipeBackgroundColor) { + return _swipeBackgroundColor; //user defined color + } + else if (self.contentView.backgroundColor && ![self.contentView.backgroundColor isEqual:[UIColor clearColor]]) { + return self.contentView.backgroundColor; + } + else if (self.backgroundColor) { + return self.backgroundColor; + } + return [UIColor clearColor]; +} + +-(UITableView *) parentTable +{ + if (_cachedParentTable) { + return _cachedParentTable; + } + + UIView * view = self.superview; + while(view != nil) { + if([view isKindOfClass:[UITableView class]]) { + _cachedParentTable = (UITableView*) view; + } + view = view.superview; + } + return _cachedParentTable; +} + +-(void) updateState: (MGSwipeState) newState; +{ + if (!_triggerStateChanges || _swipeState == newState) { + return; + } + _swipeState = newState; + if (_delegate && [_delegate respondsToSelector:@selector(swipeTableCell:didChangeSwipeState:gestureIsActive:)]) { + [_delegate swipeTableCell:self didChangeSwipeState:_swipeState gestureIsActive: self.isSwipeGestureActive] ; + } +} + +#pragma mark Swipe Animation + +- (void)setSwipeOffset:(CGFloat) newOffset; +{ + CGFloat sign = newOffset > 0 ? 1.0 : -1.0; + MGSwipeButtonsView * activeButtons = sign < 0 ? _rightView : _leftView; + MGSwipeSettings * activeSettings = sign < 0 ? _rightSwipeSettings : _leftSwipeSettings; + + if(activeSettings.enableSwipeBounces) { + _swipeOffset = newOffset; + + CGFloat maxUnbouncedOffset = sign * activeButtons.bounds.size.width; + + if ((sign > 0 && newOffset > maxUnbouncedOffset) || (sign < 0 && newOffset < maxUnbouncedOffset)) { + _swipeOffset = maxUnbouncedOffset + (newOffset - maxUnbouncedOffset) * activeSettings.swipeBounceRate; + } + } + else { + CGFloat maxOffset = sign * activeButtons.bounds.size.width; + _swipeOffset = sign > 0 ? MIN(newOffset, maxOffset) : MAX(newOffset, maxOffset); + } + CGFloat offset = fabs(_swipeOffset); + + + if (!activeButtons || offset == 0) { + if (_leftView) + [_leftView endExpansionAnimated:NO]; + if (_rightView) + [_rightView endExpansionAnimated:NO]; + [self hideSwipeOverlayIfNeeded]; + _targetOffset = 0; + [self updateState:MGSwipeStateNone]; + return; + } + else { + [self showSwipeOverlayIfNeeded]; + CGFloat swipeThreshold = activeSettings.threshold; + BOOL keepButtons = activeSettings.keepButtonsSwiped; + _targetOffset = keepButtons && offset > activeButtons.bounds.size.width * swipeThreshold ? activeButtons.bounds.size.width * sign : 0; + } + + BOOL onlyButtons = activeSettings.onlySwipeButtons; + _swipeView.transform = CGAffineTransformMakeTranslation(onlyButtons ? 0 : _swipeOffset, 0); + + //animate existing buttons + MGSwipeButtonsView* but[2] = {_leftView, _rightView}; + MGSwipeSettings* settings[2] = {_leftSwipeSettings, _rightSwipeSettings}; + MGSwipeExpansionSettings * expansions[2] = {_leftExpansion, _rightExpansion}; + + for (int i = 0; i< 2; ++i) { + MGSwipeButtonsView * view = but[i]; + if (!view) continue; + + //buttons view position + CGFloat translation = MIN(offset, view.bounds.size.width) * sign + settings[i].offset * sign; + view.transform = CGAffineTransformMakeTranslation(translation, 0); + + if (view != activeButtons) continue; //only transition if active (perf. improvement) + bool expand = expansions[i].buttonIndex >= 0 && offset > view.bounds.size.width * expansions[i].threshold; + if (expand) { + [view expandToOffset:offset settings:expansions[i]]; + _targetOffset = expansions[i].fillOnTrigger ? self.bounds.size.width * sign : 0; + _activeExpansion = view; + [self updateState:i ? MGSwipeStateExpandingRightToLeft : MGSwipeStateExpandingLeftToRight]; + } + else { + [view endExpansionAnimated:YES]; + _activeExpansion = nil; + CGFloat t = MIN(1.0f, offset/view.bounds.size.width); + [view transition:settings[i].transition percent:t]; + [self updateState:i ? MGSwipeStateSwipingRightToLeft : MGSwipeStateSwipingLeftToRight]; + } + } +} + +-(void) hideSwipeAnimated: (BOOL) animated completion:(void(^)(BOOL finished)) completion +{ + MGSwipeAnimation * animation = animated ? (_swipeOffset > 0 ? _leftSwipeSettings.hideAnimation: _rightSwipeSettings.hideAnimation) : nil; + [self setSwipeOffset:0 animation:animation completion:completion]; +} + +-(void) hideSwipeAnimated: (BOOL) animated +{ + [self hideSwipeAnimated:animated completion:nil]; +} + +-(void) showSwipe: (MGSwipeDirection) direction animated: (BOOL) animated +{ + [self showSwipe:direction animated:animated completion:nil]; +} + +-(void) showSwipe: (MGSwipeDirection) direction animated: (BOOL) animated completion:(void(^)(BOOL finished)) completion +{ + [self createSwipeViewIfNeeded]; + _allowSwipeLeftToRight = _leftButtons.count > 0; + _allowSwipeRightToLeft = _rightButtons.count > 0; + UIView * buttonsView = direction == MGSwipeDirectionLeftToRight ? _leftView : _rightView; + + if (buttonsView) { + CGFloat s = direction == MGSwipeDirectionLeftToRight ? 1.0 : -1.0; + MGSwipeAnimation * animation = animated ? (direction == MGSwipeDirectionLeftToRight ? _leftSwipeSettings.showAnimation : _rightSwipeSettings.showAnimation) : nil; + [self setSwipeOffset:buttonsView.bounds.size.width * s animation:animation completion:completion]; + } +} + +-(void) expandSwipe: (MGSwipeDirection) direction animated: (BOOL) animated +{ + CGFloat s = direction == MGSwipeDirectionLeftToRight ? 1.0 : -1.0; + MGSwipeExpansionSettings* expSetting = direction == MGSwipeDirectionLeftToRight ? _leftExpansion : _rightExpansion; + + // only perform animation if there's no pending expansion animation and requested direction has fillOnTrigger enabled + if(!_activeExpansion && expSetting.fillOnTrigger) { + [self createSwipeViewIfNeeded]; + _allowSwipeLeftToRight = _leftButtons.count > 0; + _allowSwipeRightToLeft = _rightButtons.count > 0; + UIView * buttonsView = direction == MGSwipeDirectionLeftToRight ? _leftView : _rightView; + + if (buttonsView) { + __weak MGSwipeButtonsView * expansionView = direction == MGSwipeDirectionLeftToRight ? _leftView : _rightView; + __weak MGSwipeTableCell * weakself = self; + [self setSwipeOffset:buttonsView.bounds.size.width * s * expSetting.threshold * 2 animation:expSetting.triggerAnimation completion:^(BOOL finished){ + [expansionView endExpansionAnimated:YES]; + [weakself setSwipeOffset:0 animated:NO completion:nil]; + }]; + } + } +} + +-(void) animationTick: (CADisplayLink *) timer +{ + if (!_animationData.start) { + _animationData.start = timer.timestamp; + } + CFTimeInterval elapsed = timer.timestamp - _animationData.start; + bool completed = elapsed >= _animationData.duration; + if (completed) { + _triggerStateChanges = YES; + } + self.swipeOffset = [_animationData.animation value:elapsed duration:_animationData.duration from:_animationData.from to:_animationData.to]; + + //call animation completion and invalidate timer + if (completed){ + [timer invalidate]; + [self invalidateDisplayLink]; + } +} + +-(void)invalidateDisplayLink { + [_displayLink invalidate]; + _displayLink = nil; + if (_animationCompletion) { + void (^callbackCopy)(BOOL finished) = _animationCompletion; //copy to avoid duplicated callbacks + _animationCompletion = nil; + callbackCopy(YES); + } +} + +-(void) setSwipeOffset:(CGFloat)offset animated: (BOOL) animated completion:(void(^)(BOOL finished)) completion +{ + MGSwipeAnimation * animation = animated ? [[MGSwipeAnimation alloc] init] : nil; + [self setSwipeOffset:offset animation:animation completion:completion]; +} + +-(void) setSwipeOffset:(CGFloat)offset animation: (MGSwipeAnimation *) animation completion:(void(^)(BOOL finished)) completion +{ + if (_displayLink) { + [_displayLink invalidate]; + _displayLink = nil; + } + if (_animationCompletion) { //notify previous animation cancelled + void (^callbackCopy)(BOOL finished) = _animationCompletion; //copy to avoid duplicated callbacks + _animationCompletion = nil; + callbackCopy(NO); + } + if (offset !=0) { + [self createSwipeViewIfNeeded]; + } + + if (!animation) { + self.swipeOffset = offset; + if (completion) { + completion(YES); + } + return; + } + + _animationCompletion = completion; + _triggerStateChanges = NO; + _animationData.from = _swipeOffset; + _animationData.to = offset; + _animationData.duration = animation.duration; + _animationData.start = 0; + _animationData.animation = animation; + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationTick:)]; + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; +} + +#pragma mark Gestures + +-(void) cancelPanGesture +{ + if (_panRecognizer.state != UIGestureRecognizerStateEnded && _panRecognizer.state != UIGestureRecognizerStatePossible) { + _panRecognizer.enabled = NO; + _panRecognizer.enabled = YES; + if (self.swipeOffset) { + [self hideSwipeAnimated:YES]; + } + } +} + +-(void) tapHandler: (UITapGestureRecognizer *) recognizer +{ + BOOL hide = YES; + if (_delegate && [_delegate respondsToSelector:@selector(swipeTableCell:shouldHideSwipeOnTap:)]) { + hide = [_delegate swipeTableCell:self shouldHideSwipeOnTap:[recognizer locationInView:self]]; + } + if (hide) { + [self hideSwipeAnimated:YES]; + } +} + +-(CGFloat) filterSwipe: (CGFloat) offset +{ + bool allowed = offset > 0 ? _allowSwipeLeftToRight : _allowSwipeRightToLeft; + UIView * buttons = offset > 0 ? _leftView : _rightView; + if (!buttons || ! allowed) { + offset = 0; + } + else if (!_allowsOppositeSwipe && _firstSwipeState == MGSwipeStateSwipingLeftToRight && offset < 0) { + offset = 0; + } + else if (!_allowsOppositeSwipe && _firstSwipeState == MGSwipeStateSwipingRightToLeft && offset > 0 ) { + offset = 0; + } + return offset; +} + +-(void) panHandler: (UIPanGestureRecognizer *)gesture +{ + CGPoint current = [gesture translationInView:self]; + + if (gesture.state == UIGestureRecognizerStateBegan) { + [self invalidateDisplayLink]; + + if (!_preservesSelectionStatus) + self.highlighted = NO; + [self createSwipeViewIfNeeded]; + _panStartPoint = current; + _panStartOffset = _swipeOffset; + if (_swipeOffset != 0) { + _firstSwipeState = _swipeOffset > 0 ? MGSwipeStateSwipingLeftToRight : MGSwipeStateSwipingRightToLeft; + } + + if (!_allowsMultipleSwipe) { + NSArray * cells = [self parentTable].visibleCells; + for (MGSwipeTableCell * cell in cells) { + if ([cell isKindOfClass:[MGSwipeTableCell class]] && cell != self) { + [cell cancelPanGesture]; + } + } + } + } + else if (gesture.state == UIGestureRecognizerStateChanged) { + CGFloat offset = _panStartOffset + current.x - _panStartPoint.x; + if (_firstSwipeState == MGSwipeStateNone) { + _firstSwipeState = offset > 0 ? MGSwipeStateSwipingLeftToRight : MGSwipeStateSwipingRightToLeft; + } + self.swipeOffset = [self filterSwipe:offset]; + } + else { + __weak MGSwipeButtonsView * expansion = _activeExpansion; + if (expansion) { + __weak UIView * expandedButton = [expansion getExpandedButton]; + MGSwipeExpansionSettings * expSettings = _swipeOffset > 0 ? _leftExpansion : _rightExpansion; + UIColor * backgroundColor = nil; + if (!expSettings.fillOnTrigger && expSettings.expansionColor) { + backgroundColor = expansion.backgroundColorCopy; //keep expansion background color + expansion.backgroundColorCopy = expSettings.expansionColor; + } + [self setSwipeOffset:_targetOffset animation:expSettings.triggerAnimation completion:^(BOOL finished){ + if (!finished || self.hidden || !expansion) { + return; //cell might be hidden after a delete row animation without being deallocated (to be reused later) + } + BOOL autoHide = [expansion handleClick:expandedButton fromExpansion:YES]; + if (autoHide) { + [expansion endExpansionAnimated:NO]; + } + if (backgroundColor && expandedButton) { + expandedButton.backgroundColor = backgroundColor; + } + }]; + } + else { + CGFloat velocity = [_panRecognizer velocityInView:self].x; + CGFloat inertiaThreshold = 100.0; //points per second + + if (velocity > inertiaThreshold) { + _targetOffset = _swipeOffset < 0 ? 0 : (_leftView && _leftSwipeSettings.keepButtonsSwiped ? _leftView.bounds.size.width : _targetOffset); + } + else if (velocity < -inertiaThreshold) { + _targetOffset = _swipeOffset > 0 ? 0 : (_rightView && _rightSwipeSettings.keepButtonsSwiped ? -_rightView.bounds.size.width : _targetOffset); + } + _targetOffset = [self filterSwipe:_targetOffset]; + MGSwipeSettings * settings = _swipeOffset > 0 ? _leftSwipeSettings : _rightSwipeSettings; + MGSwipeAnimation * animation = nil; + if (_targetOffset == 0) { + animation = settings.hideAnimation; + } + else if (fabs(_swipeOffset) > fabs(_targetOffset)) { + animation = settings.stretchAnimation; + } + else { + animation = settings.showAnimation; + } + [self setSwipeOffset:_targetOffset animation:animation completion:nil]; + } + + _firstSwipeState = MGSwipeStateNone; + } +} + +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + + if (gestureRecognizer == _panRecognizer) { + + if (self.isEditing) { + return NO; //do not swipe while editing table + } + + CGPoint translation = [_panRecognizer translationInView:self]; + if (fabs(translation.y) > fabs(translation.x)) { + return NO; // user is scrolling vertically + } + if (_swipeView) { + CGPoint point = [_tapRecognizer locationInView:_swipeView]; + if (!CGRectContainsPoint(_swipeView.bounds, point)) { + return _allowsSwipeWhenTappingButtons; //user clicked outside the cell or in the buttons area + } + } + + if (_swipeOffset != 0.0) { + return YES; //already swiped, don't need to check buttons or canSwipe delegate + } + + //make a decision according to existing buttons or using the optional delegate + if (_delegate && [_delegate respondsToSelector:@selector(swipeTableCell:canSwipe:fromPoint:)]) { + CGPoint point = [_panRecognizer locationInView:self]; + _allowSwipeLeftToRight = [_delegate swipeTableCell:self canSwipe:MGSwipeDirectionLeftToRight fromPoint:point]; + _allowSwipeRightToLeft = [_delegate swipeTableCell:self canSwipe:MGSwipeDirectionRightToLeft fromPoint:point]; + } + else if (_delegate && [_delegate respondsToSelector:@selector(swipeTableCell:canSwipe:)]) { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wdeprecated-declarations" + _allowSwipeLeftToRight = [_delegate swipeTableCell:self canSwipe:MGSwipeDirectionLeftToRight]; + _allowSwipeRightToLeft = [_delegate swipeTableCell:self canSwipe:MGSwipeDirectionRightToLeft]; + #pragma clang diagnostic pop + } + else { + [self fetchButtonsIfNeeded]; + _allowSwipeLeftToRight = _leftButtons.count > 0; + _allowSwipeRightToLeft = _rightButtons.count > 0; + } + + return (_allowSwipeLeftToRight && translation.x > 0) || (_allowSwipeRightToLeft && translation.x < 0); + } + else if (gestureRecognizer == _tapRecognizer) { + CGPoint point = [_tapRecognizer locationInView:_swipeView]; + return CGRectContainsPoint(_swipeView.bounds, point); + } + return YES; +} + +-(BOOL) isSwipeGestureActive +{ + return _panRecognizer.state == UIGestureRecognizerStateBegan || _panRecognizer.state == UIGestureRecognizerStateChanged; +} + +-(void)setSwipeBackgroundColor:(UIColor *)swipeBackgroundColor { + _swipeBackgroundColor = swipeBackgroundColor; + if (_swipeOverlay) { + _swipeOverlay.backgroundColor = swipeBackgroundColor; + } +} + +#pragma mark Accessibility + +- (NSInteger)accessibilityElementCount { + return _swipeOffset == 0 ? [super accessibilityElementCount] : 1; +} + +- (id)accessibilityElementAtIndex:(NSInteger)index { + return _swipeOffset == 0 ? [super accessibilityElementAtIndex:index] : self.contentView; +} + +- (NSInteger)indexOfAccessibilityElement:(id)element { + return _swipeOffset == 0 ? [super indexOfAccessibilityElement:element] : 0; +} + + +@end |