iOS--一個(gè)高仿微信左滑確認(rèn)刪除的輪子

前言

一個(gè)需求责蝠,要求左滑點(diǎn)擊刪除后出現(xiàn)二次確認(rèn)喷户。和微信一樣毡泻。

調(diào)研結(jié)果如下:

  • iOS11之后,可以通過(guò)對(duì)系統(tǒng)方法進(jìn)行改造的方式實(shí)現(xiàn)室叉。可以看這篇http://www.reibang.com/p/aa6ff5d9f965

  • iOS11之前硫惕,系統(tǒng)在點(diǎn)擊刪除按鈕之后會(huì)自動(dòng)對(duì)擴(kuò)展按鈕進(jìn)行回收茧痕。無(wú)法進(jìn)行那樣的改造。

于是決定自己寫(xiě)一個(gè)

最初參考了一個(gè)16年仿微信左滑的博客http://www.reibang.com/p/dc57e633de51

由于16年的微信與現(xiàn)在的交互差異太大恼除,所以進(jìn)行了大量改造踪旷,只保留了其對(duì)于側(cè)滑菜單的創(chuàng)建以及滑動(dòng)判定的邏輯基礎(chǔ)。

對(duì)其中的bug以及功能實(shí)現(xiàn)方式進(jìn)行優(yōu)化調(diào)整缚柳,基本實(shí)現(xiàn)了現(xiàn)在微信的左滑邏輯功能埃脏。


實(shí)際效果

伸手黨福利,先看效果不滿意直接右上角就好了秋忙。

由于我很懶...所以demo的主體結(jié)構(gòu)基本沒(méi)改彩掐,側(cè)滑菜單創(chuàng)建的邏輯沒(méi)做太多修改。

Demo在文章最后


具體到主要的代碼上

我連demo的文件名都懶得改(當(dāng)然Cell的名字我改了灰追,畢竟我做了三天才做完)堵幽,就更別提界面了...
下面是一些我修改了的地方狗超,如果你想了解的點(diǎn)在我這找不到∑酉拢可以試著查看原作者的文章http://www.reibang.com/p/dc57e633de51

  • 新增了一個(gè)專門(mén)的側(cè)滑容器View

原Demo就是一個(gè)VIew努咐,上面循環(huán)的創(chuàng)建按鈕使用。
由于新版微信需要很多復(fù)雜的交互效果(形變,反彈,確認(rèn)刪除等等)
我新建了一個(gè)KSSideslipContainerView的容器View殴胧。
可以很方便的進(jìn)行二次操作

  • 滾動(dòng)時(shí)收起側(cè)滑菜單

原Demo中側(cè)滑展示時(shí)渗稍,是滑動(dòng)交互式關(guān)閉的。

這里我通過(guò)NSProxy對(duì)tableView的滑動(dòng)代理進(jìn)行攔截

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  
    if (self.target.sideslip) {
        [self.target hiddenAllSideslip];
    }
    
    if ([self.tbDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
        [self.tbDelegate scrollViewWillBeginDragging:scrollView];
    }
    
}
  • 點(diǎn)擊時(shí)收起側(cè)滑菜單

原Demo中是在cell上添加了一個(gè)單擊手勢(shì)進(jìn)行處理

我改為將didSelectRowAtIndexPath一起放在NSProxy代理中進(jìn)行攔截了

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    if (self.target.sideslip) {
        [self.target hiddenAllSideslip];
    }
    
    if ([self.tbDelegate respondsToSelector:@selector(tableView:didSelectRowAtIndexPath:)]) {
        [self.tbDelegate tableView:tableView didSelectRowAtIndexPath:indexPath];
    }
}
  • NSProxy

剛才說(shuō)的攔截器

- (void)setTarget:(UITableView *)target {
    _target = target;
    target.sideslipCellProxy = self; //這里需要讓tableView強(qiáng)引用proxy防止釋放
    self.tbDelegate = target.delegate; //保存tableView原本的delegate团滥,進(jìn)行轉(zhuǎn)發(fā)
    self.tbDataSource = target.dataSource;//保存tableView原本的dataSource竿屹,進(jìn)行轉(zhuǎn)發(fā)
    target.delegate = self; //修改tableView.delegate攔截事件
}

這個(gè)東西會(huì)在每次側(cè)滑容器展示時(shí)嘗試綁定與tableVIew進(jìn)行綁定。當(dāng)然灸姊,它只會(huì)綁定一次

- (void)tryBindProxy {
    UITableView * tableView = [self tableView];
    if ([tableView isKindOfClass:[UITableView class]]) {
        if (![tableView.delegate isKindOfClass:[KTSideslipCellProxy class]]) {
            
            //保證一個(gè)tableView只會(huì)設(shè)置一次proxy
            KTSideslipCellProxy *proxy = [KTSideslipCellProxy alloc];
            proxy.target = tableView; //這里拱燃。proxy的target是weak屬性,并不會(huì)造成循環(huán)引用
        }
    }
}

之后力惯,利用NSProxy的特點(diǎn)碗誉,將未攔截的消息轉(zhuǎn)發(fā)給原本的代理者:

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    id res;
    if ([self.tbDelegate respondsToSelector:aSelector]) {
        res = self.tbDelegate;
    }else if ([self.tbDataSource respondsToSelector:aSelector]) {
        res = self.tbDataSource;
    }
    return res;
}
  • 側(cè)滑容器的動(dòng)畫(huà)

原Demo中側(cè)滑按鈕并沒(méi)有移動(dòng),一直是放在cell的最右側(cè)

我是通過(guò)監(jiān)聽(tīng)cell.contentView將側(cè)滑容器粘到contentView上父晶。

    if ([keyPath isEqualToString:@"frame"]) {
        
        if (self.btnContainView) {
            KS_setX(self.btnContainView, self.contentView.frame.size.width + self.contentView.frame.origin.x);
        }
    }
}

不過(guò)這里是由于另一個(gè)方案有小問(wèn)題哮缺,demo里我有注釋。大佬們可以研究研究

  • 阻尼效果

原Demo中不允許拖拽超過(guò)側(cè)滑容器的長(zhǎng)度诱建,這和微信不太一樣

if (frame.origin.x + point.x <= -(self.btnContainView.totalWidth)) {
    //超過(guò)最大距離蝴蜓,加阻尼
    CGFloat hindrance = (point.x/5);
    if (frame.origin.x + hindrance <= -(self.btnContainView.totalWidth)) {
        frame.origin.x += hindrance;
        cframe.size.width += -hindrance;
        cframe.origin.x += hindrance;
    }else {
        //這里修復(fù)了一個(gè)當(dāng)滑動(dòng)過(guò)快時(shí),導(dǎo)致最初減速時(shí)閃動(dòng)的bug
        frame.origin.x = - self.btnContainView.totalWidth;
        cframe.origin.x = self.contentView.frame.size.width - self.btnContainView.totalWidth;
    }
}else {
    //未到最大距離俺猿,正常拖拽
    frame.origin.x += point.x;
    cframe.origin.x += point.x;
}
  • 抽屜效果與過(guò)度拉伸的形變

側(cè)滑容器以及其上的子View會(huì)根據(jù)最終寬度茎匠,自動(dòng)調(diào)整布局比例

- (void)scaleToWidth:(CGFloat)width {
    CGFloat needExpandWidth = width - self.totalWidth;
    NSUInteger count = _originSubViews.count;
    CGFloat currentX = 0;
    for (int i = 0; i < count; i++) {
        UIView *s = _originSubViews[i];
        CGRect sframe = s.frame;
        sframe.origin.x = currentX;
        CGFloat sneedExpandWidth = (needExpandWidth * [_originWidths[i] floatValue]/_totalWidth);
        sframe.size.width = [_originWidths[i] floatValue] + sneedExpandWidth;
        s.frame = sframe;
        
        //下一個(gè)X起點(diǎn)為上一個(gè)起點(diǎn)+上一個(gè)寬度
        currentX += sframe.size.width;
    }
}
  • 確認(rèn)刪除按鈕的實(shí)現(xiàn)

在點(diǎn)擊側(cè)滑按鈕的代理事件中,允許傳遞一個(gè)View回來(lái)押袍。如果傳遞回了一個(gè)View诵冒,我會(huì)將其放到側(cè)滑容器上,并進(jìn)行布局的適配谊惭。

if ([self.delegate respondsToSelector:@selector(sideslipCell:rowAtIndexPath:didSelectedAtIndex:)]) {
    _nextShowView = [self.delegate sideslipCell:self rowAtIndexPath:self.indexPath didSelectedAtIndex:btn.tag];
    
    /**
        如果有需要繼續(xù)展示的View--一般是確認(rèn)刪除?
        這里會(huì)將其覆蓋到側(cè)滑容器上汽馋,并且重新以新的View作為基礎(chǔ)進(jìn)行布局
     */
    if (_nextShowView) {
        [_btnContainView addSubview:_nextShowView];
        CGRect frame = CGRectMake(0, 0, _nextShowView.frame.size.width, self.contentView.frame.size.height);

        _nextShowView.frame = CGRectMake(self.btnContainView.originSubViews.lastObject.frame.origin.x, 0, _nextShowView.frame.size.width, self.contentView.frame.size.height);
        _nextShowView.hidden = YES;
        
        [UIView animateWithDuration:0.7 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionAllowUserInteraction animations:^{
            _nextShowView.frame = frame;
            _btnContainView.frame = frame;
            _nextShowView.hidden = NO;
            [_btnContainView.subButtons setValue:@(YES) forKeyPath:@"hidden"];
            KS_setX(self.contentView, -KS_getW(_nextShowView));
            [self.btnContainView scaleToWidth:_nextShowView.frame.size.width];
        } completion:^(BOOL finished) {
            [_btnContainView.subButtons setValue:@(NO) forKeyPath:@"hidden"];
        }];
    }
}
  • 修改了原Demo內(nèi)存泄漏的問(wèn)題

問(wèn)題出在這

    if (!_tableView) {
        id view = self.superview;
        while (view && [view isKindOfClass:[UITableView class]] == NO) {
            view = [view superview];
        }
        _tableView = (UITableView *)view;
        _tableViewPan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(tableViewPan:)];
        _tableViewPan.delegate = self;
        [_tableView addGestureRecognizer:_tableViewPan];
    }
    return _tableView;
}

修改后

- (UITableView *)tableView {
    id view = self.superview;
    while (view && [view isKindOfClass:[UITableView class]] == NO) {
        view = [view superview];
    }
    if ([view isKindOfClass:[UITableView class]]) {
        return view;
    }else {
        return nil;
    }
}

最后

這個(gè)需求整整搞了我三天,還是在修改別人Demo的基礎(chǔ)上圈盔,沒(méi)成想這么復(fù)雜...
不過(guò)好在總算是弄完了

Demo可以自取

當(dāng)然豹芯,如果能點(diǎn)個(gè)贊或者給個(gè)star我也算沒(méi)白忙活

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市驱敲,隨后出現(xiàn)的幾起案子铁蹈,更是在濱河造成了極大的恐慌,老刑警劉巖众眨,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件握牧,死亡現(xiàn)場(chǎng)離奇詭異容诬,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)沿腰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)览徒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人颂龙,你說(shuō)我怎么就攤上這事习蓬。” “怎么了措嵌?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵友雳,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我铅匹,道長(zhǎng),這世上最難降的妖魔是什么饺藤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任包斑,我火速辦了婚禮,結(jié)果婚禮上涕俗,老公的妹妹穿的比我還像新娘罗丰。我一直安慰自己,他們只是感情好再姑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布萌抵。 她就那樣靜靜地躺著,像睡著了一般元镀。 火紅的嫁衣襯著肌膚如雪绍填。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天栖疑,我揣著相機(jī)與錄音讨永,去河邊找鬼。 笑死遇革,一個(gè)胖子當(dāng)著我的面吹牛卿闹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萝快,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼锻霎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了揪漩?” 一聲冷哼從身側(cè)響起旋恼,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎氢拥,沒(méi)想到半個(gè)月后蚌铜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體锨侯,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年冬殃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了囚痴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡审葬,死狀恐怖深滚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涣觉,我是刑警寧澤痴荐,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站官册,受9級(jí)特大地震影響生兆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜膝宁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一鸦难、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧员淫,春花似錦合蔽、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至圣蝎,卻和暖如春刃宵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捅彻。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工组去, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人步淹。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓从隆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親缭裆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子键闺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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

  • 一個(gè)基督徒問(wèn)牧師說(shuō):“上古時(shí)代,上帝為什么用洪水滅世俺和铡辛燥?”牧師說(shuō):“上帝用洪水滅世,并不是說(shuō)明上帝殘忍,而是說(shuō)明上...
    海王星1984閱讀 378評(píng)論 0 0
  • 花了2萬(wàn)多買(mǎi)家具 花了100多買(mǎi)衣服!A穸肌待锈!其中有30塊是一大堆泳裝的錢(qián)(??????)?? 老板娘與我聊的賊好~~...
    大王我要去巡山閱讀 242評(píng)論 3 1
  • 《魔鬼數(shù)學(xué)》| 秦瞳解讀 關(guān)于作者 本書(shū)的作者喬丹·艾倫伯格,一個(gè)數(shù)學(xué)界的超級(jí)明星嘴高,任教于美國(guó)威斯康星大學(xué)數(shù)學(xué)系竿音,...
    艷云1314閱讀 549評(píng)論 0 0
  • 很忙碌的一天呀,同時(shí)也是特別充實(shí)的一天呢拴驮。和好友一起分享我做的素食午餐(就是時(shí)間晚了點(diǎn))春瞬。為古琴課的開(kāi)班準(zhǔn)備的琴也...
    如是一一閱讀 190評(píng)論 0 0