Hack 蘋果系統 Api 實現 iOS TableViewCell 側滑方案

折騰範兒_味精發表於2019-03-04

本篇不是多複雜的東西,只是蘋果的官方 Api 支援能力不足,於是通過 Hack 的方案來補全蘋果官方 Api ,但是每當新系統出來,iOS 內部實現經常發生變更,導致以前探索的 Hack 方案會失效,得重新絞盡腦汁 Hack,因此梳理留做記錄

前言:

TableView Cell 的側滑功能,一直是一個在 iOS 下比較有特色並且被廣泛使用的一種互動

在日常的開發中也總會面臨產品或者互動提出的種種側滑定製的需求

undefined

早先的時候,側滑相關的蘋果官方 Api 的定製能力很差,但隨著 iOS 的版本迭代,相關 Api 也在調整擴充中,逐步在開放更多定製能力,但即使是 iOS 12 系統 Api 也並不能做到很容易滿足產品互動的定製需求。

因此在實現側滑的定製效果上,始終存在這兩種思路

  • 與 iOS UIKit Api 鬥智鬥勇,Trick & Hack 外加各種私有 Api 無所不用其極
  • 完全放棄 UIKit 的 canEdit 以及相關手勢控制,完全自定義實現

與 UIKit 鬥智鬥勇

UITableView 的基本側滑能力

開啟編輯模式

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
複製程式碼

當這個 delegate 返回 YES 的時候,就算開啟了當前 indexPath Cell 的編輯態,但也要注意編輯態包含2個模式

  • 左側/右滑模式

該模式通過 [tableview setEditing:YES animated:YES]開啟

undefined

  • 右側/左滑模式

該模式左滑cell開啟

undefined

只設定 canEditRowAtIndexPath 為 YES 的時候會預設同時開啟左側右側2種模式,並且像上面的截圖一樣,左側是一個紅圈 - 號,右滑是刪除按鈕,並且點選左側的 紅圈 - 號 會自動左滑展現出右側刪除按鈕

進入編輯模式

[tableview setEditing:YES animated:YES]
複製程式碼

可以通過對整個 tableview 呼叫這個方法進入編輯模式,但注意這個方法只能進入左側/右滑的編輯模式。並且會預設對所有 canEdit 的 indexPath 同時進入左側/右滑編輯模式,看到紅圈 - 號,如果想選定 cell 進入編輯模式,那就需要進一步控制 canEditRowAtIndexPath 這個 delegate 了。

想要進入右側/左滑的編輯模式,只需要對 cell 進行左滑的手勢操作就能進入。但如果不想通過使用者的手勢輸入,而是通過程式碼的方式進入右側/左滑的編輯模式則會很蛋疼,後面會講到

提交編輯模式

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
複製程式碼

無論是左側按鈕還是右側按鈕,使用者的點選操作都可以通過這個 delegate 來接收,通過 editingStyle 來區別來源 indexPath 來識別 cell

編輯模式樣式狀態

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
複製程式碼

如果想進一步使用系統 Api 控制編輯狀態,那麼就可以通過上面的 delegate ,UITableViewCellEditingStyle 有三個值,但其實可以組合出4個狀態

  • UITableViewCellEditingStyleNone

即便 canEdit 是 YES,當 CellEditingStyleNone 的時候,Cell 也不會允許你左滑手勢出現右側刪除按鈕,並且通過 tableview 的 setEditing 方法開啟左側紅圈按鈕的時候,你會發現 cell 確實向右滑動了,但紅圈按鈕已經消失,並且無法點選開啟刪除按鈕

undefined

  • UITableViewCellEditingStyleDelete (這個狀態是預設返回值)

這個狀態允許左滑手勢出現刪除按鈕,同時也允許 setEditing 出現紅圈,紅圈點選後出現刪除按鈕

  • UITableViewCellEditingStyleInsert

這個狀態不允許左滑手勢出現刪除按鈕,同時當 setEditing 的時候不出現紅圈了,而是一個綠色 + 號,綠圈點選後不會出現刪除按鈕

undefined

  • UITableViewCellEditingStyleInsert | UITableViewCellEditingStyleDelete

這個狀態就有意思了,Insert | Delete ,但這個狀態下依然是不允許左滑手勢出現刪除按鈕,區別是當 setEditing 的時候不出現紅圈or綠圈了,而是一個核取方塊,核取方塊點選後是操作複選狀態,不會出現刪除按鈕

undefined

編輯模式特殊樣式狀態 - 移動

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath;

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
複製程式碼

上面2個 delegate 可以控制 TableView 是否可以進行 cell 的手勢拖動,這裡不進一步展開手勢拖動如何實現,但 canMove 為 YES 後,會影響編輯模式的效果

undefined

如圖所示,當 setEditing 後,cell 的右側會出現一個 cell 可拖動的 icon ,此時整個cell的左滑刪除手勢已經徹底被禁止,當發生手勢的時候,就會進入把 cell 拿起來的 move 模式

但 setEditing 的左側按鈕視 CellEditingStyle 不同,表現情況不變,當 CellEditingStyleDelete 時,點選紅圈按鈕,依然會自動左滑出現刪除按鈕(手勢已被禁止,開啟刪除按鈕可以通過這個方式)

- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath
複製程式碼

在有些場景下,希望 setEditing 只進入純粹的 move 模式,不想要左側的按鈕怎麼辦,可以通過這個 delegate ,這個 delegate 會在 canMove 模式下詢問是否要進行 cell 左側的偏移,預設返回 YES,如果設為 NO 就會不顯示 cell 左側按鈕,cell也不偏移了

undefined

這個 delegate 有著很苛刻的限制條件

  • CellEditingStyle 必須為 CellEditingStyleNone

只有 CellEditingStyleNone 的時候才可以實現 cell 左側按鈕不顯示,cell也不偏移

PS:不要妄想通過這個 delegate 禁掉 cell 的左側編輯按鈕,從而讓 setEditing 直接進入右側按鈕展現模式 ╮(╯_╰)╭

右側按鈕的Title定製

- (nullable NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
複製程式碼

在早先版本(iOS 8以前)蘋果並未開放多右側按鈕的 Api ,因此右側按鈕幾乎就是為了刪除而定製的,因此蘋果給這種右側按鈕的 style 命名為 CellEditingStyleDelete

也因此蘋果只提供了一個 delegate 來定製這個按鈕的文案

這個 delegate 會讓右側按鈕自適應文字的寬度

UITableView 深度定製側滑按鈕(iOS 11以前)

很明顯,側滑按鈕只允許有一個,只能換文字,不能換顏色,不能加圖表,這麼多限制說出去,告訴產品和互動我們實現不了,這肯定會被拍死,於是乎,iOS 8 之後,蘋果開放了更多定製

- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{
    WEAKSELF(self)
    UITableViewRowAction *delAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:@"刪除" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
        // todo xxxxx
		
		......
		
        NSLog(@"點選了刪除");
        tableView.editing = NO;
    }];
    delAction.backgroundColor = [UIColor colorWithRGB:0xF4333C];
	
	UITableViewRowAction *hahaAction = [UITableViewRowAction rowActionWithStyle:UITableViewRowActionStyleNormal title:@"哈哈" handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
        // todo xxxxx
		
		......
		
		NSLog(@"點選了哈哈");
        tableView.editing = NO;
    }];
    hahaAction.backgroundColor = [UIColor colorWithRGB:0x108EE9];
	
	return @[delAction,hahaAction]
}
複製程式碼

終於蘋果開放了這個新的 Api ,可以支援向右側設定多個按鈕,並且每個 UITableViewRowAction 就代表著一個按鈕,可以設定 rowActionWithStyle (3個列舉值是改變按鈕配色),可以設定 title ,可以設定 backgroundColor。沒錯就只能定製這麼多。

互動說要有 icon !

互動說要有 icon !

互動說要有 icon !

重要的事情說三遍

Hack 系統 UIView 層級,圖示/字型等深度定製

我們先分析一下 UITableView Cell 的 UIView 層級

undefined

從圖中可以看出來右滑按鈕的介面層級是

iOS 8 - iOS 10

  • UITableView
    • UITableViewCell
      • UITableViewCellContentView (我們熟知的 cell 的內容 view)
      • UITableViewCellDeleteConfirmationView
        • UITableViewCellActionButton (側滑按鈕)

那麼我們可以在恰當的時機 layoutSubview 的時候,獲取到 UITableViewCell ,遍歷他的 subview 找到 _UITableViewCellActionButton 這個 Button 類,進行人為的修改

-(void)layoutSubviews{
    [super layoutSubviews];
    for (UIView *subView in self.subviews) {
        if ([subView isKindOfClass:NSClassFromString(@"UITableViewCellDeleteConfirmationView")]) {
            for (UIButton *btn in subView.subviews) {
                if ([btn isKindOfClass:[UIButton class]]) {
				// 很setImage好,你已經拿到了這個btn了,可以為所欲為了
				// todo
                }
            }
        }
    }
}
複製程式碼

這種遍歷檢視層級的方法在 iOS 11 以後有問題!

這種遍歷檢視層級的方法在 iOS 11 以後有問題!

這種遍歷檢視層級的方法在 iOS 11 以後有問題!

重要的事情說三遍

為所欲為:

需要說明的是,簡單的直接操作btn,可以對btn呼叫下面這些 Api 來進行 icon 的 UI 定製

  • btn.titleLabel setFont:
  • btn setTitleColor:forState:
  • btn setImage:forState:

但是直接這樣設定,會導致 image 與文字並排出現,位於文字左邊,並且有一定的重疊,這時候你可能會想到下面這倆 Api

  • btn.titleEdgeInsets
  • btn.imageEdgeInsets

但實際操作後你會發現,imageEdgeInsets 可以正常的工作,但是 title 的位置始終不能如你所願按你的意願進行更改,通過對 titleLabel 寫個監聽可以發現,layoutSubview 之後,系統還幹了別的事情,重新進行了校正,所以想要更改 title 的位置有兩個方案

  • 對 title 的 frame 進行 kvc 監聽,從而在系統座標校正後,改回你想要的位置
  • dispatch_after 個0.1秒,再手動修改 title 的 frame (╮(╯_╰)╭)

undefined

為所欲為2.0:

如果互動與設計想要這個頁面更加花哨,更加風騷,更加的定製化怎麼辦?

整個 btn 都給你了,你可以完全構造一個 customView ,frame 大小與 button 完全一致,直接蓋在 btn 上,然後這個頁面就徹底為所欲為了,想加什麼 UI 加什麼 UI,但你需要注意以下幾個事情

  • btn 的 width 是由 UITableViewRowAction 的 Title 文字來決定的
  • Title 會被系統維護在居中的位置,你蓋在上面的 customView 可以考慮 center 與 Title保持一致

呼叫私有 Api 動態展現/收起左滑動畫

互動來提新需求了,互動說怕有人不知道可以左滑,專門設計一個按鈕 ... 點了這個按鈕,自動幫使用者左滑出來。總結一下需求是:希望不用通過使用者的左滑手勢,就能讓cell出現動畫,左滑滾動出右側按鈕

尋遍所有 tableview & tableview cell 的 Api 都沒有發現可以通過程式碼觸發 cell 左滑,從而展現右側按鈕的方法。唯一比較接近的方法是 [tableview setEditing:YES animated:YES]。但這個方法有個問題在上文提到過,setEditing 會第一次進入右滑並展現左側紅圈按鈕的狀態,只有再次點選紅圈按鈕,才能進入左滑展現右側按鈕。換句話說,我可以用 setEditing:NO 來收起左滑動畫關閉編輯模式,但展現左滑還需想辦法。

各種查詢方案後發現系統有2個私有 Api 可以實現,先對 tableview 呼叫 _endSwipeToDeleteRowDidDelete 傳入 NO 來讓整個 tableview 因為 trick 呼叫導致的狀態清零,然後在對特定的 cell 呼叫 setShowingDeleteConfirmation 傳入 YES 來讓 cell 進行左滑動畫,從而展現右側按鈕。

  • 展現左滑動畫:2個私有 Api 的呼叫
  • 收起左滑動畫:setEditing:NO

補充:既然是私有 Api ,如何呼叫自然是 runtime 了,不管你是 perfermSelector 還是 Object_msgSend 怎麼具體的呼叫我就不細說了,但千萬要注意一定要把 selector 的字串進行混淆

這個方法在 iOS 11 以後有問題!

這個方法在 iOS 11 以後有問題!

這個方法在 iOS 11 以後有問題!

重要的事情說三遍

UITableView 深度定製側滑按鈕(iOS 11以後)

- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath 
- (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
複製程式碼

蘋果彷彿聽到了群眾的呼聲,群眾對側滑按鈕上面加 icon 的呼聲,於是在 iOS 11 之後加入了新的 Api 來解決這個問題,而且還特別開心的一口氣開放了2,支援了左滑和右滑的2種手勢,簡單的實現如下

- ( UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath  API_AVAILABLE(ios(11.0)){

    if (@available(iOS 11.0, *)) {
        UIContextualAction *deleteRowAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"刪除" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
            //響應事件在這裡操作
        }];
        UIImage *image = [UIImage imageNamed:@"xxx"];
        [deleteRowAction setImage:image];
        deleteRowAction.backgroundColor = [UIColor redColor];

        UIContextualAction *hahaRowAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"哈哈" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
            //響應事件在這裡操作
        }];
        editRowAction.image = [UIImage imageNamed:@"xxx"];
        editRowAction.backgroundColor = [UIColor blueColor];
        UISwipeActionsConfiguration *config = [UISwipeActionsConfiguration configurationWithActions:@[deleteRowAction,editRowAction]];
        //設定全屏滑動時不自定響應事件
        config.performsFirstActionWithFullSwipe = false;
        return config;
    }else{
        return nil;
    }
}

複製程式碼

那麼問題來了

  • UIContextualAction 和 iOS 10 以前的 UITableViewRowAction 他倆是什麼關係?可以共存麼?
  • leadingSwipeActionsConfigurationForRowAtIndexPath 是從左往右滑漏出左邊的側滑按鈕,那麼和 setEditing 出現的左側紅圈按鈕,他倆是什麼關係?可以共存麼?

iOS 11 新老 Api 共存關係

整個 UISwipeActionsConfiguration 的 iOS 11 新 Api 都是可以和過去的 setEditing & UITableViewRowAction 共存,只不過蘋果推薦使用新 Api 老的 UITableViewRowAction 在未來可能會被拋棄,我們來看一下他倆的共存狀態。

讓我們同時用2個 delegate 實現側滑 刪除 按鈕 哈哈 按鈕,只不過老 UITableViewRowAction 用的是紅灰配色,新 UISwipeActionsConfiguration 用的是紅藍配色

  • 當我們直接用手勢進行左滑操作
    • cell 出現了紅藍配色的側滑按鈕

undefined

  • 我們在 didselect 的時候 進行 setEditing:YES
    • cell 出現了左側 紅圈按鈕
    • 點選紅圈按鈕 cell 出現了紅灰配色的側滑按鈕

undefined

其實大家還可以把 trailingSwipe 換成 leadingSwipe 的右滑,來看看 leadingSwipe 與 setEditing 的 紅圈按鈕 的衝突情況,我就不詳細展開了

總結如下:

  • 當實現了 leadingSwipe or trailingSwipe 的新 Api delegate 後
    • 無論左滑右滑,都會執行新 Api 的 UISwipeActionsConfiguration 結果
  • 當沒有實現新 Api,於是只走舊 editActionsForRowAtIndexPath 的 Api
    • 展現 UITableViewRowAction 的結果
  • 當操作 setEditing:YES 後
    • cell 會遵循舊 editActionsForRowAtIndexPath 與 UITableViewCellEditingStyle 進行展現

Hack 系統 UIView 層級 新-為所欲為

新 Api 確實提供了增加 icon 的方法,但通過上面的截圖發現,icon 與 title 的位置還是不夠自由,更何況我們還有更深度的任意定製互動的需求,所以我們依然需要為所欲為,然而!舊的為所欲為方案不管用了。

老辦法,分析一波系統 View 層級

undefined

新的系統 View 層級是這樣的

  • UITableView
    • UITableViewCell
      • UITableViewCellContentView
    • UISwipeActionPullView
      • UISwipeActionStandardButton

於是問題找到了,之前在 cell 層級之下的用來放置側滑按鈕的 UITableViewCellDeleteConfirmationView 已經沒了,新的側滑按鈕並沒有放在 cell 層級之下,而是提升到了 cell 的平級,tableview 之下了,新的類名叫 UISwipeActionPullView,並且無論你用的新 Api 還是老 Api ,側滑按鈕的層級都是這樣,所以以前那套 Hack 查詢 View 的方法在 iOS 11 不適用了,得更新了。

首先不能再 tableview cell 的 layoutSubview 來進行這個操作了,而是應該換到 tableview 的 layoutSubview 中進行

-(void)findSwipeCell{
    if (@available(iOS 11.0, *))
    {
        // UITableView -> UISwipeActionPullView
        for (UIView *subview in self.tableView.subviews)
        {
            if ([subview isKindOfClass:NSClassFromString(@"UISwipeActionPullView")] && [subview.subviews count] >= 1)
            {
                for (UIView * button in subview.subviews) {
                    if ([button isKindOfClass:[UIButton class]]) {
						UIButton *bt = (UIButton *)button;
						// 為所欲為把
						// todo
						...
                    }
                }
            }
        }
    }
}

複製程式碼

為所欲為3.0

嗯,你已經找到 button 了,又可以為所欲為了

動態展現/收起左滑動畫 (老辦法私有 Api 失效...

看來 iOS 11 對 tableview 內部側滑按鈕的方案改動還真是非常大,不僅僅介面層級失效了,連私有 Api 動態展現側滑按鈕都失敗了。想盡一切辦法,各種搜尋,有且僅有一篇文章可以勉強實現,並且還有瑕疵。

點選Cell上按鈕出現Cell的左滑刪除編輯介面

大家還記得我在文章中反覆提到的左側 紅圈按鈕 麼?

Hack 蘋果系統 Api 實現 iOS TableViewCell 側滑方案

這篇文章的思路其實說的有點多,如果去掉一些細枝末節的邏輯,簡化版就是如下(這思路真是野...)

  • setEditing: YES 讓 cell 進入編輯態,然後同步進入下一步
  • 通過查詢 cell 的 UITableViewCellEditControl 類找到紅圈button
  • 直接呼叫這個 button 的 sendActionsForControlEvents 程式碼觸發點選事件
  • setEditing: NO 恢復常態
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//    [tableView setEditing:YES animated:YES];
    [tableView setEditing:YES animated:YES];
    BLListCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    
    for (UIView *subview in cell.subviews){
        if ([NSStringFromClass([subview class]) isEqualToString:@"UITableViewCellEditControl"]){
            [(UIControl *) subview sendActionsForControlEvents:UIControlEventTouchUpInside];
        }
    }
    
    
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}
複製程式碼

這個辦法目前為止還存在問題,如何在點選後收回側滑按鈕回覆常態,這個問題即便是文章原文也並沒有解決:

  • 如果通過這種方法展示了左滑動畫,漏出右側按鈕
  • 使用者再次點選 tableview cell 的時候,不會觸發任何 cell 的 contentview 的點選事件,也不會觸發 didselect (本意是想在這些事件裡執行 setEditing: NO)
    • 而是會直接進入系統操作,右滑漏出左側紅圈按鈕
  • 使用者不再次點選 tableview cell,而是使用者在手動右滑,把cell滑回去
    • 無法做到恢復 setEditing: NO 的狀態,而是右滑漏出左側 紅圈按鈕

其實我們需要的就是一個時機,來讓程式碼執行 setEditing: NO 就可以收回側滑按鈕,迴歸正常 cell 狀態,但眼下根本無法捕獲這個時機,cell 的didselect,使用者的點選觸控都無效。

為什麼會這樣我們分析一下原因,所有的問題都發生在 左滑動畫,漏出右側按鈕 這個狀態,所以在這個狀態下我們觀察一下 cell 的檢視層級

undefined

納尼!TableViewCell 的 userInteractionEnabled 居然在這種狀態下是 NO !我們寫個 KVO 來觀察一下是不是真的這樣,如果是,我再把他置回來

// 註冊 KVO        
[self addObserver:self forKeyPath:@"userInteractionEnabled" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
        

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if([keyPath isEqualToString:@"userInteractionEnabled"])
    {
        BOOL s = [[change objectForKey:@"new"] boolValue];
        if (!s) {
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                self.userInteractionEnabled = YES;
            });
        }
    }
}
複製程式碼

試了一下果然!只要進入 左滑動畫,漏出右側按鈕 都會監聽到系統設定 cell 的 userInteraction 為 NO ,然而我還是想簡單了,即便我把他設定回 YES ,依然沒有任何的卵用(鬼知道蘋果的程式設計師裡面寫的是怎麼樣的程式碼),即便我設定了 YES ,整個 cell 依然接受不到任何的touch事件

  • cell 重寫了 touchBegin 發現在這個狀態下不會被呼叫,即便 userInteraction 為 YES
  • tableview 重寫了 touchBegin 發現在這個狀態下不會被呼叫,即便 userInteraction 為 YES

還得另想辦法,touch 這條路走不通換了一個思路觀察一下手勢,因為在 iOS 的觸控事件中,手勢識別與觸控響應,是2個不同的流程。於是我在 cell 的類裡重寫了 UIGestureRecognizerDelegate 的所有 delegate ,只為希望通過這麼些 delegate 來觀察到底系統的 cell 中的手勢是怎麼做的,這些 delegate 能否呈現一些蛛絲馬跡,結果發現,有且只有2個 delegate 可以被響應

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
複製程式碼

在 gestureRecognizer:shouldReceiveTouch: 中,只會傳遞出一個 UILongPressGesture,但是我強制 return YES,依然沒效果,Touch不觸發

在 gestureRecognizer:shouldRequireFailureOfGestureRecognizer: 中我們可以看到有無數個 gesture 他們之間被相互呼叫來詢問依賴關係,而最可疑的還是那個 UILongPressGesture

並且還有一個現象,在 左滑動畫,漏出右側按鈕 的狀態下,無論是點選,還是滑動,都會觸發相同的 UILongPressGesture 手勢 shouldReceiveTouch 詢問,好吧我就覺得這個最可疑,就在這個時機嘗試直接呼叫 setEditing: NO 試試吧,誰知道呢,就靠猜了

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if (self.isEditing && [gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
        UITableView * table = self.superview;
		//嘗試直接在這個時機呼叫 setEditing: NO
        [table setEditing:NO animated:YES];
    }
    return YES;
}
複製程式碼

居然。。。成功了!!!! 回顧一下實現過程

  • 通過程式碼觸發 cell 左滑動畫,漏出側邊按鈕
    • 先讓 cell 進入 Editing 狀態
    • 再遍歷 cell subview 找到 UITableViewCellEditControl
    • 直接程式碼觸發 UITableViewCellEditControl 的點選事件
  • 通過程式碼觸發 cell 回覆正常,收起側邊按鈕
    • 先用 KVO 監聽 cell 的 userInteractionEnabled
    • 在系統置 userInteraction 為 NO 後,重置為 YES
    • 再用手勢 gestureRecognizer:shouldReceiveTouch: 監聽系統手勢詢問
    • 在 UILongPressGesture 詢問 shouldReceiveTouch 的時候 setEditing:NO

自行實現滑動手勢 tableview cell

怎麼樣,是不是有點夠夠的了?

  • 無止境的通過頁面層級去幹預系統介面
  • 沒目標的嘗試各種 KVO 來猜測系統內部程式碼
  • 無頭緒的重寫 cell 的全部手勢 delegate 來猜測系統到底有多少種手勢
  • 在不知道保險不保險的時機,非正常的呼叫系統 Api
  • 受夠了因為蘋果的 iOS 版本迭代,導致所有的 Hack 嘗試手段失效,重新猜測適配

還是自己實現吧,徹底丟掉蘋果官方 Api 的依賴,自己實現吧,這樣就沒了很多蘋果限定的約束,雖然麻煩點,但好處就可以滿足任何需求的定製了。

大概的思路類似於這樣,我就不深入展開了,因為相關的開源庫都很多,一千個人有一千種設計與寫法

  • 放棄 tableview 自帶的編輯模式,canEdit 返回 NO,自己實現 custom Edit
  • 對 cell 加一個 pan 手勢識別
  • 在 UIGestureRecognizerStateBegan 的時候,根據 delegate 設計構造出 swipeView 和支援定製的子 button
  • 在 UIGestureRecognizerStateChanged ,來精確計算手指移動過的位移 deltaX ,從而運算出 swipeView 的約束與原本 cell 裡面的 UI 新的約束,從而隨著手指移動重新整理介面元素
  • 在 UIGestureRecognizerStateEnd 的時候,根據介面元素的位置,算出應該回彈的距離,用動畫作出回彈效果

相關文章