UITableView - cell拖動

cell拖動
  • UITableView - cell之間拖動 下次在進入APP 自動記錄此次位置! 網絡請求數據進行排序處理! (cell自定義)直接VC引用此View

核心代碼如下:
.h中

#import <UIKit/UIKit.h>


@class RTDragCellTableView;
@protocol RTDragCellTableViewDataSource <UITableViewDataSource>

@required
/**將外部數據源數組傳入,以便在移動cell數據發生改變時進行修改重排*/
- (NSArray *)originalArrayDataForTableView:(RTDragCellTableView *)tableView;

@end

@protocol RTDragCellTableViewDelegate <UITableViewDelegate>

@required
/**將修改重排后的數組傳入,以便外部更新數據源*/
- (void)tableView:(RTDragCellTableView *)tableView newArrayDataForDataSource:(NSArray *)newArray;
@optional
/**選中的cell準備好可以移動的時候*/
- (void)tableView:(RTDragCellTableView *)tableView cellReadyToMoveAtIndexPath:(NSIndexPath *)indexPath;
/**選中的cell正在移動,變換位置,手勢尚未松開*/
- (void)cellIsMovingInTableView:(RTDragCellTableView *)tableView;
/**選中的cell完成移動,手勢已松開*/
- (void)cellDidEndMovingInTableView:(RTDragCellTableView *)tableView;

@end

@interface RTDragCellTableView : UITableView

@property (nonatomic, assign) id<RTDragCellTableViewDataSource> dataSource;
@property (nonatomic, assign) id<RTDragCellTableViewDelegate> delegate;

@end

.m中

#import "RTDragCellTableView.h"
typedef enum{
    RTSnapshotMeetsEdgeTop,
    RTSnapshotMeetsEdgeBottom,
}RTSnapshotMeetsEdge;

@interface RTDragCellTableView ()
/**對被選中的cell的截圖*/
@property (nonatomic, weak) UIView *snapshot;
/**被選中的cell的原始位置*/
@property (nonatomic, strong) NSIndexPath *originalIndexPath;
/**被選中的cell的新位置*/
@property (nonatomic, strong) NSIndexPath *relocatedIndexPath;
/**cell被拖動到邊緣后開啟,tableview自動向上或向下滾動*/
@property (nonatomic, strong) CADisplayLink *autoScrollTimer;
/**記錄手指所在的位置*/
@property (nonatomic, assign) CGPoint fingerLocation;
/**自動滾動的方向*/
@property (nonatomic, assign) RTSnapshotMeetsEdge autoScrollDirection;

@end


@implementation RTDragCellTableView

@dynamic delegate;
@dynamic dataSource;

# pragma mark - initialization methods
/**在初始化時加入一個長按手勢*/
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style{
    self = [super initWithFrame:frame style:style];
    if (self) {
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressGestureRecognized:)];
        [self addGestureRecognizer:longPress];
    }
    return self;
}
/**在初始化時加入一個長按手勢*/
//- (instancetype)init{
//    self = [super init];
//    if (self) {
//        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressGestureRecognized:)];
//        [self addGestureRecognizer:longPress];
//    }
//    return self;
//}

# pragma mark - Gesture methods

- (void)longPressGestureRecognized:(id)sender{
    UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
    UIGestureRecognizerState longPressState = longPress.state;
    //手指在tableView中的位置
    _fingerLocation = [longPress locationInView:self];
    //手指按住位置對應的indexPath,可能為nil
    _relocatedIndexPath = [self indexPathForRowAtPoint:_fingerLocation];
    switch (longPressState) {
        case UIGestureRecognizerStateBegan:{  //手勢開始,對被選中cell截圖,隱藏原cell
            _originalIndexPath = [self indexPathForRowAtPoint:_fingerLocation];
            if (_originalIndexPath) {
                [self cellSelectedAtIndexPath:_originalIndexPath];
            }
            break;
        }
        case UIGestureRecognizerStateChanged:{//點擊位置移動,判斷手指按住位置是否進入其它indexPath范圍,若進入則更新數據源并移動cell
            //截圖跟隨手指移動
            CGPoint center = _snapshot.center;
            center.y = _fingerLocation.y;
            _snapshot.center = center;
            if ([self checkIfSnapshotMeetsEdge]) {
                [self startAutoScrollTimer];
            }else{
                [self stopAutoScrollTimer];
            }
            //手指按住位置對應的indexPath,可能為nil
            _relocatedIndexPath = [self indexPathForRowAtPoint:_fingerLocation];
            if (_relocatedIndexPath && ![_relocatedIndexPath isEqual:_originalIndexPath]) {
                [self cellRelocatedToNewIndexPath:_relocatedIndexPath];
            }
            break;
        }
        default: {                             //長按手勢結束或被取消,移除截圖,顯示cell
            [self stopAutoScrollTimer];
            [self didEndDraging];
            break;
        }
    }
}

# pragma mark - timer methods
/**
 *  創建定時器并運行
 */
- (void)startAutoScrollTimer{
    if (!_autoScrollTimer) {
        _autoScrollTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(startAutoScroll)];
        [_autoScrollTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }
}
/**
 *  停止定時器并銷毀
 */
- (void)stopAutoScrollTimer{
    if (_autoScrollTimer) {
        [_autoScrollTimer invalidate];
        _autoScrollTimer = nil;
    }
}

# pragma mark - Private methods
/**修改數據源,通知外部更新數據源*/
- (void)updateDataSource{
    //通過DataSource代理獲得原始數據源數組
    NSMutableArray *tempArray = [NSMutableArray array];
    if ([self.dataSource respondsToSelector:@selector(originalArrayDataForTableView:)]) {
        [tempArray addObjectsFromArray:[self.dataSource originalArrayDataForTableView:self]];
    }
    //判斷原始數據源是否為嵌套數組
    if ([self nestedArrayCheck:tempArray]) {//是嵌套數組
        if (_originalIndexPath.section == _relocatedIndexPath.section) {//在同一個section內
            [self moveObjectInMutableArray:tempArray[_originalIndexPath.section] fromIndex:_originalIndexPath.row toIndex:_relocatedIndexPath.row];
        }else{                                                          //不在同一個section內
            id originalObj = tempArray[_originalIndexPath.section][_originalIndexPath.item];
            [tempArray[_relocatedIndexPath.section] insertObject:originalObj atIndex:_relocatedIndexPath.item];
            [tempArray[_originalIndexPath.section] removeObjectAtIndex:_originalIndexPath.item];
        }
    }else{                                  //不是嵌套數組
        [self moveObjectInMutableArray:tempArray fromIndex:_originalIndexPath.row toIndex:_relocatedIndexPath.row];
    }
    //將新數組傳出外部以更改數據源
    if ([self.delegate respondsToSelector:@selector(tableView:newArrayDataForDataSource:)]) {
        [self.delegate tableView:self newArrayDataForDataSource:tempArray];
    }
}

/**
 *  檢查數組是否為嵌套數組
 *  @param array 需要被檢測的數組
 *  @return 返回YES則表示是嵌套數組
 */
- (BOOL)nestedArrayCheck:(NSArray *)array{
    for (id obj in array) {
        if ([obj isKindOfClass:[NSArray class]]) {
            return YES;
        }
    }
    return NO;
}

/**
 *  cell被長按手指選中,對其進行截圖,原cell隱藏
 */
- (void)cellSelectedAtIndexPath:(NSIndexPath *)indexPath{
    //HomePageTableViewCell.h
    UITableViewCell *cell = [self cellForRowAtIndexPath:indexPath];
    UIView *snapshot = [self customSnapshotFromView:cell];
    [self addSubview:snapshot];
    _snapshot = snapshot;
    cell.hidden = YES;
    CGPoint center = _snapshot.center;
    center.y = _fingerLocation.y;
    [UIView animateWithDuration:0.2 animations:^{
        _snapshot.transform = CGAffineTransformMakeScale(1.03, 1.03);
        _snapshot.alpha = 0.98;
        _snapshot.center = center;
    }];
}
/**
 *  截圖被移動到新的indexPath范圍,這時先更新數據源,重排數組,再將cell移至新位置
 *  @param indexPath 新的indexPath
 */
- (void)cellRelocatedToNewIndexPath:(NSIndexPath *)indexPath{
    //更新數據源并返回給外部
    [self updateDataSource];
    //交換移動cell位置
    [self moveRowAtIndexPath:_originalIndexPath toIndexPath:indexPath];
    //更新cell的原始indexPath為當前indexPath
    _originalIndexPath = indexPath;
}
/**
 *  拖拽結束,顯示cell,并移除截圖
 */
- (void)didEndDraging{
    UITableViewCell *cell = [self cellForRowAtIndexPath:_originalIndexPath];
    cell.hidden = NO;
    cell.alpha = 0;
    [UIView animateWithDuration:0.2 animations:^{
        _snapshot.center = cell.center;
        _snapshot.alpha = 0;
        _snapshot.transform = CGAffineTransformIdentity;
        cell.alpha = 1;
    } completion:^(BOOL finished) {
        cell.hidden = NO;
        [_snapshot removeFromSuperview];
        _snapshot = nil;
        _originalIndexPath = nil;
        _relocatedIndexPath = nil;
    }];
}



/** 返回一個給定view的截圖. */
- (UIView *)customSnapshotFromView:(UIView *)inputView {
    
    // Make an image from the input view.
    UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, NO, 0);
    [inputView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    // Create an image view.
    UIView *snapshot = [[UIImageView alloc] initWithImage:image];
    snapshot.center = inputView.center;
    snapshot.layer.masksToBounds = NO;
    snapshot.layer.cornerRadius = 0.0;
    snapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);
    snapshot.layer.shadowRadius = 5.0;
    snapshot.layer.shadowOpacity = 0.4;
    
    return snapshot;
}
/**
 *  將可變數組中的一個對象移動到該數組中的另外一個位置
 *  @param array     要變動的數組
 *  @param fromIndex 從這個index
 *  @param toIndex   移至這個index
 */
- (void)moveObjectInMutableArray:(NSMutableArray *)array fromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex{
    if (fromIndex < toIndex) {
        for (NSInteger i = fromIndex; i < toIndex; i ++) {
            [array exchangeObjectAtIndex:i withObjectAtIndex:i + 1];
        }
    }else{
        for (NSInteger i = fromIndex; i > toIndex; i --) {
            [array exchangeObjectAtIndex:i withObjectAtIndex:i - 1];
        }
    }
}

/**
 *  檢查截圖是否到達邊緣,并作出響應
 */
- (BOOL)checkIfSnapshotMeetsEdge{
    CGFloat minY = CGRectGetMinY(_snapshot.frame);
    CGFloat maxY = CGRectGetMaxY(_snapshot.frame);
    if (minY < self.contentOffset.y) {
        _autoScrollDirection = RTSnapshotMeetsEdgeTop;
        return YES;
    }
    if (maxY > self.bounds.size.height + self.contentOffset.y) {
        _autoScrollDirection = RTSnapshotMeetsEdgeBottom;
        return YES;
    }
    return NO;
}
/**
 *  開始自動滾動
 */
- (void)startAutoScroll{
    CGFloat pixelSpeed = 4;
    if (_autoScrollDirection == RTSnapshotMeetsEdgeTop) {//向下滾動
        if (self.contentOffset.y > 0) {//向下滾動最大范圍限制
            [self setContentOffset:CGPointMake(0, self.contentOffset.y - pixelSpeed)];
            _snapshot.center = CGPointMake(_snapshot.center.x, _snapshot.center.y - pixelSpeed);
        }
    }else{                                               //向上滾動
        if (self.contentOffset.y + self.bounds.size.height < self.contentSize.height) {//向下滾動最大范圍限制
            [self setContentOffset:CGPointMake(0, self.contentOffset.y + pixelSpeed)];
            _snapshot.center = CGPointMake(_snapshot.center.x, _snapshot.center.y + pixelSpeed);
        }
    }
    
    /*  當把截圖拖動到邊緣,開始自動滾動,如果這時手指完全不動,則不會觸發‘UIGestureRecognizerStateChanged’,對應的代碼就不會執行,導致雖然截圖在tableView中的位置變了,但并沒有移動那個隱藏的cell,用下面代碼可解決此問題,cell會隨著截圖的移動而移動
     */
    _relocatedIndexPath = [self indexPathForRowAtPoint:_snapshot.center];
    if (_relocatedIndexPath && ![_relocatedIndexPath isEqual:_originalIndexPath]) {
        [self cellRelocatedToNewIndexPath:_relocatedIndexPath];
    }
}
@end

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,885評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,312評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,993評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,667評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,410評論 6 411
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,778評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,775評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,955評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,521評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,266評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,468評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,998評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,696評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,095評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,385評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,193評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,431評論 2 378

推薦閱讀更多精彩內容

  • Swift1> Swift和OC的區別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,125評論 1 32
  • 朋友圈看到一則知乎分享,題目是《摧毀一個熊孩子有多困難》。無盡的長度,往下展開全文了好多次都沒到底。 里面貢獻了無...
    白露Lin閱讀 494評論 0 0
  • 一、喜之郎果凍: 本來:長大我要當太空人,爺爺奶奶可高興了,我喜歡愛吃的喜之郎果凍。 被改一:長大我要當喜之...
    zy呵呵呵閱讀 408評論 0 2
  • 鹽 藕 味千拉面
    般若迷閱讀 232評論 0 0