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];
}
}
效果如下:
代碼參考:
BackSpace