iOS 長按移動(dòng)UITableViewCell

之前寫了一篇有關(guān)于UICollectionViewCell的長按移動(dòng)的文章:iOS 長按移動(dòng)UICollectionView中Cell的位置。講述的是iOS端的UICollectionViewCell的長按移動(dòng)方式徘跪。同樣的方法培廓,在UITableViewCell上面是不能實(shí)現(xiàn)的肢预,因?yàn)樘O果并沒有提供:

-(void) beginInteractiveMovementForItemAtIndexPath:(NSIndexPath *)indexPath

這個(gè)方法給UITableView臀防。

所以我們要換一種思路去完成這個(gè)功能涛救,其實(shí)在實(shí)現(xiàn)原理上和UICollectionView很相似:
1.我們需要記錄長按的Cell的NSIndexPath牧嫉,然后對(duì)其截圖剂跟,并且將Cell隱藏减途,之后的移動(dòng)動(dòng)作全是對(duì)這個(gè)截圖完成的。
2.在移動(dòng)的過程中曹洽,不停地刷新手勢的位置鳍置。通過手勢位置獲取新的NSIndexPath,并且不斷地更新數(shù)據(jù)源送淆,修改UITableViewCell的位置税产。這時(shí),可以開一個(gè)CADisplayLink(定時(shí)器偷崩,和屏幕刷新率相同的頻率調(diào)用的辟拷。),作用是阐斜,當(dāng)手勢滑動(dòng)到最頂部或者最底部的時(shí)候衫冻,動(dòng)態(tài)的改變UITableView的contentOffset。
3.結(jié)束定時(shí)器谒出,銷毀截圖隅俘,顯示Cell。

思路其實(shí)就是這么簡單到推,先上圖:


MoveTableViewCell.gif

ok考赛,接下來給大家演示代碼:

#import "ViewController.h"

#define kScreenWidth  [[UIScreen mainScreen] bounds].size.width
#define kScreenHeight [[UIScreen mainScreen] bounds].size.height

typedef NS_ENUM(NSInteger, LYFTableViewType) {
    /// 頂部
    LYFTableViewTypeTop,
    /// 底部
    LYFTableViewTypeBottom
};

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

/// 列表
@property (nonatomic, strong) UITableView *tableView;
/// 數(shù)據(jù)
@property (nonatomic, strong) NSMutableArray *datas;

/// 記錄手指所在的位置
@property (nonatomic, assign) CGPoint longLocation;
/// 對(duì)被選中的cell的截圖
@property (nonatomic, strong) UIView *snapshotView;
/// 被選中的cell的原始位置
@property (nonatomic, strong) NSIndexPath *oldIndexPath;
/// 被選中的cell的新位置
@property (nonatomic, strong) NSIndexPath *newestIndexPath;
/// 定時(shí)器
@property (nonatomic, strong) CADisplayLink *scrollTimer;

/// 滾動(dòng)方向
@property (nonatomic, assign) LYFTableViewType scrollType;

@end

static NSString *cellId = @"cell";

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.datas = [NSMutableArray arrayWithArray:@[[NSMutableArray arrayWithArray:@[@"老大", @"老二", @"老三", @"老四", @"老五", @"老六", @"老七", @"老八", @"老九", @"老十"]], [NSMutableArray arrayWithArray:@[@"老1", @"老2", @"老3", @"老4", @"老5", @"老6", @"老7", @"老8", @"老9", @"老10"]]]];
    [self.tableView reloadData];
}

#pragma mark - 對(duì)cell進(jìn)行截圖,并且隱藏
-(void)snapshotCellAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
    /// 截圖
    UIView *snapshot = [self snapshotView:cell];
    /// 添加在UITableView上
    [self.tableView addSubview:snapshot];
    self.snapshotView = snapshot;
    /// 隱藏cell
    cell.hidden = YES;
    CGPoint center = self.snapshotView.center;
    center.y = self.longLocation.y;
    /// 移動(dòng)截圖
    [UIView animateWithDuration:0.2 animations:^{
        self.snapshotView.transform = CGAffineTransformMakeScale(1.03, 1.03);
        self.snapshotView.alpha = 0.98;
        self.snapshotView.center = center;
    }];
}

#pragma mark - 截圖對(duì)應(yīng)的cell
- (UIView *)snapshotView:(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;
}

#pragma mark - 長按手勢
-(void)longPressGestureRecognized:(UILongPressGestureRecognizer *)longPress {
    UIGestureRecognizerState longPressState = longPress.state;
    //長按的cell在tableView中的位置
    self.longLocation = [longPress locationInView:self.tableView];
    //手指按住位置對(duì)應(yīng)的indexPath莉测,可能為nil
    self.newestIndexPath = [self.tableView indexPathForRowAtPoint:self.longLocation];
    switch (longPressState) {
        case UIGestureRecognizerStateBegan:{
            //手勢開始颜骤,對(duì)被選中cell截圖,隱藏原cell
            self.oldIndexPath = [self.tableView indexPathForRowAtPoint:self.longLocation];
            if (self.oldIndexPath) {
                [self snapshotCellAtIndexPath:self.oldIndexPath];
            }
            break;
        }
        case UIGestureRecognizerStateChanged:{//點(diǎn)擊位置移動(dòng)捣卤,判斷手指按住位置是否進(jìn)入其它indexPath范圍忍抽,若進(jìn)入則更新數(shù)據(jù)源并移動(dòng)cell
            //截圖跟隨手指移動(dòng)
            CGPoint center = _snapshotView.center;
            center.y = self.longLocation.y;
            self.snapshotView.center = center;
            if ([self checkIfSnapshotMeetsEdge]) {
                [self startAutoScrollTimer];
            }else{
                [self stopAutoScrollTimer];
            }
            //手指按住位置對(duì)應(yīng)的indexPath,可能為nil
            self.newestIndexPath = [self.tableView indexPathForRowAtPoint:self.longLocation];
            if (self.newestIndexPath && ![self.newestIndexPath isEqual:self.oldIndexPath]) {
                [self cellRelocatedToNewIndexPath:self.newestIndexPath];
            }
            break;
        }
        default: {
            //長按手勢結(jié)束或被取消董朝,移除截圖鸠项,顯示cell
            [self stopAutoScrollTimer];
            [self didEndDraging];
            break;
        }
    }
}

#pragma mark - 檢查截圖是否到達(dá)邊緣,并作出響應(yīng)
- (BOOL)checkIfSnapshotMeetsEdge{
    CGFloat minY = CGRectGetMinY(self.snapshotView.frame);
    CGFloat maxY = CGRectGetMaxY(self.snapshotView.frame);
    if (minY < self.tableView.contentOffset.y) {
        self.scrollType = LYFTableViewTypeTop;
        return YES;
    }
    if (maxY > self.tableView.bounds.size.height + self.tableView.contentOffset.y) {
        self.scrollType = LYFTableViewTypeBottom;
        return YES;
    }
    return NO;
}

#pragma mark - 當(dāng)截圖到了新的位置子姜,先改變數(shù)據(jù)源祟绊,然后將cell移動(dòng)過去
- (void)cellRelocatedToNewIndexPath:(NSIndexPath *)indexPath{
    //更新數(shù)據(jù)源并返回給外部
    [self updateData];
    //交換移動(dòng)cell位置
    [self.tableView moveRowAtIndexPath:self.oldIndexPath toIndexPath:indexPath];
    //更新cell的原始indexPath為當(dāng)前indexPath
    self.oldIndexPath = indexPath;
    
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:_oldIndexPath];
    cell.hidden = YES;
}

#pragma mark - 更新數(shù)據(jù)源
-(void)updateData {
    //通過DataSource代理獲得原始數(shù)據(jù)源數(shù)組
    NSMutableArray *tempArray = self.datas;
    
    //判斷原始數(shù)據(jù)源是否為多重?cái)?shù)組
    if ([self arrayCheck:tempArray]) {//是嵌套數(shù)組
        if (self.oldIndexPath.section == self.newestIndexPath.section) {//在同一個(gè)section內(nèi)
            [self moveObjectInMutableArray:tempArray[self.oldIndexPath.section] fromIndex:self.oldIndexPath.row toIndex:self.newestIndexPath.row];
        }else{                                                          //不在同一個(gè)section內(nèi)
            id originalObj = tempArray[self.oldIndexPath.section][self.oldIndexPath.item];
            [tempArray[self.newestIndexPath.section] insertObject:originalObj atIndex:self.newestIndexPath.item];
            [tempArray[self.oldIndexPath.section] removeObjectAtIndex:self.oldIndexPath.item];
        }
    }else{                                  //不是嵌套數(shù)組
        [self moveObjectInMutableArray:tempArray fromIndex:self.oldIndexPath.row toIndex:self.newestIndexPath.row];
    }
}

#pragma mark - UITableViewDataSource / UITableViewDelegate
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.datas.count;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSMutableArray *data = self.datas[section];
    return data.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
    
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
        UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPressGestureRecognized:)];
        [cell.contentView addGestureRecognizer:longPress];
    }
    
    NSMutableArray *data = self.datas[indexPath.section];
    cell.textLabel.text = data[indexPath.row];
    
    return cell;
}

#pragma mark - 檢測是否是多重?cái)?shù)組
- (BOOL)arrayCheck:(NSArray *)array{
    for (id obj in array) {
        if ([obj isKindOfClass:[NSArray class]]) {
            return YES;
        }
    }
    return NO;
}

#pragma mark - 將可變數(shù)組中的一個(gè)對(duì)象移動(dòng)到該數(shù)組中的另外一個(gè)位置
- (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];
        }
    }
}

#pragma mark - 開始自動(dòng)滾動(dòng)
- (void)startAutoScroll {
    CGFloat pixelSpeed = 4;
    if (self.scrollType == LYFTableViewTypeTop) {//向下滾動(dòng)
        if (self.tableView.contentOffset.y > 0) {//向下滾動(dòng)最大范圍限制
            [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y - pixelSpeed)];
            self.snapshotView.center = CGPointMake(self.snapshotView.center.x, self.snapshotView.center.y - pixelSpeed);
        }
    }else{                                               //向上滾動(dòng)
        if (self.tableView.contentOffset.y + self.tableView.bounds.size.height < self.tableView.contentSize.height) {//向下滾動(dòng)最大范圍限制
            [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y + pixelSpeed)];
            self.snapshotView.center = CGPointMake(self.snapshotView.center.x, self.snapshotView.center.y + pixelSpeed);
        }
    }
    
    ///  當(dāng)把截圖拖動(dòng)到邊緣,開始自動(dòng)滾動(dòng)哥捕,如果這時(shí)手指完全不動(dòng)牧抽,則不會(huì)觸發(fā)‘UIGestureRecognizerStateChanged’,對(duì)應(yīng)的代碼就不會(huì)執(zhí)行遥赚,導(dǎo)致雖然截圖在tableView中的位置變了扬舒,但并沒有移動(dòng)那個(gè)隱藏的cell,用下面代碼可解決此問題凫佛,cell會(huì)隨著截圖的移動(dòng)而移動(dòng)
    self.newestIndexPath = [self.tableView indexPathForRowAtPoint:self.snapshotView.center];
    if (self.newestIndexPath && ![self.newestIndexPath isEqual:self.oldIndexPath]) {
        [self cellRelocatedToNewIndexPath:self.newestIndexPath];
    }
}

#pragma mark - 拖拽結(jié)束讲坎,顯示cell孕惜,并移除截圖
- (void)didEndDraging{
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:self.oldIndexPath];
    cell.hidden = NO;
    cell.alpha = 0;
    [UIView animateWithDuration:0.2 animations:^{
        self.snapshotView.center = cell.center;
        self.snapshotView.alpha = 0;
        self.snapshotView.transform = CGAffineTransformIdentity;
        cell.alpha = 1;
    } completion:^(BOOL finished) {
        cell.hidden = NO;
        [self.snapshotView removeFromSuperview];
        self.snapshotView = nil;
        self.oldIndexPath = nil;
        self.newestIndexPath = nil;
        
        [self.tableView reloadData];
    }];
}

#pragma mark - 創(chuàng)建定時(shí)器
- (void)startAutoScrollTimer {
    if (!self.scrollTimer) {
        self.scrollTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(startAutoScroll)];
        [self.scrollTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    }
}

#pragma mark - 銷毀定時(shí)器
- (void)stopAutoScrollTimer {
    if (self.scrollTimer) {
        [self.scrollTimer invalidate];
        self.scrollTimer = nil;
    }
}

#pragma mark - Get方法
-(UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight) style:UITableViewStyleGrouped];
        _tableView.dataSource = self;
        _tableView.delegate = self;
        
        [self.view addSubview:_tableView];
    }
    
    return _tableView;
}

@end

代碼很簡單,迎大家提出意見晨炕、建議衫画。
這是github鏈接:傳送門

喜歡的朋友可以點(diǎn)個(gè)贊啦!??

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末府瞄,一起剝皮案震驚了整個(gè)濱河市碧磅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌遵馆,老刑警劉巖鲸郊,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異货邓,居然都是意外死亡秆撮,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門换况,熙熙樓的掌柜王于貴愁眉苦臉地迎上來职辨,“玉大人,你說我怎么就攤上這事戈二∈婵悖” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵觉吭,是天一觀的道長腾供。 經(jīng)常有香客問我,道長鲜滩,這世上最難降的妖魔是什么伴鳖? 我笑而不...
    開封第一講書人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮徙硅,結(jié)果婚禮上榜聂,老公的妹妹穿的比我還像新娘。我一直安慰自己嗓蘑,他們只是感情好须肆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桩皿,像睡著了一般豌汇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上业簿,一...
    開封第一講書人閱讀 50,021評(píng)論 1 291
  • 那天瘤礁,我揣著相機(jī)與錄音阳懂,去河邊找鬼梅尤。 笑死柜思,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的巷燥。 我是一名探鬼主播赡盘,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起钧忽,我...
    開封第一講書人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤抖拦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后趁矾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年定硝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片毫目。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔬啡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出镀虐,到底是詐尸還是另有隱情箱蟆,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布刮便,位于F島的核電站空猜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏诺核。R本人自食惡果不足惜抄肖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窖杀。 院中可真熱鬧漓摩,春花似錦、人聲如沸入客。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽夭咬。三九已至,卻和暖如春铆隘,著一層夾襖步出監(jiān)牢的瞬間卓舵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來泰國打工膀钠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掏湾,地道東北人裹虫。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像融击,于是被迫代替她去往敵國和親筑公。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容