仿微信左滑刪除


Demo地址:https://github.com/SPStore/WeChatDelete

開門見山,先上微信原生效果圖


 
未命名.gif

這個效果也只有從iOS11開始,微信才有的,iOS11之前點擊刪除,底部會彈出一個是否確認刪除的提示框,既然是iOS11才有,那么微信必然用了iOS11的新特性。這個功能實現起來非常非常簡單,不用自定義cell,一個UILabel就可以搞定,雖然簡單,但是想到這個方案的過程當中遇到了許多阻礙,我會一個一個為大家排解。

切入正題

在iOS11 以后,我們要實現左滑刪除功能,方法如下:

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } - (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)){ UIContextualAction *deleteRowAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:[NSString stringWithFormat:@"刪除"] handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { }]; UIContextualAction *remarkAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"備注" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { }]; UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:@[deleteRowAction,remarkAction]]; config.performsFirstActionWithFullSwipe = NO; return config; } 

好的,我們已經實現了左滑出現兩個按鈕,一個是備注,一個是刪除,如圖:


 
31FA7CA81C0FE5F36441855AEC5790A1.jpg

我們渲染一下層級結構圖,發現iOS11之后層級如下:

 
31837F3E48B9D65C4BE06F230AE96145.jpg

截圖中紅方框款起來的就是左滑按鈕的層級結構,發現有一個 UISwipeActionPullView,這個view加在了UITableView上,該view有1個子控件: UISwipeActionStandardButton,在這個 button里,系統插入了一個UIView,我猜想這個view有2個功能:一個是方便添加毛玻璃效果,一個是要實現系統的使勁左滑后action變長效果(備注:在iOS11之前,左滑按鈕是加在cell上的)。

 

廢話

首先,我想大家和我一樣,實現這個微信左滑刪除效果,第一個想到的,就是在點擊刪除按鈕的block塊當中,改變刪除action的標題,將其title改為“確認刪除”,但是很遺憾,沒有用,你改變之后,系統內部會再重置一次,會覆蓋掉你的修改,既然系統會重置,那么我就想,我用GCD函數dispatch_after延時0.1秒修改呢,這樣就會先走系統的修改,再走我的修改,這樣不就能實現了嗎?是的,的確如我所料,延時0.1秒能修改成功,但是,修改為“確認刪除”文字后,當你的手指按下“確認刪除”按鈕的那一刻,會瞬間變一下“刪除”,然后再變回“確認刪除”,所以此路行不通,而且這樣做,最多能修改文字,不能修改“刪除”按鈕的寬度。

我們仔細研究一個重要問題:

當我們在創建一個action的時候,是這樣創建的:

    UIContextualAction *deleteRowAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:[NSString stringWithFormat:@"刪除"] handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { }]; 

其中有一個參數是block,這個block就是本文的重點研究對象,我們仔細看看這個block,有3個參數:

參數1:action,這個參數就是創建的action對象

參數2:sourceView,這個view非常重要,當action的title不為空時,sourceView是一個UILabel, 當action的title為空時,sourceView是一個UIButton(實際上是UISwipeActionStandardButton),那既然控件都給我們了,我想大家肯定也是聰明人,在這個地方必有文章可做。

參數3 :completionHandler,這個參數是一個block,這個block是一個細節了,不知道大家有沒有注意到,在iOS11之前,只要你點擊了左滑出現的任意一個按鈕,cell都會退出編輯,也就是左滑按鈕會消失,iOS11之后不會了,如果你想實現這個效果,只要回調一下completionHandler即可,參數是一個 BOOL 值,傳YES和NO的區別是:傳NO,系統只退出編輯,傳YES ,如果是刪除樣式,系統會自動為你做刪除cell操作。

3個參數講完了,我們把重點放在第二個參數sourceView上

解決方案:

我的思路是,創建一個UILabel,點擊刪除按鈕時,將該Label加在sourceView最頂層父view上,即加在前面提到過的UISwipeActionPullView上,同時以UIView動畫改變這個Label的x值和width,核心源碼如下:

// 先創建一個UILabel - (UILabel *)sureDeleteLabel { if (!_sureDeleteLabel) { UILabel *sureDeleteLabel = [[UILabel alloc] init]; sureDeleteLabel.text = @"確認刪除"; sureDeleteLabel.textAlignment = NSTextAlignmentCenter; sureDeleteLabel.textColor = [UIColor whiteColor]; sureDeleteLabel.backgroundColor = [UIColor colorWithRed:255.0/255.0 green:56.0/255.0 blue:50.0/255.0 alpha:1.0]; sureDeleteLabel.userInteractionEnabled = YES; _sureDeleteLabel = sureDeleteLabel; } return _sureDeleteLabel; } - (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)){ UIContextualAction *deleteRowAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:[NSString stringWithFormat:@"刪除"] handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { // 核心代碼 UIView *rootView = nil; // 這個根view指的是UISwipeActionPullView,最上層的父view if ([sourceView isKindOfClass:[UILabel class]]) { rootView = sourceView.superview.superview; self.sureDeleteLabel.font = ((UILabel *)sourceView).font; } self.sureDeleteLabel.frame = CGRectMake(sourceView.bounds.size.width, 0, sourceView.bounds.size.width, sourceView.bounds.size.height); [sourceView.superview.superview addSubview:self.sureDeleteLabel]; [UIView animateWithDuration:0.7 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseInOut animations:^{ CGRect labelFrame = self.sureDeleteLabel.frame; labelFrame.origin.x = 0; labelFrame.size.width = rootView.bounds.size.width; self.sureDeleteLabel.frame = labelFrame; } completion:^(BOOL finished) { }]; }]; UIContextualAction *remarkAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"備注" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { }]; UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:@[deleteRowAction,remarkAction]]; config.performsFirstActionWithFullSwipe = NO; return config; } 

到這里,基本的效果已經實現了,但是點擊事件又成了一個很頭疼的問題,我們要點擊 確認刪除響應我們的點擊事件呀,但是造化弄人,你在確認刪除Label上加一個tap手勢,即便交互被打開,這個tap手勢事件並不會被觸發,即便把UILabel換成UIButton也不會觸發按鈕點擊事件,觸發的依然是系統自帶的刪除按鈕事件和備注事件,這個地方我想了很久,系統一定是重寫了UISwipeActionPullView- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;方法,在這個方法對開發者自主添加的控件做了過濾處理

點擊事件解決方案

不會響應我們自己的點擊事件,但是會響應系統的自帶的“刪除”按鈕事件和“備注”事件,那么我們何嘗不直接用系統自帶的呢,當“確認刪除”Label顯示出來的時候,點擊“備注”也實現“刪除”操作,完整源碼如下:

- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)){ UIContextualAction *deleteRowAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:[NSString stringWithFormat:@"刪除"] handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { if (self.sureDeleteLabel.superview) { // 說明確認刪除Label顯示在界面上 NSLog(@"確認刪除"); } else { NSLog(@"顯示確認刪除Label"); // 核心代碼 UIView *rootView = nil; // 這個根view指的是UISwipeActionPullView,最上層的父view if ([sourceView isKindOfClass:[UILabel class]]) { rootView = sourceView.superview.superview; self.sureDeleteLabel.font = ((UILabel *)sourceView).font; } self.sureDeleteLabel.frame = CGRectMake(sourceView.bounds.size.width, 0, sourceView.bounds.size.width, sourceView.bounds.size.height); [sourceView.superview.superview addSubview:self.sureDeleteLabel]; [UIView animateWithDuration:0.7 delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseInOut animations:^{ CGRect labelFrame = self.sureDeleteLabel.frame; labelFrame.origin.x = 0; labelFrame.size.width = rootView.bounds.size.width; self.sureDeleteLabel.frame = labelFrame; } completion:^(BOOL finished) { }]; } }]; UIContextualAction *remarkAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"備注" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) { // 如果確認刪除Label顯示在界面上,那么本次點擊備注的區域響應確認刪除按鈕事件 if(self.sureDeleteLabel.superview) { NSLog(@"確認刪除"); } else { NSLog(@"備注"); } }]; UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:@[deleteRowAction,remarkAction]]; config.performsFirstActionWithFullSwipe = NO; return config; } 

最終效果圖:


 
未命名.gif

其余細節

  • 如何改變左滑動刪除按鈕的文字顏色和字體大小?
    系統並沒有為我們提供改變文字顏色和字體大小的屬性,沒辦法,我們只能獲取控件達到我們的目的,那么我們在哪里獲取這個控件呢?tableView有一個代理方法:- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath,我們的手指將要左滑時,就會觸發這個代理方法,只要在這個代理方法遍歷tableView子控件就能拿到左滑動按鈕,源碼如下:
- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath { NSLog(@"將要開始編輯cell"); for (UIView *subView in tableView.subviews) { if ([subView isKindOfClass:NSClassFromString(@"UISwipeActionPullView")]) { for (UIView *childView in subView.subviews) { if ([childView isKindOfClass:NSClassFromString(@"UISwipeActionStandardButton")]) { UIButton *button = (UIButton *)childView; button.titleLabel.font = [UIFont systemFontOfSize:18]; [button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; } } } } } 

改變后的效果圖:


 
397F27E7F540E283EA3680A62051C965.jpg
  • 如何去除滑動手勢滿屏時第一個action變長效果
    大家有沒有發現,當你的手指左滑cell,使勁往左滑動后,最右邊的按鈕(稱為第一個按鈕)會變長,並且送手后直接回調action的 block,想要去除這個效果很簡單,只需要設置UISwipeActionsConfiguration的屬性performsFirstActionWithFullSwipe為NO即可,如圖:

     
    BD977FFFE08279AB4DFDBA7D5DD007CA.jpg

     

  • 如何實現當左滑按鈕已經出現時,再次左滑則移除自己添加的“確認刪除”Label
    這里我並沒有找到非常棒的方案,但是也實現了,我是獲取tableView的左滑手勢,然后給該手勢再添加一個方法,如:

    // 獲取系統左滑手勢 for (UIGestureRecognizer *ges in self.tableView.gestureRecognizers) { if ([ges isKindOfClass:NSClassFromString(@"_UISwipeActionPanGestureRecognizer")]) { [ges addTarget:self action:@selector(_swipeRecognizerDidRecognize:)]; } } // 當左滑按鈕已經出現時,再次左滑則移除“確認刪除”控件 - (void)_swipeRecognizerDidRecognize:(UISwipeGestureRecognizer *)swip { if (_sureDeleteLabel.superview) { [_sureDeleteLabel removeFromSuperview]; _sureDeleteLabel = nil; } } 
  • 如何去除左滑后再使勁右滑的反彈效果
    我們發現系統自帶的,左滑后,再緊接着使勁右滑,會有反彈效果,微信是沒有的,我的解決辦法是再上面的那個手勢方法里強制將cell的x值改為0,這個方案個人覺得不是很好,但是目前我只知道這種解決方案,如果你有更好的辦法,可以給我留言, 實現如下:
- (void)_swipeRecognizerDidRecognize:(UISwipeGestureRecognizer *)swip { if (_sureDeleteLabel.superview) { [_sureDeleteLabel removeFromSuperview]; _sureDeleteLabel = nil; } CGPoint currentPoint = [swip locationInView:self.tableView]; for (UITableViewCell *cell in self.tableView.visibleCells) { if (CGRectContainsPoint(cell.frame, currentPoint)) { if (cell.frame.origin.x > 0) { cell.frame = CGRectMake(0, cell.frame.origin.y,cell.bounds.size.width, cell.bounds.size.height); } } } } 

Demo地址:https://github.com/SPStore/WeChatDelete


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM