前言

有個最近才從 Android 跳槽的同學向我抱怨 iOS 內建的鬧鐘,其編輯過程繁瑣不直觀。

她表示 Android 直接點選欲編輯的鬧鐘即可編輯,iOS 則需要多一步點擊左上角編輯的步驟。

的確如她所言,點擊左上角編輯後,進入的編輯狀態著實雞肋,除了刪除、編輯項目外無其他功能。

直接將項目往左拖曳也能達到刪除項目的功能,且比點擊編輯刪除還要來得快捷。

於是,我撰寫了擴充套件 TimerFix,來實現快速編輯鬧鐘。


實作過程

  1. 使用 Reveal 檢視編輯按鈕,發現是個 UINavigationButton(UIButton 類別)。

  2. 使用 LLDB 指令po [UIButton allTargets],來取得 target 與 action:

    po [0x10328af30 allTargets]
    {(
        <UIBarButtonItem: 0x10326e1e0> target=0x10323c080 action=_toggleEditing: title='編輯'
    )}
    
  3. 使用 LLDB 指令po target,來取得 Controller:

    po 0x10323c080
    <MTAAlarmTableViewController: 0x10323c080>
    
  4. 透過查閱.h檔,尋找 action 方法_toggleEditing:實現的地方:

    1. MTAAlarmTableViewController 中沒有實現_toggleEditing:,往父類找。
    2. MTAAlarmTableViewController 繼承於 MTATableViewController。
    3. MTATableViewController 繼承於 UITableViewController。
    4. UITableViewController 繼承於 UIViewController。
    5. 最終發現於 UIViewController 中實現了_toggleEditing:
  5. 使用 IDA 逆向 UIKitCore 找到 UIViewController 中的_toggleEditing:偽代碼:

    void __cdecl -[UIViewController _toggleEditing:](UIViewController *self, SEL a2, id a3)
    {
      char v3; // al
    
      v3 = (unsigned __int64)-[UIViewController isEditing](self, "isEditing", a3);
      -[UIViewController setEditing:animated:](self, "setEditing:animated:", (unsigned __int8)(v3 ^ 1), 1LL);
    }
    

    可見是呼叫了[UIViewController setEditing:animated:]才進入編輯狀態。

  6. 接著,撰寫程式碼,使不是處於編輯狀態的項目也能點選:

    %hook MTAAlarmTableViewController
    -(void)viewDidLoad
    {
      %orig;
      [self.tableView setAllowsSelection:YES];
    }
    %end
    

    實測後發現點選後還是沒反應。

  7. 使用 IDA 逆向處理項目點選的方法,發現當中存在是否處於編輯狀態的判斷。

    再結合上述 UIKitCore 的逆向,在%orig;前先[self setEditing:YES animated:NO];即可:

    %hook MTAAlarmTableViewController
    -(void)tableView:(id)arg1 didSelectRowAtIndexPath:(id)arg2
    {
      if(![self isEditing])
      {
        is_tap_cell = YES;
        [self setEditing:YES animated:NO];
      }
      %orig;
    }
    %end
    
  8. 最後再於 MTAAlarmTableViewCell 做一些外觀上的修飾:

    static BOOL is_tap_cell = NO;
    
    %hook MTAAlarmTableViewCell
    -(void)setEditing:(_Bool)arg1 animated:(_Bool)arg2
    {
      if(is_tap_cell) { %orig(NO,NO); }
      else { %orig; }
    }
    
    -(void)refreshUI:(id)arg1 animated:(_Bool)arg2
    {
      is_tap_cell = NO;
      %orig;
    }
    %end
    

    完成。