今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

iOS 鍵盤刪除鍵響應

iOS 鍵盤刪除鍵響應#

背景#

背景是,實現一個分享到微信,多選加輸入框,點擊鍵盤刪除鍵,刪除多選選中的對象的東西。

實現#

由於UITextField沒有刪除鍵的代理,所以筆者最開始的想法是,通過textField:shouldChangeCharactersInRange:replacementString:來實現監聽,當當前字符串為空且要替換字符串為空時,說明是點擊的刪除按鈕,通過 Block 方法回調出去,代碼如下:


- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    if ((textField.text.length == 0) && (string.length == 0)) {
        if (self.deleteBackwardBlock) {
            self.deleteBackwardBlock
        }
    }
    return YES;
}

驗證後發現:第三方輸入法用此邏輯沒有問題,但是系統原生輸入法,當 textField 為空時,點擊刪除鍵是無法走這個代理方法的,故而此方法行不通。

然後,筆者就查了一下,可以通過 runtime,來獲取到deleteBackward事件,通過 hook 此事件,可以獲取到點擊鍵盤刪除按鈕的事件,代碼如下:


//  UITextField+BackSpace.h
#import <UIKit/UIKit.h>

@protocol BackSpaceDelegate <NSObject>

@optional
- (void)textFieldBackSpaceTapped:(UITextField *)textField;

@end

@interface UITextField (BackSpace)

@property (nonatomic, weak) id<BackSpaceDelegate>bsDelegate;
@property (nonatomic, copy) void(^ backSpaceCallback)(void);

@end



//  UITextField+BackSpace.m

#import "UITextField+BackSpace.h"
#import <objc/runtime.h>

@implementation UITextField (BackSpace)

static const char *kDelegatePropertyKey = "kDelegatePropertyKey";
static const char *kBlockPropertyKey = "kBlockPropertyKey";

+ (void)load {
    Method originalMethod = class_getInstanceMethod([self class], NSSelectorFromString(@"deleteBackward"));
    Method targetMethod = class_getInstanceMethod([self class], @selector(mk_deleteBackward));
    method_exchangeImplementations(originalMethod, targetMethod);
}

- (id<BackSpaceDelegate>)bsDelegate {
    return objc_getAssociatedObject(self, kDelegatePropertyKey);
}

- (void)setBsDelegate:(id<BackSpaceDelegate>)bsDelegate {
    objc_setAssociatedObject(self, kDelegatePropertyKey, bsDelegate, OBJC_ASSOCIATION_ASSIGN);
}

- (void (^)(void))backSpaceCallback {
    return objc_getAssociatedObject(self, kBlockPropertyKey);
}

- (void)setBackSpaceCallback:(void (^)(void))backSpaceCallback {
    objc_setAssociatedObject(self, kBlockPropertyKey, backSpaceCallback, OBJC_ASSOCIATION_COPY);
}

- (void)mk_deleteBackward {
    [self mk_deleteBackward];
    
    if ([self.bsDelegate respondsToSelector:@selector(textFieldBackSpaceTapped:)]) {
        [self.bsDelegate textFieldBackSpaceTapped:self];
    }
}

然後在要使用的地方設置 textField.bsdelegate,並實現 textFieldBackSpaceTapped: 方法。測試後可以發現點擊鍵盤刪除鍵時,代理方法確實響應了,代碼如下:


@interface TargetView ()<BackSpaceDelegate>

@property (nonatomic, strong) UITextField *textField;

@end

@implementation TargetView

...
    self.textField.delegate = self;
    self.textField.bsDelegate = self;
...

- (void)textFieldBackSpaceTapped:(UITextField *)textField {
    NSLog(@"刪除");
}


@end

再回過頭來看需求,當輸入框中沒有數據時,刪除多選選中的結果。所以筆者直接在此代理方法中判斷,當 textField 的 text 為空時,刪除多選選中的結果。

代碼如下:


- (void)textFieldBackSpaceTapped:(UITextField *)textField {
    NSLog(@"刪除");
    
    if (textField.text.length != 0) {
        return;
    }
    
    UIView *lastView = self.multipleSelectView.subviews.lastObject;
    if (lastView) {
        [lastView removeFromSuperview];
    }
}

調試後發現,當到最後一個字符時,點擊刪除,字符和多選一同被刪除了,而我們需要的是,在最後一個字符刪除後,再次點擊刪除才應該操作多選。

筆者最初的理解應該是,刪除按鈕的事件在前面,點擊刪除按鈕時,獲取到的 textField 的 text 應該是未刪除的,然後再走textField:shouldChangeCharactersInRange:replacementString:方法。然而調試後發現,實際的順序是點擊刪除按鈕,然後執行了textField:shouldChangeCharactersInRange:replacementString:,最後才走到了textFieldBackSpaceTapped:的回調。

所以就出現了上面的情況,那怎麼解決呢?

最簡單的方法是記錄一下上一次輸入框的值,當上一次輸入框的值為空時,才可以刪除多選數據;否則不操作多選的數據,只更新上一次輸入框的值。

代碼如下:


- (void)textFieldBackSpaceTapped:(UITextField *)textField {
    NSLog(@"刪除");
    
    if (textField.text.length != 0) {
        self.previousStr = textField.text;
        return;
    }
    
    if (self.previousStr.length != 0) {
        self.previousStr = textField.text;
        return;
    }
    
    UIView *lastView = self.multipleSelectView.subviews.lastObject;
    if (lastView) {
        [lastView removeFromSuperview];
    }
}

效果如下:

screen-recording-2021-07-21-at-17.52.05.gif

代碼參考:
BackSpace

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。