Last active
December 18, 2015 19:09
-
-
Save leeprobert/5831377 to your computer and use it in GitHub Desktop.
UIView+DragAndDrop is an iOS Category for enabling simple drag & drop functionality on any UIView
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
| // | |
| // UIView+DragAndDrop.h | |
| // Engager | |
| // | |
| // Created by Lee Probert on 21/06/2013. | |
| // Copyright (c) 2013 Agnitio. All rights reserved. | |
| // | |
| #import <UIKit/UIKit.h> | |
| @protocol UIViewDragAndDropDelegate; | |
| extern const NSString *notificationViewDidStartDragging; | |
| extern const NSString *notificationViewDidUpdateDragging; | |
| extern const NSString *notificationViewDidEndDragging; | |
| @interface UIView (DragAndDrop) | |
| @property (nonatomic, strong) UILongPressGestureRecognizer *viewDragGestureRecognizer; | |
| @property (nonatomic, assign) id<UIViewDragAndDropDelegate> dragDropDelegate; | |
| @property (nonatomic, assign, getter=isEnableDragAndDrop) BOOL enableDragAndDrop; | |
| @property (nonatomic, assign, getter=isEnableSpringBack) BOOL enableSpringBack; | |
| @property (nonatomic, assign, getter=isEnableShrinkDrop) BOOL enableShrinkDrop; | |
| @property (nonatomic, assign, getter=isDraggingThumb) BOOL draggingThumb; | |
| @property (nonatomic, strong) UIImageView *thumbView; | |
| @property (nonatomic, assign) CGPoint startDragPoint; | |
| -(IBAction)viewDragDaropAction:(id)sender; | |
| -(void) configureDragAndDrop; | |
| -(void) startDraggingAtPoint:(CGPoint)point; | |
| -(void) endDraggingAtPoint:(CGPoint)point; | |
| -(void) updateDraggingAtPoint:(CGPoint)point; | |
| -(CGPoint) getThumbPointFromPoint:(CGPoint)point targetView:(UIView*)view; | |
| -(UIView*) setNewThumbFrameFromPoint:(CGPoint)point; | |
| -(NSDictionary*) getNotificationUserInfoDictionary; | |
| -(UIImage*) getThumbImage; | |
| -(void) removeDragDropThumbView; | |
| -(void) animateDropEffect; | |
| @end | |
| @protocol UIViewDragAndDropDelegate <NSObject> | |
| @optional | |
| -(void) view:(UIView*)dragView didStartDraggingWithGesture:(UIGestureRecognizer*)gesture inView:(UIView*)containerView atPoint:(CGPoint)point; | |
| -(void) view:(UIView*)dragView didUpdateDraggingWithGesture:(UIGestureRecognizer*)gesture inView:(UIView*)containerView atPoint:(CGPoint)point; | |
| -(void) view:(UIView*)dragView didEndDraggingWithGesture:(UIGestureRecognizer*)gesture inView:(UIView*)containerView atPoint:(CGPoint)point; | |
| @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
| // | |
| // UIView+DragAndDrop.m | |
| // Engager | |
| // | |
| // Created by Lee Probert on 21/06/2013. | |
| // Copyright (c) 2013 Agnitio. All rights reserved. | |
| // | |
| #import "UIView+DragAndDrop.h" | |
| #import "AppDelegate.h" | |
| #import <objc/runtime.h> | |
| static char const * const _viewDragGestureRecognizer = "_viewDragGestureRecognizer"; | |
| static char const * const _dragDropDelegate = "_dragDropDelegate"; | |
| static char const * const _enableDragAndDrop = "_enableDragAndDrop"; | |
| static char const * const _enableSpringBack = "_enableSpringBack"; | |
| static char const * const _enableShrinkDrop = "_enableShrinkDrop"; | |
| static char const * const _draggingThumb = "_draggingThumb"; | |
| static char const * const _thumbView = "_thumbView"; | |
| static char const * const _startDragPointX = "_startDragPointX"; | |
| static char const * const _startDragPointY = "_startDragPointY"; | |
| NSString *notificationViewDidStartDragging = @"notificationViewDidStartDragging"; | |
| NSString *notificationViewDidUpdateDragging = @"notificationViewDidUpdateDragging"; | |
| NSString *notificationViewDidEndDragging = @"notificationViewDidEndDragging"; | |
| @implementation UIView (DragAndDrop) | |
| #pragma mark - setter & getter pairs | |
| -(void) setViewDragGestureRecognizer:(UILongPressGestureRecognizer *)viewDragGestureRecognizer { | |
| objc_setAssociatedObject(self, _viewDragGestureRecognizer, viewDragGestureRecognizer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
| } | |
| -(UILongPressGestureRecognizer*) viewDragGestureRecognizer { | |
| return objc_getAssociatedObject(self, _viewDragGestureRecognizer); | |
| } | |
| -(void) setDragDropDelegate:(id<UIViewDragAndDropDelegate>)dragDropDelegate { | |
| objc_setAssociatedObject(self, _dragDropDelegate, dragDropDelegate, OBJC_ASSOCIATION_ASSIGN); | |
| } | |
| -(id) dragDropDelegate { | |
| return objc_getAssociatedObject(self, _dragDropDelegate); | |
| } | |
| -(void) setEnableDragAndDrop:(BOOL)enableDragAndDrop { | |
| objc_setAssociatedObject(self, _enableDragAndDrop, @(enableDragAndDrop), OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
| if(enableDragAndDrop && !self.viewDragGestureRecognizer){ | |
| [self configureDragAndDrop]; | |
| }else{ | |
| // tear down | |
| [self removeGestureRecognizer:self.viewDragGestureRecognizer]; | |
| self.viewDragGestureRecognizer = nil; | |
| self.thumbView = nil; | |
| } | |
| } | |
| -(BOOL) isEnableDragAndDrop { | |
| NSNumber *b = objc_getAssociatedObject(self, _enableDragAndDrop); | |
| return [b boolValue]; | |
| } | |
| -(void) setEnableSpringBack:(BOOL)enableSpringBack { | |
| objc_setAssociatedObject(self, _enableSpringBack, @(enableSpringBack), OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
| } | |
| -(BOOL) isEnableSpringBack { | |
| NSNumber *b = objc_getAssociatedObject(self, _enableSpringBack); | |
| return [b boolValue]; | |
| } | |
| -(void) setEnableShrinkDrop:(BOOL)enableShrinkDrop { | |
| objc_setAssociatedObject(self, _enableShrinkDrop, @(enableShrinkDrop), OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
| } | |
| -(BOOL) isEnableShrinkDrop { | |
| NSNumber *b = objc_getAssociatedObject(self, _enableShrinkDrop); | |
| return [b boolValue]; | |
| } | |
| -(void) setDraggingThumb:(BOOL)draggingThumb { | |
| objc_setAssociatedObject(self, _draggingThumb, @(draggingThumb), OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
| } | |
| -(BOOL) isDraggingThumb { | |
| NSNumber *b = objc_getAssociatedObject(self, _draggingThumb); | |
| return [b boolValue]; | |
| } | |
| -(void) setThumbView:(UIImageView *)thumbView { | |
| objc_setAssociatedObject(self, _thumbView, thumbView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
| } | |
| -(UIImageView*) thumbView { | |
| return objc_getAssociatedObject(self, _thumbView); | |
| } | |
| -(void) setStartDragPoint:(CGPoint)startDragPoint { | |
| objc_setAssociatedObject(self, _startDragPointX, @(startDragPoint.x), OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
| objc_setAssociatedObject(self, _startDragPointY, @(startDragPoint.y), OBJC_ASSOCIATION_RETAIN_NONATOMIC); | |
| } | |
| -(CGPoint) startDragPoint { | |
| NSNumber *posX = objc_getAssociatedObject(self, _startDragPointX); | |
| NSNumber *posY = objc_getAssociatedObject(self, _startDragPointY); | |
| CGPoint pt = CGPointMake([posX floatValue], [posY floatValue]); | |
| return pt; | |
| } | |
| #pragma mark - public | |
| -(IBAction)viewDragDropAction:(id)sender { | |
| UIGestureRecognizer *gesture = (UIGestureRecognizer*)sender; | |
| switch (gesture.state) { | |
| case UIGestureRecognizerStateBegan: | |
| [self startDraggingAtPoint:[gesture locationInView:self]]; | |
| break; | |
| case UIGestureRecognizerStateChanged: | |
| [self updateDraggingAtPoint:[gesture locationInView:self]]; | |
| break; | |
| case UIGestureRecognizerStateEnded: | |
| [self endDraggingAtPoint:[gesture locationInView:self]]; | |
| break; | |
| case UIGestureRecognizerStateCancelled: | |
| case UIGestureRecognizerStateFailed: | |
| [self endDraggingAtPoint:[gesture locationInView:self]]; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| #pragma mark - private | |
| -(void) startDraggingAtPoint:(CGPoint)point; { | |
| self.draggingThumb = YES; | |
| self.startDragPoint = point; | |
| self.enableSpringBack = YES; // always the default. Delegates can change if required at drag end. | |
| self.enableShrinkDrop = NO; // default to spring back so shrink drop needs to be initiated by delegates | |
| UIImage *thumb = [self getThumbImage]; | |
| self.thumbView = [[UIImageView alloc] initWithImage:thumb]; | |
| self.thumbView.backgroundColor = [UIColor clearColor]; | |
| self.thumbView.alpha = 0.8; | |
| UIView *topView = [self setNewThumbFrameFromPoint:point]; | |
| [topView addSubview:self.thumbView]; | |
| [[NSNotificationCenter defaultCenter] postNotificationName:notificationViewDidStartDragging object:self userInfo:[self getNotificationUserInfoDictionary]]; | |
| if(self.dragDropDelegate && [self.dragDropDelegate respondsToSelector:@selector(view:didStartDraggingWithGesture:inView:atPoint:)]){ | |
| [self.dragDropDelegate view:self didStartDraggingWithGesture:self.viewDragGestureRecognizer inView:self.thumbView.superview atPoint:self.thumbView.frame.origin]; | |
| } | |
| self.alpha = 0.2f; | |
| } | |
| -(void) updateDraggingAtPoint:(CGPoint)point; { | |
| if(self.thumbView && self.thumbView.superview) { | |
| [self setNewThumbFrameFromPoint:point]; | |
| [[NSNotificationCenter defaultCenter] postNotificationName:notificationViewDidUpdateDragging object:self userInfo:[self getNotificationUserInfoDictionary]]; | |
| if(self.dragDropDelegate && [self.dragDropDelegate respondsToSelector:@selector(view:didUpdateDraggingWithGesture:inView:atPoint:)]){ | |
| [self.dragDropDelegate view:self didUpdateDraggingWithGesture:self.viewDragGestureRecognizer inView:self.thumbView.superview atPoint:self.thumbView.frame.origin]; | |
| } | |
| } | |
| } | |
| -(void) endDraggingAtPoint:(CGPoint)point; { | |
| self.draggingThumb = NO; | |
| self.alpha = 1.0f; | |
| if(self.thumbView && self.thumbView.superview) { | |
| [[NSNotificationCenter defaultCenter] postNotificationName:notificationViewDidEndDragging object:self userInfo:[self getNotificationUserInfoDictionary]]; | |
| if(self.dragDropDelegate && [self.dragDropDelegate respondsToSelector:@selector(view:didEndDraggingWithGesture:inView:atPoint:)]){ | |
| [self.dragDropDelegate view:self didEndDraggingWithGesture:self.viewDragGestureRecognizer inView:self.thumbView.superview atPoint:self.thumbView.frame.origin]; | |
| } | |
| /* | |
| The Delegates are given the chance to disable spring back so they can properly handle the drop behaviour | |
| */ | |
| if(self.isEnableSpringBack){ | |
| [UIView animateWithDuration:0.33f delay:0.0f options:UIViewAnimationOptionCurveEaseOut | |
| animations:^{ | |
| CGPoint p = [self convertPoint:CGPointZero toView:self.thumbView.superview]; | |
| self.thumbView.frame = CGRectMake(p.x, p.y, self.thumbView.frame.size.width, self.thumbView.frame.size.height); | |
| } completion:^(BOOL finished) { | |
| [self removeDragDropThumbView]; | |
| }]; | |
| }else{ | |
| if(self.isEnableShrinkDrop){ | |
| [self animateDropEffect]; | |
| }else{ | |
| [self removeDragDropThumbView]; | |
| } | |
| } | |
| } | |
| } | |
| -(CGPoint) getThumbPointFromPoint:(CGPoint)point targetView:(UIView*)view { | |
| CGPoint convertedPoint = [self convertPoint:point toView:view]; | |
| CGPoint newPoint = CGPointMake(convertedPoint.x-self.startDragPoint.x, convertedPoint.y-self.startDragPoint.y); | |
| return newPoint; | |
| } | |
| -(UIView*) setNewThumbFrameFromPoint:(CGPoint)point { | |
| AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; | |
| UIView *topView = [[[appDelegate window] rootViewController] view]; | |
| CGPoint newPoint = [self getThumbPointFromPoint:point targetView:topView]; | |
| self.thumbView.frame = CGRectMake(newPoint.x, newPoint.y, self.thumbView.frame.size.width, self.thumbView.frame.size.height); | |
| return topView; | |
| } | |
| -(NSDictionary*) getNotificationUserInfoDictionary { | |
| NSDictionary *d = @{@"gesture" : self.viewDragGestureRecognizer, | |
| @"view" : self.thumbView.superview, | |
| @"pointX" : @(self.thumbView.frame.origin.x), | |
| @"pointY" : @(self.thumbView.frame.origin.y)}; | |
| return d; | |
| } | |
| -(UIImage*) getThumbImage { | |
| CGSize screenShotSize = self.bounds.size; | |
| UIImage *img; | |
| UIGraphicsBeginImageContextWithOptions(screenShotSize, NO, 2.0f); | |
| [self.layer renderInContext:UIGraphicsGetCurrentContext()]; | |
| img = UIGraphicsGetImageFromCurrentImageContext(); | |
| UIGraphicsEndImageContext(); | |
| return img; | |
| } | |
| -(void) configureDragAndDrop { | |
| self.viewDragGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(viewDragDropAction:)]; | |
| self.viewDragGestureRecognizer.minimumPressDuration = 0.33f; | |
| self.viewDragGestureRecognizer.numberOfTouchesRequired = 1; | |
| [self addGestureRecognizer:self.viewDragGestureRecognizer]; | |
| } | |
| -(void) removeDragDropThumbView { | |
| if(self.thumbView && self.thumbView.superview) { | |
| [self.thumbView removeFromSuperview]; | |
| } | |
| self.thumbView = nil; | |
| } | |
| -(void) animateDropEffect { | |
| if(self.thumbView && self.thumbView.superview) { | |
| [UIView animateWithDuration:0.33f delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^{ | |
| self.thumbView.transform = CGAffineTransformScale(self.thumbView.transform, 0.0f, 0.0f); | |
| } completion:^(BOOL finished) { | |
| [self removeDragDropThumbView]; | |
| }]; | |
| } | |
| } | |
| @end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment