UIResponder 拾遗:inputView 和 inputAccessoryView
前几天在看响应者链条的时候,看到 UIResponder 有两个很有用的属性,但是自己不熟悉,就是 inputView 和 inputAccessoryView。原来自己练手的时候,需要这样的功能,但是自己不知道这两个属性,导致自己花费了很多时间。所以,就写下这篇,算是对不知道的知识的补充。
UITextField 的 inputView 和 inputAccessoryView
UITextField 的 inputView
默认情况下,当 UITextField 对象成为第一响应者的时候,系统会唤出系统键盘来接收用户的输入。
但是在有些时候,我们不希望唤出系统键盘,而是我们自定义的 view 来接收用户的输入,在这种情况下,inputView 就派上用场了。inputView 的作用就是让开发者提供自定义的 view 来获取用户的输入,当 UITextField 对象成为第一响应者的时候,系统会尝试唤出 inputView,如果 inputView 存在,就唤出开发者提供的 inputView;如果 inputView 不存在,也就是说这个属性的值是 nil(inputView 默认就是 nil),系统会唤出系统键盘。
现在假设当我们点击 UITextField 之后,显示出来的是一个 UIDatePicker。大概的实现代码:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UITextField *textField;
@property (nonatomic, strong) UIDatePicker *datePicker;
@end
@implementation ViewController
- (UIDatePicker *)datePicker {
if (!_datePicker) {
_datePicker = [[UIDatePicker alloc] init];
_datePicker.datePickerMode = UIDatePickerModeCountDownTimer;
}
return _datePicker;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.textField.inputView = self.datePicker;
}
@end
UITextField 的 inputAccessoryView
接下来说一下 inputAccessoryView 的作用,这个属性就是在 inputView 或者系统键盘上添加一个辅助的 view,例如下图高亮部分(图片截取自奇点):
我们自己来添加一个 inputAccessoryView,大概的代码实现:
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UITextField *textField;
@property (nonatomic, strong) UIToolbar *toolBar;
@end
@implementation ViewController
- (UIToolbar *)toolBar {
if (!_toolBar) {
_toolBar = [[UIToolbar alloc] init];
_toolBar.bounds = (CGRect){CGPointZero, self.view.bounds.size.width, 49};
UIBarButtonItem *spaceItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil
action:NULL];
UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(done)];
_toolBar.items = @[spaceItem, rightItem];
}
return _toolBar;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.textField.inputAccessoryView = self.toolBar;
}
- (void)done {
NSString *text = [NSString stringWithFormat:@"%.2lf s", self.datePicker.countDownDuration];
if (self.textField.isFirstResponder) {
self.textField.text = text;
[self.textField resignFirstResponder];
}
}
@end
UITextView 的自定义 inputView/inputAccessoryView 与 UITextField 类似。
UIReponder 的 inputView 和 inputAccessoryView
UIReponder 的 inputView
我们看一下 inputView 和 inputAccessoryView 在 UIResponder 的声明:
// Called and presented when object becomes first responder. Goes up the responder chain.
@property (nullable, nonatomic, readonly, strong) __kindof UIView *inputView NS_AVAILABLE_IOS(3_2);
@property (nullable, nonatomic, readonly, strong) __kindof UIView *inputAccessoryView NS_AVAILABLE_IOS(3_2);
这里我们看到 UIResponder 将这两个属性声明为 readonly,所以当我们使用例如:UIButton 的时候,我们就需要继承这些类,然后重新将这两个属性声明称 readwrite。
假设我们点击一个 button 时,要显示一个 UIDatePicker,大概的代码实现:
@interface TSButton : UIButton
@property (nonatomic, strong, readwrite, nullable) UIView *inputView;
@property (nonatomic, strong, readwrite, nullable) UIView *inputAccessoryView;
@end
@implementation TSButton
// 1
- (BOOL)canBecomeFirstResponder {
return YES;
}
// 2
- (BOOL)canResignFirstResponder {
return YES;
}
@end
@interface ViewController ()
@property (nonatomic, weak) IBOutlet TSButton *showDatePickerButton;
@property (nonatomic, strong) UIDatePicker *datePicker;
@end
@implementation ViewController
- (UIDatePicker *)datePicker {
if (!_datePicker) {
_datePicker = [[UIDatePicker alloc] init];
_datePicker.datePickerMode = UIDatePickerModeCountDownTimer;
}
return _datePicker;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.showDatePickerButton.inputView = self.datePicker;
}
- (IBAction)showDatePicker {
// 3
[self.showDatePickerButton becomeFirstResponder];
}
@end
我们在这里要注意有注释的地方,除却 UITextField 和 UITextView 之外,因为这两个控件会自动成为第一响应者,其他的 UIResponder 子类想要成为第一响应者有两步要做:
- 覆写
canBecomeFirstResponder并返回 YES; - 主动调用
becomeFirstResponder方法。
因为只有成为第一响应者,系统才会去唤出 inputView 与 inputAccessoryView,所以我们需要主动调用 becomeFirstResponder 方法。
reloadInputViews 用法
当对象是第一响应者时,调用这个方法来刷新 inputView 和 inputAccessoryView。这些 view 会立马被替换,没有动画。如果对象不是第一响应者,则这个方法就没有任何效果。
与 inputView 和 inputAccessoryView 相关的通知
我们有时会监听与系统键盘相关的通知,以便在系统键盘出现的时候,来调整我们的 UI 界面。同样,自定义的 inputView 出现或者消失时也会触发键盘相关的通知。我们同样可以监听 UIKeyboardWillShowNotification, UIKeyboardDidShowNotification, UIKeyboardWillHideNotification, 和 UIKeyboardDidHideNotification 通知来调整 UI 视图。这里需要注意的是,当有 inputAccessoryView 时,通知的 userInfo 中的高度数据,是 inputView 的高度加上 inputAccessoryView 的高度。
最后
在 UIResponder 还有 inputViewController 和 inputAccessoryViewController 属性,这两个属性跟自定义键盘有关。下面是它们在 UIResponder 中的声明:
// For viewController equivalents of -inputView and -inputAccessoryView
// Called and presented when object becomes first responder. Goes up the responder chain.
@property (nullable, nonatomic, readonly, strong) UIInputViewController *inputViewController NS_AVAILABLE_IOS(8_0);
@property (nullable, nonatomic, readonly, strong) UIInputViewController *inputAccessoryViewController NS_AVAILABLE_IOS(8_0);