├── Demo ├── PanCollectionView.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj └── PanCollectionView │ ├── XWCellModel.m │ ├── ViewController.h │ ├── XWCell.h │ ├── AppDelegate.h │ ├── XWCellModel.h │ ├── main.m │ ├── XWCell.m │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Info.plist │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── AppDelegate.m │ ├── XWCell.xib │ └── ViewController.m ├── .gitignore ├── README.md └── XWDragCellCollectionView ├── XWDragCellCollectionView.h └── XWDragCellCollectionView.m /Demo/PanCollectionView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/XWCellModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // XWCellModel.m 3 | // PanCollectionView 4 | // 5 | // Created by wazrx on 16/1/4. 6 | // Copyright © 2016年 wazrx. All rights reserved. 7 | // 8 | 9 | #import "XWCellModel.h" 10 | 11 | @implementation XWCellModel 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // PanCollectionView 4 | // 5 | // Created by YouLoft_MacMini on 16/1/4. 6 | // Copyright © 2016年 wazrx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | .svn 4 | */build/* 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | #*.xcworkspace 14 | xcuserdata 15 | *.moved-aside 16 | DerivedData 17 | .idea/ 18 | *.hmap 19 | *.xccheckout 20 | 21 | #CocoaPods 22 | #Pods 23 | 24 | 25 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/XWCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // XWCell.h 3 | // PanCollectionView 4 | // 5 | // Created by YouLoft_MacMini on 16/1/4. 6 | // Copyright © 2016年 wazrx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class XWCellModel; 12 | 13 | @interface XWCell : UICollectionViewCell 14 | 15 | @property (nonatomic, strong) XWCellModel *data; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // PanCollectionView 4 | // 5 | // Created by YouLoft_MacMini on 16/1/4. 6 | // Copyright © 2016年 wazrx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/XWCellModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // XWCellModel.h 3 | // PanCollectionView 4 | // 5 | // Created by wazrx on 16/1/4. 6 | // Copyright © 2016年 wazrx. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface XWCellModel : NSObject 12 | @property (nonatomic, strong) UIColor *backGroundColor; 13 | @property (nonatomic, copy) NSString *title; 14 | @end 15 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // PanCollectionView 4 | // 5 | // Created by YouLoft_MacMini on 16/1/4. 6 | // Copyright © 2016年 wazrx. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/XWCell.m: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // XWCell.m 4 | // PanCollectionView 5 | // 6 | // Created by YouLoft_MacMini on 16/1/4. 7 | // Copyright © 2016年 wazrx. All rights reserved. 8 | // 9 | 10 | #import "XWCell.h" 11 | #import "XWCellModel.h" 12 | 13 | @interface XWCell () 14 | @property (weak, nonatomic) IBOutlet UILabel *label; 15 | 16 | @end 17 | 18 | @implementation XWCell 19 | 20 | - (void)setData:(XWCellModel *)data{ 21 | _data = data; 22 | _label.text = data.title; 23 | self.backgroundColor = data.backGroundColor; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XWDragCellCollectionView 2 | 封装的CollectionView的拖动重排的效果控件,先请看图:(吐槽:不知道为啥从xcode7开始,模拟器变得很卡很卡,所以截图的效果不好,大家可以在真机上测试,效果还是非常不错的) 3 | ####图1:垂直滚动 4 | 5 | ![drag1.gif](http://ww2.sinaimg.cn/mw690/5ededce5gw1ezoq84h05ig208m0gawwn.gif) 6 | ####图2:水平滚动 7 | 8 | ![drag2.gif](http://ww1.sinaimg.cn/mw690/5ededce5gw1ezoq869c1ig208m0gahb3.gif) 9 | ####图3:配合瀑布流 10 | 11 | ![drag5.gif](http://ww3.sinaimg.cn/mw690/5ededce5gw1ezoq8a18dzg208m0gab2a.gif) 12 | 13 | 14 | 使用也非常简单,只需3步,步骤如下: 15 | 16 | ``` 17 | 1、继承于XWDragCellCollectionView; 18 | 19 | 2、实现必须实现的DataSouce代理方法:(在该方法中返回整个CollectionView的数据数组用于重排) 20 | - (NSArray *)dataSourceArrayOfCollectionView:(XWDragCellCollectionView *)collectionView; 21 | 22 | 3、实现必须实现的一个Delegate代理方法:(在该方法中将重拍好的新数据源设为当前数据源)(例如 :_data = newDataArray) 23 | - (void)dragCellCollectionView:(XWDragCellCollectionView *)collectionView newDataArrayAfterMove:(NSArray *)newDataArray; 24 | 25 | ``` 26 | 27 | 详细的使用可以查看代码中的demo,支持设置长按事件,是否开启边缘滑动,抖动、以及设置抖动等级,这些在h文件里面都有详细说明,有需要的可以尝试一下,并多多提意见,详细请浏览我的简书:[可拖拽重排的CollectionView](http://www.jianshu.com/p/8f0153ce17f9) 28 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // PanCollectionView 4 | // 5 | // Created by YouLoft_MacMini on 16/1/4. 6 | // Copyright © 2016年 wazrx. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/XWCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /XWDragCellCollectionView/XWDragCellCollectionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // XWDragCellCollectionView.h 3 | // PanCollectionView 4 | // 5 | // Created by YouLoft_MacMini on 16/1/4. 6 | // Copyright © 2016年 wazrx. All rights reserved. 7 | // 可拖动cell重新排布的collectionView 8 | /**如何使用: 9 | 1、继承与XWDragCellCollectionView; 10 | 2、实现必须实现的DataSouce代理方法:(在该方法中返回整个CollectionView的数据数组用于重排) 11 | - (NSArray *)dataSourceArrayOfCollectionView:(XWDragCellCollectionView *)collectionView; 12 | 3、实现必须实现的一个Delegate代理方法:(在该方法中将重拍好的新数据源设为当前数据源)(例如 :_data = newDataArray) 13 | - (void)dragCellCollectionView:(XWDragCellCollectionView *)collectionView newDataArrayAfterMove:(NSArray *)newDataArray; 14 | */ 15 | 16 | 17 | 18 | #import 19 | @class XWDragCellCollectionView; 20 | 21 | @protocol XWDragCellCollectionViewDelegate 22 | 23 | @required 24 | /** 25 | * 当数据源更新的到时候调用,必须实现,需将新的数据源设置为当前tableView的数据源(例如 :_data = newDataArray) 26 | * @param newDataArray 更新后的数据源 27 | */ 28 | - (void)dragCellCollectionView:(XWDragCellCollectionView *)collectionView newDataArrayAfterMove:(NSArray *)newDataArray; 29 | 30 | @optional 31 | 32 | /** 33 | * 某些indexPaths是不需要交换和晃动的,常见的比如添加按钮等,传入这些indexPaths数组排出交换和抖动操作 34 | * @param return 需要排除的indexPath数组,该数组中的indexPath无法长按抖动和交换 35 | */ 36 | - (NSArray *)excludeIndexPathsWhenMoveDragCellCollectionView:(XWDragCellCollectionView *)collectionView; 37 | 38 | /** 39 | * 某个cell将要开始移动的时候调用 40 | * @param indexPath 该cell当前的indexPath 41 | */ 42 | - (void)dragCellCollectionView:(XWDragCellCollectionView *)collectionView cellWillBeginMoveAtIndexPath:(NSIndexPath *)indexPath; 43 | /** 44 | * 某个cell正在移动的时候 45 | */ 46 | - (void)dragCellCollectionViewCellisMoving:(XWDragCellCollectionView *)collectionView; 47 | /** 48 | * cell移动完毕,并成功移动到新位置的时候调用 49 | */ 50 | - (void)dragCellCollectionViewCellEndMoving:(XWDragCellCollectionView *)collectionView; 51 | /** 52 | * 成功交换了位置的时候调用 53 | * @param fromIndexPath 交换cell的起始位置 54 | * @param toIndexPath 交换cell的新位置 55 | */ 56 | - (void)dragCellCollectionView:(XWDragCellCollectionView *)collectionView moveCellFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath; 57 | 58 | @end 59 | 60 | @protocol XWDragCellCollectionViewDataSource 61 | 62 | 63 | @required 64 | /** 65 | * 返回整个CollectionView的数据,必须实现,需根据数据进行移动后的数据重排 66 | */ 67 | - (NSArray *)dataSourceArrayOfCollectionView:(XWDragCellCollectionView *)collectionView; 68 | 69 | @end 70 | 71 | @interface XWDragCellCollectionView : UICollectionView 72 | 73 | @property (nonatomic, assign) id delegate; 74 | @property (nonatomic, assign) id dataSource; 75 | 76 | /**长按多少秒触发拖动手势,默认1秒,如果设置为0,表示手指按下去立刻就触发拖动*/ 77 | @property (nonatomic, assign) NSTimeInterval minimumPressDuration; 78 | /**是否开启拖动到边缘滚动CollectionView的功能,默认YES*/ 79 | @property (nonatomic, assign) BOOL edgeScrollEable; 80 | /**是否开启拖动的时候所有cell抖动的效果,默认YES*/ 81 | @property (nonatomic, assign) BOOL shakeWhenMoveing; 82 | /**抖动的等级(1.0f~10.0f),默认4*/ 83 | @property (nonatomic, assign) CGFloat shakeLevel; 84 | /**是否正在编辑模式,调用xwp_enterEditingModel和xw_stopEditingModel会修改该方法的值*/ 85 | @property (nonatomic, assign, readonly, getter=isEditing) BOOL editing; 86 | 87 | /**进入编辑模式,如果开启抖动会自动持续抖动,且不用长按就能出发拖动*/ 88 | - (void)xw_enterEditingModel; 89 | 90 | /**退出编辑模式*/ 91 | - (void)xw_stopEditingModel; 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /Demo/PanCollectionView/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // PanCollectionView 4 | // 5 | // Created by YouLoft_MacMini on 16/1/4. 6 | // Copyright © 2016年 wazrx. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "XWCell.h" 11 | #import "XWCellModel.h" 12 | #import "XWDragCellCollectionView.h" 13 | 14 | #define XWLog(fmt, ...) fprintf(stderr,"%s:%d\t%s\n",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, [[NSString stringWithFormat:fmt, ##__VA_ARGS__] UTF8String]); 15 | 16 | @interface ViewController () 17 | @property (nonatomic, strong) NSArray *data; 18 | @property (nonatomic, weak) XWDragCellCollectionView *mainView; 19 | @property (nonatomic, assign) UIBarButtonItem *editButton; 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | self.title = @"XWDragCellCollectionView"; 27 | UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; 28 | CGFloat width = (self.view.bounds.size.width - 40) / 3.0f; 29 | layout.itemSize = CGSizeMake(width, width); 30 | layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); 31 | layout.minimumInteritemSpacing = 10; 32 | XWDragCellCollectionView *mainView = [[XWDragCellCollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; 33 | _mainView = mainView; 34 | mainView.delegate = self; 35 | mainView.dataSource = self; 36 | mainView.shakeLevel = 3.0f; 37 | mainView.backgroundColor = [UIColor whiteColor]; 38 | [mainView registerNib:[UINib nibWithNibName:@"XWCell" bundle:nil] forCellWithReuseIdentifier:@"XWCell"]; 39 | [self.view addSubview:mainView]; 40 | UIBarButtonItem *editingButton = [[UIBarButtonItem alloc] initWithTitle:@"编辑" style:UIBarButtonItemStylePlain target:self action:@selector(xwp_edting:)]; 41 | _editButton = editingButton; 42 | self.navigationItem.rightBarButtonItem = editingButton; 43 | } 44 | 45 | - (NSArray *)data{ 46 | if (!_data) { 47 | NSMutableArray *temp = @[].mutableCopy; 48 | NSArray *colors = @[[UIColor redColor], [UIColor blueColor], [UIColor yellowColor], [UIColor orangeColor], [UIColor greenColor]]; 49 | for (int i = 0; i < 5; i ++) { 50 | NSMutableArray *tempSection = @[].mutableCopy; 51 | for (int j = 0; j < arc4random() % 12 + 6; j ++) { 52 | NSString *str = [NSString stringWithFormat:@"%d--%d", i, j]; 53 | XWCellModel *model = [XWCellModel new]; 54 | model.backGroundColor = colors[i]; 55 | model.title = str; 56 | [tempSection addObject:model]; 57 | } 58 | [temp addObject:tempSection.copy]; 59 | } 60 | _data = temp.copy; 61 | } 62 | return _data; 63 | } 64 | 65 | - (void)xwp_edting:(UIBarButtonItem *)sender{ 66 | if (_mainView.isEditing) { 67 | [_mainView xw_stopEditingModel]; 68 | sender.title = @"编辑"; 69 | }else{ 70 | [_mainView xw_enterEditingModel]; 71 | sender.title = @"退出"; 72 | } 73 | } 74 | 75 | 76 | #pragma mark - 77 | 78 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{ 79 | return self.data.count; 80 | } 81 | 82 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ 83 | NSArray *sec = _data[section]; 84 | return sec.count; 85 | } 86 | 87 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ 88 | XWCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"XWCell" forIndexPath:indexPath]; 89 | cell.data = _data[indexPath.section][indexPath.item]; 90 | return cell; 91 | } 92 | 93 | - (NSArray *)dataSourceArrayOfCollectionView:(XWDragCellCollectionView *)collectionView{ 94 | return _data; 95 | } 96 | 97 | #pragma mark - 98 | 99 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{ 100 | XWCellModel *model = _data[indexPath.section][indexPath.item]; 101 | NSLog(@"点击了%@",model.title); 102 | } 103 | 104 | - (void)dragCellCollectionView:(XWDragCellCollectionView *)collectionView newDataArrayAfterMove:(NSArray *)newDataArray{ 105 | _data = newDataArray; 106 | } 107 | 108 | - (void)dragCellCollectionView:(XWDragCellCollectionView *)collectionView cellWillBeginMoveAtIndexPath:(NSIndexPath *)indexPath{ 109 | //拖动时候最后禁用掉编辑按钮的点击 110 | _editButton.enabled = NO; 111 | } 112 | 113 | - (void)dragCellCollectionViewCellEndMoving:(XWDragCellCollectionView *)collectionView{ 114 | _editButton.enabled = YES; 115 | } 116 | 117 | - (NSArray *)excludeIndexPathsWhenMoveDragCellCollectionView:(XWDragCellCollectionView *)collectionView{ 118 | //每个section的最后一个cell都不能交换 119 | NSMutableArray * excluedeIndexPaths = [NSMutableArray arrayWithCapacity:self.data.count]; 120 | [self.data enumerateObjectsUsingBlock:^(NSArray* _Nonnull section, NSUInteger idx, BOOL * _Nonnull stop) { 121 | [excluedeIndexPaths addObject:[NSIndexPath indexPathForItem:section.count - 1 inSection:idx]]; 122 | }]; 123 | return excluedeIndexPaths.copy; 124 | } 125 | 126 | 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /Demo/PanCollectionView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3BFA00F61E6034BE00CB6345 /* XWDragCellCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3BFA00F51E6034BE00CB6345 /* XWDragCellCollectionView.m */; }; 11 | 40A234281C3A609F008B710C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 40A234271C3A609F008B710C /* main.m */; }; 12 | 40A2342B1C3A609F008B710C /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 40A2342A1C3A609F008B710C /* AppDelegate.m */; }; 13 | 40A2342E1C3A609F008B710C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 40A2342D1C3A609F008B710C /* ViewController.m */; }; 14 | 40A234311C3A60A0008B710C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 40A2342F1C3A60A0008B710C /* Main.storyboard */; }; 15 | 40A234331C3A60A0008B710C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 40A234321C3A60A0008B710C /* Assets.xcassets */; }; 16 | 40A234361C3A60A0008B710C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 40A234341C3A60A0008B710C /* LaunchScreen.storyboard */; }; 17 | 40A2343F1C3A60C2008B710C /* XWCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 40A2343E1C3A60C2008B710C /* XWCell.m */; }; 18 | 40A234411C3A60DD008B710C /* XWCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 40A234401C3A60DD008B710C /* XWCell.xib */; }; 19 | 8BC29DAD1C3AC63900DEF66D /* XWCellModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BC29DAC1C3AC63900DEF66D /* XWCellModel.m */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 3BFA00F41E6034BE00CB6345 /* XWDragCellCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = XWDragCellCollectionView.h; path = ../../XWDragCellCollectionView/XWDragCellCollectionView.h; sourceTree = ""; }; 24 | 3BFA00F51E6034BE00CB6345 /* XWDragCellCollectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XWDragCellCollectionView.m; path = ../../XWDragCellCollectionView/XWDragCellCollectionView.m; sourceTree = ""; }; 25 | 40A234231C3A609F008B710C /* PanCollectionView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PanCollectionView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | 40A234271C3A609F008B710C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 27 | 40A234291C3A609F008B710C /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 28 | 40A2342A1C3A609F008B710C /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 29 | 40A2342C1C3A609F008B710C /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 30 | 40A2342D1C3A609F008B710C /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 31 | 40A234301C3A60A0008B710C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 32 | 40A234321C3A60A0008B710C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 33 | 40A234351C3A60A0008B710C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 34 | 40A234371C3A60A0008B710C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | 40A2343D1C3A60C2008B710C /* XWCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XWCell.h; sourceTree = ""; }; 36 | 40A2343E1C3A60C2008B710C /* XWCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XWCell.m; sourceTree = ""; }; 37 | 40A234401C3A60DD008B710C /* XWCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = XWCell.xib; sourceTree = ""; }; 38 | 8BC29DAB1C3AC63900DEF66D /* XWCellModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XWCellModel.h; sourceTree = ""; }; 39 | 8BC29DAC1C3AC63900DEF66D /* XWCellModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XWCellModel.m; sourceTree = ""; }; 40 | /* End PBXFileReference section */ 41 | 42 | /* Begin PBXFrameworksBuildPhase section */ 43 | 40A234201C3A609F008B710C /* Frameworks */ = { 44 | isa = PBXFrameworksBuildPhase; 45 | buildActionMask = 2147483647; 46 | files = ( 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXFrameworksBuildPhase section */ 51 | 52 | /* Begin PBXGroup section */ 53 | 40A2341A1C3A609F008B710C = { 54 | isa = PBXGroup; 55 | children = ( 56 | 40A234251C3A609F008B710C /* PanCollectionView */, 57 | 40A234241C3A609F008B710C /* Products */, 58 | ); 59 | sourceTree = ""; 60 | }; 61 | 40A234241C3A609F008B710C /* Products */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 40A234231C3A609F008B710C /* PanCollectionView.app */, 65 | ); 66 | name = Products; 67 | sourceTree = ""; 68 | }; 69 | 40A234251C3A609F008B710C /* PanCollectionView */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 3BFA00F41E6034BE00CB6345 /* XWDragCellCollectionView.h */, 73 | 3BFA00F51E6034BE00CB6345 /* XWDragCellCollectionView.m */, 74 | 40A234291C3A609F008B710C /* AppDelegate.h */, 75 | 40A2342A1C3A609F008B710C /* AppDelegate.m */, 76 | 40A2342C1C3A609F008B710C /* ViewController.h */, 77 | 40A2342D1C3A609F008B710C /* ViewController.m */, 78 | 40A2343D1C3A60C2008B710C /* XWCell.h */, 79 | 40A2343E1C3A60C2008B710C /* XWCell.m */, 80 | 8BC29DAB1C3AC63900DEF66D /* XWCellModel.h */, 81 | 8BC29DAC1C3AC63900DEF66D /* XWCellModel.m */, 82 | 40A234401C3A60DD008B710C /* XWCell.xib */, 83 | 40A2342F1C3A60A0008B710C /* Main.storyboard */, 84 | 40A234321C3A60A0008B710C /* Assets.xcassets */, 85 | 40A234341C3A60A0008B710C /* LaunchScreen.storyboard */, 86 | 40A234371C3A60A0008B710C /* Info.plist */, 87 | 40A234261C3A609F008B710C /* Supporting Files */, 88 | ); 89 | path = PanCollectionView; 90 | sourceTree = ""; 91 | }; 92 | 40A234261C3A609F008B710C /* Supporting Files */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 40A234271C3A609F008B710C /* main.m */, 96 | ); 97 | name = "Supporting Files"; 98 | sourceTree = ""; 99 | }; 100 | /* End PBXGroup section */ 101 | 102 | /* Begin PBXNativeTarget section */ 103 | 40A234221C3A609F008B710C /* PanCollectionView */ = { 104 | isa = PBXNativeTarget; 105 | buildConfigurationList = 40A2343A1C3A60A0008B710C /* Build configuration list for PBXNativeTarget "PanCollectionView" */; 106 | buildPhases = ( 107 | 40A2341F1C3A609F008B710C /* Sources */, 108 | 40A234201C3A609F008B710C /* Frameworks */, 109 | 40A234211C3A609F008B710C /* Resources */, 110 | ); 111 | buildRules = ( 112 | ); 113 | dependencies = ( 114 | ); 115 | name = PanCollectionView; 116 | productName = PanCollectionView; 117 | productReference = 40A234231C3A609F008B710C /* PanCollectionView.app */; 118 | productType = "com.apple.product-type.application"; 119 | }; 120 | /* End PBXNativeTarget section */ 121 | 122 | /* Begin PBXProject section */ 123 | 40A2341B1C3A609F008B710C /* Project object */ = { 124 | isa = PBXProject; 125 | attributes = { 126 | LastUpgradeCheck = 0800; 127 | ORGANIZATIONNAME = wazrx; 128 | TargetAttributes = { 129 | 40A234221C3A609F008B710C = { 130 | CreatedOnToolsVersion = 7.1; 131 | DevelopmentTeam = 4TH838523D; 132 | }; 133 | }; 134 | }; 135 | buildConfigurationList = 40A2341E1C3A609F008B710C /* Build configuration list for PBXProject "PanCollectionView" */; 136 | compatibilityVersion = "Xcode 3.2"; 137 | developmentRegion = English; 138 | hasScannedForEncodings = 0; 139 | knownRegions = ( 140 | en, 141 | Base, 142 | ); 143 | mainGroup = 40A2341A1C3A609F008B710C; 144 | productRefGroup = 40A234241C3A609F008B710C /* Products */; 145 | projectDirPath = ""; 146 | projectRoot = ""; 147 | targets = ( 148 | 40A234221C3A609F008B710C /* PanCollectionView */, 149 | ); 150 | }; 151 | /* End PBXProject section */ 152 | 153 | /* Begin PBXResourcesBuildPhase section */ 154 | 40A234211C3A609F008B710C /* Resources */ = { 155 | isa = PBXResourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | 40A234361C3A60A0008B710C /* LaunchScreen.storyboard in Resources */, 159 | 40A234331C3A60A0008B710C /* Assets.xcassets in Resources */, 160 | 40A234411C3A60DD008B710C /* XWCell.xib in Resources */, 161 | 40A234311C3A60A0008B710C /* Main.storyboard in Resources */, 162 | ); 163 | runOnlyForDeploymentPostprocessing = 0; 164 | }; 165 | /* End PBXResourcesBuildPhase section */ 166 | 167 | /* Begin PBXSourcesBuildPhase section */ 168 | 40A2341F1C3A609F008B710C /* Sources */ = { 169 | isa = PBXSourcesBuildPhase; 170 | buildActionMask = 2147483647; 171 | files = ( 172 | 40A2342E1C3A609F008B710C /* ViewController.m in Sources */, 173 | 40A2342B1C3A609F008B710C /* AppDelegate.m in Sources */, 174 | 8BC29DAD1C3AC63900DEF66D /* XWCellModel.m in Sources */, 175 | 3BFA00F61E6034BE00CB6345 /* XWDragCellCollectionView.m in Sources */, 176 | 40A2343F1C3A60C2008B710C /* XWCell.m in Sources */, 177 | 40A234281C3A609F008B710C /* main.m in Sources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXSourcesBuildPhase section */ 182 | 183 | /* Begin PBXVariantGroup section */ 184 | 40A2342F1C3A60A0008B710C /* Main.storyboard */ = { 185 | isa = PBXVariantGroup; 186 | children = ( 187 | 40A234301C3A60A0008B710C /* Base */, 188 | ); 189 | name = Main.storyboard; 190 | sourceTree = ""; 191 | }; 192 | 40A234341C3A60A0008B710C /* LaunchScreen.storyboard */ = { 193 | isa = PBXVariantGroup; 194 | children = ( 195 | 40A234351C3A60A0008B710C /* Base */, 196 | ); 197 | name = LaunchScreen.storyboard; 198 | sourceTree = ""; 199 | }; 200 | /* End PBXVariantGroup section */ 201 | 202 | /* Begin XCBuildConfiguration section */ 203 | 40A234381C3A60A0008B710C /* Debug */ = { 204 | isa = XCBuildConfiguration; 205 | buildSettings = { 206 | ALWAYS_SEARCH_USER_PATHS = NO; 207 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 208 | CLANG_CXX_LIBRARY = "libc++"; 209 | CLANG_ENABLE_MODULES = YES; 210 | CLANG_ENABLE_OBJC_ARC = YES; 211 | CLANG_WARN_BOOL_CONVERSION = YES; 212 | CLANG_WARN_CONSTANT_CONVERSION = YES; 213 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 214 | CLANG_WARN_EMPTY_BODY = YES; 215 | CLANG_WARN_ENUM_CONVERSION = YES; 216 | CLANG_WARN_INFINITE_RECURSION = YES; 217 | CLANG_WARN_INT_CONVERSION = YES; 218 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 219 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 220 | CLANG_WARN_UNREACHABLE_CODE = YES; 221 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 222 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 223 | COPY_PHASE_STRIP = NO; 224 | DEBUG_INFORMATION_FORMAT = dwarf; 225 | ENABLE_STRICT_OBJC_MSGSEND = YES; 226 | ENABLE_TESTABILITY = YES; 227 | GCC_C_LANGUAGE_STANDARD = gnu99; 228 | GCC_DYNAMIC_NO_PIC = NO; 229 | GCC_NO_COMMON_BLOCKS = YES; 230 | GCC_OPTIMIZATION_LEVEL = 0; 231 | GCC_PREPROCESSOR_DEFINITIONS = ( 232 | "DEBUG=1", 233 | "$(inherited)", 234 | ); 235 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 236 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 237 | GCC_WARN_UNDECLARED_SELECTOR = YES; 238 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 239 | GCC_WARN_UNUSED_FUNCTION = YES; 240 | GCC_WARN_UNUSED_VARIABLE = YES; 241 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 242 | MTL_ENABLE_DEBUG_INFO = YES; 243 | ONLY_ACTIVE_ARCH = YES; 244 | SDKROOT = iphoneos; 245 | }; 246 | name = Debug; 247 | }; 248 | 40A234391C3A60A0008B710C /* Release */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 253 | CLANG_CXX_LIBRARY = "libc++"; 254 | CLANG_ENABLE_MODULES = YES; 255 | CLANG_ENABLE_OBJC_ARC = YES; 256 | CLANG_WARN_BOOL_CONVERSION = YES; 257 | CLANG_WARN_CONSTANT_CONVERSION = YES; 258 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 259 | CLANG_WARN_EMPTY_BODY = YES; 260 | CLANG_WARN_ENUM_CONVERSION = YES; 261 | CLANG_WARN_INFINITE_RECURSION = YES; 262 | CLANG_WARN_INT_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 268 | COPY_PHASE_STRIP = NO; 269 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 270 | ENABLE_NS_ASSERTIONS = NO; 271 | ENABLE_STRICT_OBJC_MSGSEND = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu99; 273 | GCC_NO_COMMON_BLOCKS = YES; 274 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 275 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 276 | GCC_WARN_UNDECLARED_SELECTOR = YES; 277 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 278 | GCC_WARN_UNUSED_FUNCTION = YES; 279 | GCC_WARN_UNUSED_VARIABLE = YES; 280 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 281 | MTL_ENABLE_DEBUG_INFO = NO; 282 | SDKROOT = iphoneos; 283 | VALIDATE_PRODUCT = YES; 284 | }; 285 | name = Release; 286 | }; 287 | 40A2343B1C3A60A0008B710C /* Debug */ = { 288 | isa = XCBuildConfiguration; 289 | buildSettings = { 290 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 291 | CODE_SIGN_IDENTITY = "iPhone Developer"; 292 | DEVELOPMENT_TEAM = 4TH838523D; 293 | INFOPLIST_FILE = PanCollectionView/Info.plist; 294 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 295 | PRODUCT_BUNDLE_IDENTIFIER = com.youloft.PanCollectionView; 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | }; 298 | name = Debug; 299 | }; 300 | 40A2343C1C3A60A0008B710C /* Release */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 304 | CODE_SIGN_IDENTITY = "iPhone Developer"; 305 | DEVELOPMENT_TEAM = 4TH838523D; 306 | INFOPLIST_FILE = PanCollectionView/Info.plist; 307 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 308 | PRODUCT_BUNDLE_IDENTIFIER = com.youloft.PanCollectionView; 309 | PRODUCT_NAME = "$(TARGET_NAME)"; 310 | }; 311 | name = Release; 312 | }; 313 | /* End XCBuildConfiguration section */ 314 | 315 | /* Begin XCConfigurationList section */ 316 | 40A2341E1C3A609F008B710C /* Build configuration list for PBXProject "PanCollectionView" */ = { 317 | isa = XCConfigurationList; 318 | buildConfigurations = ( 319 | 40A234381C3A60A0008B710C /* Debug */, 320 | 40A234391C3A60A0008B710C /* Release */, 321 | ); 322 | defaultConfigurationIsVisible = 0; 323 | defaultConfigurationName = Release; 324 | }; 325 | 40A2343A1C3A60A0008B710C /* Build configuration list for PBXNativeTarget "PanCollectionView" */ = { 326 | isa = XCConfigurationList; 327 | buildConfigurations = ( 328 | 40A2343B1C3A60A0008B710C /* Debug */, 329 | 40A2343C1C3A60A0008B710C /* Release */, 330 | ); 331 | defaultConfigurationIsVisible = 0; 332 | defaultConfigurationName = Release; 333 | }; 334 | /* End XCConfigurationList section */ 335 | }; 336 | rootObject = 40A2341B1C3A609F008B710C /* Project object */; 337 | } 338 | -------------------------------------------------------------------------------- /XWDragCellCollectionView/XWDragCellCollectionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // XWDragCellCollectionView.m 3 | // PanCollectionView 4 | // 5 | // Created by YouLoft_MacMini on 16/1/4. 6 | // Copyright © 2016年 wazrx. All rights reserved. 7 | // 8 | 9 | #import "XWDragCellCollectionView.h" 10 | #import 11 | 12 | #define angelToRandian(x) ((x)/180.0*M_PI) 13 | 14 | typedef NS_ENUM(NSUInteger, XWDragCellCollectionViewScrollDirection) { 15 | XWDragCellCollectionViewScrollDirectionNone = 0, 16 | XWDragCellCollectionViewScrollDirectionLeft, 17 | XWDragCellCollectionViewScrollDirectionRight, 18 | XWDragCellCollectionViewScrollDirectionUp, 19 | XWDragCellCollectionViewScrollDirectionDown 20 | }; 21 | 22 | @interface XWDragCellCollectionView () 23 | @property (nonatomic, strong) NSIndexPath *originalIndexPath; 24 | @property (nonatomic, weak) UICollectionViewCell *orignalCell; 25 | @property (nonatomic, assign) CGPoint orignalCenter; 26 | @property (nonatomic, strong) NSIndexPath *moveIndexPath; 27 | @property (nonatomic, weak) UIView *tempMoveCell; 28 | @property (nonatomic, weak) UILongPressGestureRecognizer *longPressGesture; 29 | @property (nonatomic, strong) CADisplayLink *edgeTimer; 30 | @property (nonatomic, assign) CGPoint lastPoint; 31 | @property (nonatomic, assign) XWDragCellCollectionViewScrollDirection scrollDirection; 32 | @property (nonatomic, assign) CGFloat oldMinimumPressDuration; 33 | @property (nonatomic, assign, getter=isObservering) BOOL observering; 34 | @property (nonatomic) BOOL isPanning; 35 | 36 | @end 37 | 38 | @implementation XWDragCellCollectionView 39 | 40 | @dynamic delegate; 41 | @dynamic dataSource; 42 | 43 | #pragma mark - initailize methods 44 | 45 | - (void)dealloc{ 46 | [self xwp_removeContentOffsetObserver]; 47 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 48 | } 49 | 50 | - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(nonnull UICollectionViewLayout *)layout 51 | { 52 | self = [super initWithFrame:frame collectionViewLayout:layout]; 53 | if (self) { 54 | [self xwp_initializeProperty]; 55 | [self xwp_addGesture]; 56 | //添加监听 57 | [self xwp_addContentOffsetObserver]; 58 | } 59 | return self; 60 | } 61 | 62 | - (instancetype)initWithCoder:(NSCoder *)coder 63 | { 64 | self = [super initWithCoder:coder]; 65 | if (self) { 66 | [self xwp_initializeProperty]; 67 | [self xwp_addGesture]; 68 | //添加监听 69 | [self xwp_addContentOffsetObserver]; 70 | } 71 | return self; 72 | } 73 | 74 | - (void)xwp_initializeProperty{ 75 | _minimumPressDuration = 1; 76 | _edgeScrollEable = YES; 77 | _shakeWhenMoveing = YES; 78 | _shakeLevel = 4.0f; 79 | } 80 | 81 | #pragma mark - longPressGesture methods 82 | 83 | /** 84 | * 添加一个自定义的滑动手势 85 | */ 86 | - (void)xwp_addGesture{ 87 | UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(xwp_longPressed:)]; 88 | _longPressGesture = longPress; 89 | longPress.minimumPressDuration = _minimumPressDuration; 90 | [self addGestureRecognizer:longPress]; 91 | } 92 | 93 | /** 94 | * 监听手势的改变 95 | */ 96 | - (void)xwp_longPressed:(UILongPressGestureRecognizer *)longPressGesture{ 97 | if (longPressGesture.state == UIGestureRecognizerStateBegan) { 98 | [self xwp_gestureBegan:longPressGesture]; 99 | } 100 | if (longPressGesture.state == UIGestureRecognizerStateChanged) { 101 | [self xwp_gestureChange:longPressGesture]; 102 | } 103 | if (longPressGesture.state == UIGestureRecognizerStateCancelled || 104 | longPressGesture.state == UIGestureRecognizerStateEnded){ 105 | [self xwp_gestureEndOrCancle:longPressGesture]; 106 | } 107 | } 108 | 109 | /** 110 | * 手势开始 111 | */ 112 | - (void)xwp_gestureBegan:(UILongPressGestureRecognizer *)longPressGesture{ 113 | //获取手指所在的cell 114 | _originalIndexPath = [self indexPathForItemAtPoint:[longPressGesture locationOfTouch:0 inView:longPressGesture.view]]; 115 | if ([self xwp_indexPathIsExcluded:_originalIndexPath]) { 116 | return; 117 | } 118 | _isPanning = YES; 119 | UICollectionViewCell *cell = [self cellForItemAtIndexPath:_originalIndexPath]; 120 | UIImage *snap; 121 | UIGraphicsBeginImageContextWithOptions(cell.bounds.size, 1.0f, 0); 122 | [cell.layer renderInContext:UIGraphicsGetCurrentContext()]; 123 | snap = UIGraphicsGetImageFromCurrentImageContext(); 124 | UIGraphicsEndImageContext(); 125 | UIView *tempMoveCell = [UIView new]; 126 | tempMoveCell.layer.contents = (__bridge id)snap.CGImage; 127 | cell.hidden = YES; 128 | //记录cell,不能通过_originalIndexPath,在重用之后原indexpath所对应的cell可能不会是这个cell了 129 | _orignalCell = cell; 130 | //记录ceter,同理不能通过_originalIndexPath来获取cell 131 | _orignalCenter = cell.center; 132 | _tempMoveCell = tempMoveCell; 133 | _tempMoveCell.frame = cell.frame; 134 | [self addSubview:_tempMoveCell]; 135 | //开启边缘滚动定时器 136 | [self xwp_setEdgeTimer]; 137 | //开启抖动 138 | if (!_editing) { 139 | [self xwp_shakeAllCell]; 140 | } 141 | _lastPoint = [longPressGesture locationOfTouch:0 inView:longPressGesture.view]; 142 | //通知代理 143 | if ([self.delegate respondsToSelector:@selector(dragCellCollectionView:cellWillBeginMoveAtIndexPath:)]) { 144 | [self.delegate dragCellCollectionView:self cellWillBeginMoveAtIndexPath:_originalIndexPath]; 145 | } 146 | } 147 | /** 148 | * 手势拖动 149 | */ 150 | - (void)xwp_gestureChange:(UILongPressGestureRecognizer *)longPressGesture{ 151 | //通知代理 152 | if ([self.delegate respondsToSelector:@selector(dragCellCollectionViewCellisMoving:)]) { 153 | [self.delegate dragCellCollectionViewCellisMoving:self]; 154 | } 155 | CGFloat tranX = [longPressGesture locationOfTouch:0 inView:longPressGesture.view].x - _lastPoint.x; 156 | CGFloat tranY = [longPressGesture locationOfTouch:0 inView:longPressGesture.view].y - _lastPoint.y; 157 | _tempMoveCell.center = CGPointApplyAffineTransform(_tempMoveCell.center, CGAffineTransformMakeTranslation(tranX, tranY)); 158 | _lastPoint = [longPressGesture locationOfTouch:0 inView:longPressGesture.view]; 159 | [self xwp_moveCell]; 160 | } 161 | 162 | /** 163 | * 手势取消或者结束 164 | */ 165 | - (void)xwp_gestureEndOrCancle:(UILongPressGestureRecognizer *)longPressGesture{ 166 | UICollectionViewCell *cell = [self cellForItemAtIndexPath:_originalIndexPath]; 167 | self.userInteractionEnabled = NO; 168 | _isPanning = NO; 169 | [self xwp_stopEdgeTimer]; 170 | //通知代理 171 | if ([self.delegate respondsToSelector:@selector(dragCellCollectionViewCellEndMoving:)]) { 172 | [self.delegate dragCellCollectionViewCellEndMoving:self]; 173 | } 174 | [UIView animateWithDuration:0.25 animations:^{ 175 | _tempMoveCell.center = _orignalCenter; 176 | } completion:^(BOOL finished) { 177 | [self xwp_stopShakeAllCell]; 178 | [_tempMoveCell removeFromSuperview]; 179 | cell.hidden = NO; 180 | _orignalCell.hidden = NO; 181 | self.userInteractionEnabled = YES; 182 | _originalIndexPath = nil; 183 | }]; 184 | } 185 | 186 | #pragma mark - setter methods 187 | 188 | - (void)setMinimumPressDuration:(NSTimeInterval)minimumPressDuration{ 189 | _minimumPressDuration = minimumPressDuration; 190 | _longPressGesture.minimumPressDuration = minimumPressDuration; 191 | } 192 | 193 | - (void)setShakeLevel:(CGFloat)shakeLevel{ 194 | CGFloat level = MAX(1.0f, shakeLevel); 195 | _shakeLevel = MIN(level, 10.0f); 196 | } 197 | 198 | #pragma mark - timer methods 199 | 200 | - (void)xwp_setEdgeTimer{ 201 | if (!_edgeTimer && _edgeScrollEable) { 202 | _edgeTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(xwp_edgeScroll)]; 203 | [_edgeTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; 204 | } 205 | } 206 | 207 | - (void)xwp_stopEdgeTimer{ 208 | if (_edgeTimer) { 209 | [_edgeTimer invalidate]; 210 | _edgeTimer = nil; 211 | } 212 | } 213 | 214 | 215 | #pragma mark - private methods 216 | 217 | - (void)xwp_moveCell{ 218 | for (UICollectionViewCell *cell in [self visibleCells]) { 219 | if ([self indexPathForCell:cell] == _originalIndexPath || [self xwp_indexPathIsExcluded:[self indexPathForCell:cell]]) { 220 | continue; 221 | } 222 | //计算中心距 223 | CGFloat spacingX = fabs(_tempMoveCell.center.x - cell.center.x); 224 | CGFloat spacingY = fabs(_tempMoveCell.center.y - cell.center.y); 225 | if (spacingX <= _tempMoveCell.bounds.size.width / 2.0f && spacingY <= _tempMoveCell.bounds.size.height / 2.0f) { 226 | _moveIndexPath = [self indexPathForCell:cell]; 227 | _orignalCell = cell; 228 | _orignalCenter = cell.center; 229 | //更新数据源 230 | [self xwp_updateDataSource]; 231 | //移动 232 | NSLog(@"%@", [self cellForItemAtIndexPath:_originalIndexPath]); 233 | // cell.hidden = YES; 234 | [CATransaction begin]; 235 | [self moveItemAtIndexPath:_originalIndexPath toIndexPath:_moveIndexPath]; 236 | [CATransaction setCompletionBlock:^{ 237 | NSLog(@"动画完成"); 238 | }]; 239 | [CATransaction commit]; 240 | //通知代理 241 | if ([self.delegate respondsToSelector:@selector(dragCellCollectionView:moveCellFromIndexPath:toIndexPath:)]) { 242 | [self.delegate dragCellCollectionView:self moveCellFromIndexPath:_originalIndexPath toIndexPath:_moveIndexPath]; 243 | } 244 | //设置移动后的起始indexPath 245 | _originalIndexPath = _moveIndexPath; 246 | break; 247 | } 248 | } 249 | } 250 | 251 | /** 252 | * 更新数据源 253 | */ 254 | - (void)xwp_updateDataSource{ 255 | NSMutableArray *temp = @[].mutableCopy; 256 | //获取数据源 257 | if ([self.dataSource respondsToSelector:@selector(dataSourceArrayOfCollectionView:)]) { 258 | [temp addObjectsFromArray:[self.dataSource dataSourceArrayOfCollectionView:self]]; 259 | } 260 | //判断数据源是单个数组还是数组套数组的多section形式,YES表示数组套数组 261 | BOOL dataTypeCheck = ([self numberOfSections] != 1 || ([self numberOfSections] == 1 && [temp[0] isKindOfClass:[NSArray class]])); 262 | if (dataTypeCheck) { 263 | for (int i = 0; i < temp.count; i ++) { 264 | [temp replaceObjectAtIndex:i withObject:[temp[i] mutableCopy]]; 265 | } 266 | } 267 | if (_moveIndexPath.section == _originalIndexPath.section) { 268 | NSMutableArray *orignalSection = dataTypeCheck ? temp[_originalIndexPath.section] : temp; 269 | if (_moveIndexPath.item > _originalIndexPath.item) { 270 | for (NSUInteger i = _originalIndexPath.item; i < _moveIndexPath.item ; i ++) { 271 | [orignalSection exchangeObjectAtIndex:i withObjectAtIndex:i + 1]; 272 | } 273 | }else{ 274 | for (NSUInteger i = _originalIndexPath.item; i > _moveIndexPath.item ; i --) { 275 | [orignalSection exchangeObjectAtIndex:i withObjectAtIndex:i - 1]; 276 | } 277 | } 278 | }else{ 279 | NSMutableArray *orignalSection = temp[_originalIndexPath.section]; 280 | NSMutableArray *currentSection = temp[_moveIndexPath.section]; 281 | [currentSection insertObject:orignalSection[_originalIndexPath.item] atIndex:_moveIndexPath.item]; 282 | [orignalSection removeObject:orignalSection[_originalIndexPath.item]]; 283 | } 284 | //将重排好的数据传递给外部 285 | if ([self.delegate respondsToSelector:@selector(dragCellCollectionView:newDataArrayAfterMove:)]) { 286 | [self.delegate dragCellCollectionView:self newDataArrayAfterMove:temp.copy]; 287 | } 288 | } 289 | 290 | - (void)xwp_edgeScroll{ 291 | [self xwp_setScrollDirection]; 292 | switch (_scrollDirection) { 293 | case XWDragCellCollectionViewScrollDirectionLeft:{ 294 | //这里的动画必须设为NO 295 | [self setContentOffset:CGPointMake(self.contentOffset.x - 4, self.contentOffset.y) animated:NO]; 296 | _tempMoveCell.center = CGPointMake(_tempMoveCell.center.x - 4, _tempMoveCell.center.y); 297 | _lastPoint.x -= 4; 298 | 299 | } 300 | break; 301 | case XWDragCellCollectionViewScrollDirectionRight:{ 302 | [self setContentOffset:CGPointMake(self.contentOffset.x + 4, self.contentOffset.y) animated:NO]; 303 | _tempMoveCell.center = CGPointMake(_tempMoveCell.center.x + 4, _tempMoveCell.center.y); 304 | _lastPoint.x += 4; 305 | 306 | } 307 | break; 308 | case XWDragCellCollectionViewScrollDirectionUp:{ 309 | [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y - 4) animated:NO]; 310 | _tempMoveCell.center = CGPointMake(_tempMoveCell.center.x, _tempMoveCell.center.y - 4); 311 | _lastPoint.y -= 4; 312 | } 313 | break; 314 | case XWDragCellCollectionViewScrollDirectionDown:{ 315 | [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y + 4) animated:NO]; 316 | _tempMoveCell.center = CGPointMake(_tempMoveCell.center.x, _tempMoveCell.center.y + 4); 317 | _lastPoint.y += 4; 318 | } 319 | break; 320 | default: 321 | break; 322 | } 323 | 324 | } 325 | 326 | - (void)xwp_shakeAllCell{ 327 | if (!_shakeWhenMoveing) { 328 | //没有开启抖动只需要遍历设置个cell的hidden属性 329 | NSArray *cells = [self visibleCells]; 330 | for (UICollectionViewCell *cell in cells) { 331 | //顺便设置各个cell的hidden属性,由于有cell被hidden,其hidden状态可能被冲用到其他cell上,不能直接利用_originalIndexPath相等判断,这很坑 332 | BOOL hidden = _originalIndexPath && [self indexPathForCell:cell].item == _originalIndexPath.item && [self indexPathForCell:cell].section == _originalIndexPath.section; 333 | cell.hidden = hidden; 334 | } 335 | return; 336 | } 337 | CAKeyframeAnimation* anim=[CAKeyframeAnimation animation]; 338 | anim.keyPath=@"transform.rotation"; 339 | anim.values=@[@(angelToRandian(-_shakeLevel)),@(angelToRandian(_shakeLevel)),@(angelToRandian(-_shakeLevel))]; 340 | anim.repeatCount=MAXFLOAT; 341 | anim.duration=0.2; 342 | NSArray *cells = [self visibleCells]; 343 | for (UICollectionViewCell *cell in cells) { 344 | if ([self xwp_indexPathIsExcluded:[self indexPathForCell:cell]]) { 345 | continue; 346 | } 347 | /**如果加了shake动画就不用再加了*/ 348 | if (![cell.layer animationForKey:@"shake"]) { 349 | [cell.layer addAnimation:anim forKey:@"shake"]; 350 | } 351 | //顺便设置各个cell的hidden属性,由于有cell被hidden,其hidden状态可能被冲用到其他cell上 352 | BOOL hidden = _originalIndexPath && [self indexPathForCell:cell].item == _originalIndexPath.item && [self indexPathForCell:cell].section == _originalIndexPath.section; 353 | cell.hidden = hidden; 354 | } 355 | if (![_tempMoveCell.layer animationForKey:@"shake"]) { 356 | [_tempMoveCell.layer addAnimation:anim forKey:@"shake"]; 357 | } 358 | } 359 | 360 | - (void)xwp_stopShakeAllCell{ 361 | if (!_shakeWhenMoveing || _editing) { 362 | return; 363 | } 364 | NSArray *cells = [self visibleCells]; 365 | for (UICollectionViewCell *cell in cells) { 366 | [cell.layer removeAllAnimations]; 367 | } 368 | [_tempMoveCell.layer removeAllAnimations]; 369 | } 370 | 371 | - (void)xwp_setScrollDirection{ 372 | _scrollDirection = XWDragCellCollectionViewScrollDirectionNone; 373 | if (self.bounds.size.height + self.contentOffset.y - _tempMoveCell.center.y < _tempMoveCell.bounds.size.height / 2 && self.bounds.size.height + self.contentOffset.y < self.contentSize.height) { 374 | _scrollDirection = XWDragCellCollectionViewScrollDirectionDown; 375 | } 376 | if (_tempMoveCell.center.y - self.contentOffset.y < _tempMoveCell.bounds.size.height / 2 && self.contentOffset.y > 0) { 377 | _scrollDirection = XWDragCellCollectionViewScrollDirectionUp; 378 | } 379 | if (self.bounds.size.width + self.contentOffset.x - _tempMoveCell.center.x < _tempMoveCell.bounds.size.width / 2 && self.bounds.size.width + self.contentOffset.x < self.contentSize.width) { 380 | _scrollDirection = XWDragCellCollectionViewScrollDirectionRight; 381 | } 382 | 383 | if (_tempMoveCell.center.x - self.contentOffset.x < _tempMoveCell.bounds.size.width / 2 && self.contentOffset.x > 0) { 384 | _scrollDirection = XWDragCellCollectionViewScrollDirectionLeft; 385 | } 386 | } 387 | 388 | - (void)xwp_addContentOffsetObserver{ 389 | if (_observering) return; 390 | [self addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; 391 | _observering = YES; 392 | } 393 | 394 | - (void)xwp_removeContentOffsetObserver{ 395 | if (!_observering) return; 396 | [self removeObserver:self forKeyPath:@"contentOffset"]; 397 | _observering = NO; 398 | } 399 | 400 | - (BOOL)xwp_indexPathIsExcluded:(NSIndexPath *)indexPath{ 401 | if (!indexPath || ![self.delegate respondsToSelector:@selector(excludeIndexPathsWhenMoveDragCellCollectionView:)]) { 402 | return NO; 403 | } 404 | NSArray *excludeIndexPaths = [self.delegate excludeIndexPathsWhenMoveDragCellCollectionView:self]; 405 | __block BOOL flag = NO; 406 | [excludeIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 407 | if (obj.item == indexPath.item && obj.section == indexPath.section) { 408 | flag = YES; 409 | *stop = YES; 410 | } 411 | }]; 412 | return flag; 413 | } 414 | 415 | #pragma mark - public methods 416 | 417 | - (void)xw_enterEditingModel{ 418 | _editing = YES; 419 | _oldMinimumPressDuration = _longPressGesture.minimumPressDuration; 420 | _longPressGesture.minimumPressDuration = 0; 421 | if (_shakeWhenMoveing) { 422 | [self xwp_shakeAllCell]; 423 | [self xwp_addContentOffsetObserver]; 424 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(xwp_foreground) name:UIApplicationWillEnterForegroundNotification object:nil]; 425 | } 426 | } 427 | 428 | - (void)xw_stopEditingModel{ 429 | _editing = NO; 430 | _longPressGesture.minimumPressDuration = _oldMinimumPressDuration; 431 | [self xwp_stopShakeAllCell]; 432 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; 433 | } 434 | 435 | 436 | #pragma mark - overWrite methods 437 | 438 | /** 439 | * 重写hitTest事件,判断是否应该相应自己的滑动手势,还是系统的滑动手势 440 | */ 441 | 442 | - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ 443 | _longPressGesture.enabled = [self indexPathForItemAtPoint:point]; 444 | return [super hitTest:point withEvent:event]; 445 | } 446 | 447 | #pragma mark - KVO 448 | 449 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ 450 | if (![keyPath isEqualToString:@"contentOffset"]) return; 451 | if (_editing || _isPanning) { 452 | [self xwp_shakeAllCell]; 453 | }else if (!_editing && !_isPanning){ 454 | [self xwp_stopShakeAllCell]; 455 | } 456 | } 457 | 458 | #pragma mark - notification 459 | 460 | - (void)xwp_foreground{ 461 | if (_editing) { 462 | [self xwp_shakeAllCell]; 463 | } 464 | } 465 | 466 | 467 | 468 | @end 469 | --------------------------------------------------------------------------------