今是昨非

今是昨非

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

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

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