長按即可移動(dòng)cell的UITableView

期望效果

JXMovableCellTableView.gif

1.長按即可觸發(fā)移動(dòng)cell攒岛,操作邏輯簡單;
2.移動(dòng)cell時(shí)越靠近屏幕邊緣,速度越快萄喳;
3.被移動(dòng)cell的樣式可以自定義;

github地址

JXMovableCellTableView,如果你喜歡蹋半,不妨給顆星吧_~

調(diào)研

如果只是實(shí)現(xiàn)移動(dòng)UITableViewCell他巨,系統(tǒng)自帶的API即可搞定。
調(diào)用下面的方法[tableView setEditing:YES animated:YES];即可進(jìn)入編輯模式减江。然后實(shí)現(xiàn)下面的方法即可開啟移動(dòng)cell染突。

//默認(rèn)編輯模式下,每個(gè)cell左邊有個(gè)紅色的刪除按鈕辈灼,設(shè)置為None即可去掉
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewCellEditingStyleNone;
}
//是否允許indexPath的cell移動(dòng)
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES;
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
    //更新數(shù)據(jù)源
}

用系統(tǒng)的方法有幾個(gè)缺點(diǎn):
1.需要用一個(gè)開關(guān)去控制編輯狀態(tài)觉痛,不方便;
2.移動(dòng)cell的時(shí)候cell右邊有個(gè)指示圖標(biāo)茵休,看著不爽薪棒;
3.被移動(dòng)cell的樣式不能自己定制。
綜上所述榕莺,需要自己去寫效果了俐芯。

實(shí)現(xiàn)原理

大概原理:為UITableView添加一個(gè)長按手勢(shì),然后給選中的cell截圖钉鸯;讓截圖隨著手勢(shì)移動(dòng)吧史,同時(shí)記錄選中的indexPath,方便位置互換唠雕。

1.添加長按手勢(shì)

- (void)jx_addGesture
{
    _gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(jx_processGesture:)];
    _gesture.minimumPressDuration = _gestureMinimumPressDuration;
    [self addGestureRecognizer:_gesture];
}

2.處理手勢(shì)開始贸营,需要通過dataSource方法獲取數(shù)據(jù)源

- (void)jx_gestureBegan:(UILongPressGestureRecognizer *)gesture
{
    CGPoint point = [gesture locationInView:gesture.view];
    NSIndexPath *selectedIndexPath = [self indexPathForRowAtPoint:point];
    if (!selectedIndexPath) {
        return;
    }
    if (self.delegate && [self.delegate respondsToSelector:@selector(tableView:willMoveCellAtIndexPath:)]) {
        [self.delegate tableView:self willMoveCellAtIndexPath:selectedIndexPath];
    }
    if (_canEdgeScroll) {
        //開啟邊緣滾動(dòng)
        [self jx_startEdgeScroll];
    }
    //每次移動(dòng)開始獲取一次數(shù)據(jù)源
    if (self.dataSource && [self.dataSource respondsToSelector:@selector(dataSourceArrayInTableView:)]) {
        _tempDataSource = [self.dataSource dataSourceArrayInTableView:self].mutableCopy;
    }
    _selectedIndexPath = selectedIndexPath;
    UITableViewCell *cell = [self cellForRowAtIndexPath:selectedIndexPath];
    _tempView = [self jx_snapshotViewWithInputView:cell];
    if (_drawMovalbeCellBlock) {
        //將_tempView通過block讓使用者自定義
        _drawMovalbeCellBlock(_tempView);
    }else {
        //配置默認(rèn)樣式
        _tempView.layer.shadowColor = [UIColor grayColor].CGColor;
        _tempView.layer.masksToBounds = NO;
        _tempView.layer.cornerRadius = 0;
        _tempView.layer.shadowOffset = CGSizeMake(-5, 0);
        _tempView.layer.shadowOpacity = 0.4;
        _tempView.layer.shadowRadius = 5;
    }
    _tempView.frame = cell.frame;
    [self addSubview:_tempView];
    //隱藏cell
    cell.hidden = YES;
    [UIView animateWithDuration:kJXMovableCellAnimationTime animations:^{
        _tempView.center = CGPointMake(_tempView.center.x, point.y);
    }];
}

3.處理手勢(shì)變化,移動(dòng)成功之后用delegate告訴使用者

- (void)jx_gestureChanged:(UILongPressGestureRecognizer *)gesture
{
    CGPoint point = [gesture locationInView:gesture.view];
    NSIndexPath *currentIndexPath = [self indexPathForRowAtPoint:point];
    if (currentIndexPath && ![_selectedIndexPath isEqual:currentIndexPath]) {
        //交換數(shù)據(jù)源和cell
        [self jx_updateDataSourceAndCellFromIndexPath:_selectedIndexPath toIndexPath:currentIndexPath];
        if (self.delegate && [self.delegate respondsToSelector:@selector(tableView:didMoveCellFromIndexPath:toIndexPath:)]) {
            [self.delegate tableView:self didMoveCellFromIndexPath:_selectedIndexPath toIndexPath:currentIndexPath];
        }
        _selectedIndexPath = currentIndexPath;
    }
    //讓截圖跟隨手勢(shì)
    _tempView.center = CGPointMake(_tempView.center.x, point.y);
}

交換數(shù)據(jù)源和cell

- (void)jx_updateDataSourceAndCellFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
    if ([self numberOfSections] == 1) {
        //只有一組
        [_tempDataSource exchangeObjectAtIndex:fromIndexPath.row withObjectAtIndex:toIndexPath.row];
        //交換cell
        [self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
    }else {
        //有多組
        id fromData = _tempDataSource[fromIndexPath.section][fromIndexPath.row];
        id toData = _tempDataSource[toIndexPath.section][toIndexPath.row];
        NSMutableArray *fromArray = [_tempDataSource[fromIndexPath.section] mutableCopy];
        NSMutableArray *toArray = [_tempDataSource[toIndexPath.section] mutableCopy];
        [fromArray replaceObjectAtIndex:fromIndexPath.row withObject:toData];
        [toArray replaceObjectAtIndex:toIndexPath.row withObject:fromData];
        [_tempDataSource replaceObjectAtIndex:fromIndexPath.section withObject:fromArray];
        [_tempDataSource replaceObjectAtIndex:toIndexPath.section withObject:toArray];
        //交換cell
        [self beginUpdates];
        [self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
        [self moveRowAtIndexPath:toIndexPath toIndexPath:fromIndexPath];
        [self endUpdates];
    }
}

4.處理手勢(shì)結(jié)束或取消岩睁,結(jié)束之后通過dataSource代理返回交換后的數(shù)據(jù)源钞脂。

- (void)jx_gestureEndedOrCancelled:(UILongPressGestureRecognizer *)gesture
{
    if (_canEdgeScroll) {
        [self jx_stopEdgeScroll];
    }
    //返回交換后的數(shù)據(jù)源
    if (self.dataSource && [self.dataSource respondsToSelector:@selector(tableView:newDataSourceArrayAfterMove:)]) {
        [self.dataSource tableView:self newDataSourceArrayAfterMove:_tempDataSource.copy];
    }
    if (self.delegate && [self.delegate respondsToSelector:@selector(tableView:endMoveCellAtIndexPath:)]) {
        [self.delegate tableView:self endMoveCellAtIndexPath:_selectedIndexPath];
    }
    UITableViewCell *cell = [self cellForRowAtIndexPath:_selectedIndexPath];
    [UIView animateWithDuration:kJXMovableCellAnimationTime animations:^{
        _tempView.frame = cell.frame;
    } completion:^(BOOL finished) {
        cell.hidden = NO;
        [_tempView removeFromSuperview];
        _tempView = nil;
    }];
}

5.開啟邊緣滾動(dòng)
CADisplayLink實(shí)現(xiàn),1/60秒刷新一次捕儒,和屏幕刷新頻率一樣冰啃。

- (void)jx_startEdgeScroll
{
    _edgeScrollTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(jx_processEdgeScroll)];
    [_edgeScrollTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

6.處理邊緣滾動(dòng)

- (void)jx_processEdgeScroll
{
    [self jx_gestureChanged:_gesture];
    CGFloat minOffsetY = self.contentOffset.y + _edgeScrollRange;
    CGFloat maxOffsetY = self.contentOffset.y + self.bounds.size.height - _edgeScrollRange;
    CGPoint touchPoint = _tempView.center;
    //處理上下達(dá)到極限之后不再滾動(dòng)tableView邓夕,其中處理了滾動(dòng)到最邊緣的時(shí)候,當(dāng)前處于edgeScrollRange內(nèi)阎毅,但是tableView還未顯示完焚刚,需要顯示完tableView才停止?jié)L動(dòng)
    if (touchPoint.y < _edgeScrollRange) {
        if (self.contentOffset.y <= 0) {
            return;
        }else {
            if (self.contentOffset.y - 1 < 0) {
                return;
            }
            [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y - 1) animated:NO];
            _tempView.center = CGPointMake(_tempView.center.x, _tempView.center.y - 1);
        }
    }
    if (touchPoint.y > self.contentSize.height - _edgeScrollRange) {
        if (self.contentOffset.y >= self.contentSize.height - self.bounds.size.height) {
            return;
        }else {
            if (self.contentOffset.y + 1 > self.contentSize.height - self.bounds.size.height) {
                return;
            }
            [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y + 1) animated:NO];
            _tempView.center = CGPointMake(_tempView.center.x, _tempView.center.y + 1);
        }
    }
    //處理滾動(dòng)
    CGFloat maxMoveDistance = 20;
    if (touchPoint.y < minOffsetY) {
        //cell在往上移動(dòng), moveDistance越大移動(dòng)越快
        CGFloat moveDistance = (minOffsetY - touchPoint.y)/_edgeScrollRange*maxMoveDistance;
        [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y - moveDistance) animated:NO];
        _tempView.center = CGPointMake(_tempView.center.x, _tempView.center.y - moveDistance);
    }else if (touchPoint.y > maxOffsetY) {
        //cell在往下移動(dòng), moveDistance越大移動(dòng)越快
        CGFloat moveDistance = (touchPoint.y - maxOffsetY)/_edgeScrollRange*maxMoveDistance;
        [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y + moveDistance) animated:NO];
        _tempView.center = CGPointMake(_tempView.center.x, _tempView.center.y + moveDistance);
    }
}

使用

直接繼承JXMovableCellTableView就可以使用了,然后通過公開屬性進(jìn)行自定義:

/**
 *  長按手勢(shì)最小觸發(fā)時(shí)間扇调,默認(rèn)1.0矿咕,最小0.2
 */
@property (nonatomic, assign) CGFloat gestureMinimumPressDuration;
/**
 *  自定義可移動(dòng)cell的截圖樣式
 */
@property (nonatomic, copy) void(^drawMovalbeCellBlock)(UIView *movableCell);
/**
 *  是否允許拖動(dòng)到屏幕邊緣后,開啟邊緣滾動(dòng)狼钮,默認(rèn)YES
 */
@property (nonatomic, assign) BOOL canEdgeScroll;
/**
 *  邊緣滾動(dòng)觸發(fā)范圍痴腌,默認(rèn)150,越靠近邊緣速度越快
 */
@property (nonatomic, assign) CGFloat edgeScrollRange;

總結(jié)

通過截圖大法實(shí)現(xiàn)以假亂真燃领,自由移動(dòng)截圖產(chǎn)生的"cell"士聪;交換的時(shí)候,首先要交換數(shù)據(jù)源猛蔽,再交換cell剥悟;處理邊緣滾動(dòng)的時(shí)候,需要小心各種邊界條件曼库。
既然UITableViewCell可以自由移動(dòng)了区岗,那么UICollectionViewCell同理也可以實(shí)現(xiàn)。下篇文章就講可移動(dòng)UICollectionViewCell的實(shí)現(xiàn)毁枯。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末慈缔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子种玛,更是在濱河造成了極大的恐慌藐鹤,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赂韵,死亡現(xiàn)場(chǎng)離奇詭異娱节,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)祭示,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門肄满,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人质涛,你說我怎么就攤上這事稠歉。” “怎么了汇陆?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵怒炸,是天一觀的道長。 經(jīng)常有香客問我瞬测,道長横媚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任月趟,我火速辦了婚禮灯蝴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘孝宗。我一直安慰自己穷躁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布因妇。 她就那樣靜靜地躺著问潭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪婚被。 梳的紋絲不亂的頭發(fā)上狡忙,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音址芯,去河邊找鬼灾茁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛谷炸,可吹牛的內(nèi)容都是我干的北专。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼旬陡,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼拓颓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起描孟,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤驶睦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后匿醒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啥繁,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年青抛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旗闽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜜另,死狀恐怖适室,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情举瑰,我是刑警寧澤捣辆,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站此迅,受9級(jí)特大地震影響汽畴,放射性物質(zhì)發(fā)生泄漏旧巾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一忍些、第九天 我趴在偏房一處隱蔽的房頂上張望鲁猩。 院中可真熱鬧,春花似錦罢坝、人聲如沸廓握。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隙券。三九已至,卻和暖如春闹司,著一層夾襖步出監(jiān)牢的瞬間娱仔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工游桩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拟枚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓众弓,卻偏偏與公主長得像恩溅,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谓娃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫脚乡、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,029評(píng)論 4 62
  • 前言 iOS里的UI控件其實(shí)沒有幾個(gè)滨达,界面基本就是圍繞那么幾個(gè)控件靈活展開奶稠,最難的應(yīng)屬UICollectionVi...
    alenpaulkevin閱讀 31,386評(píng)論 9 175
  • 目錄 UICollectionView的定義 UICollectionView快速構(gòu)建GridView網(wǎng)格視圖 U...
    Tr2e閱讀 4,879評(píng)論 2 34
  • 今天是立秋,我調(diào)休了一天捡遍,實(shí)在是這段時(shí)間太累了锌订,想休息一天。 休息了一天画株,什么也沒干辆飘,在家睡了一天。主要是天氣太熱...
    BookCodingLife閱讀 157評(píng)論 0 0
  • 改變我的人 我是一個(gè)很奇怪的人谓传,不怎么喜歡與別人分享自己蜈项,這也是我真心朋友不多的原因吧。往往越是對(duì)朋友好的续挟,對(duì)朋...
    追夢(mèng)的差生閱讀 181評(píng)論 2 0