如何从iOS应用上的自定义键盘检索击键?

我需要为我的iPhone应用程序构build自定义键盘。 以前的问题和答案已经集中在自定义键盘的视觉元素,但我想了解如何从这个键盘检索按键。

Apple提供了inputView机制,可以很容易地将自定义键盘与UITextField或UITextView相关联,但是它们不提供将生成的击键返回给关联对象的function。 根据这些对象的典型代表,我们期望三个函数:一个是正常字符,一个是退格键,一个是input键。 然而,似乎没有人明确定义这些function或如何使用它们。

如何为我的iOS应用程序构build自定义键盘并从中检索按键?

格雷格的方法应该工作,但我有一种方法,不需要键盘被告知有关文本字段或文本视图。 实际上,您可以创build一个键盘实例,并将其分配给多个文本字段和/或文本视图。 键盘把手知道哪一个是第一响应者。

这是我的方法。 我不会显示任何代码来创build键盘布局。 这是很容易的部分。 这段代码显示了所有的pipe道。

编辑:这已被更新,以正确处理UITextFieldDelegate textField:shouldChangeCharactersInRange:replacementString:UITextViewDelegate textView:shouldChangeTextInRange:replacementText:

头文件:

 @interface SomeKeyboard : UIView <UIInputViewAudioFeedback> @end 

执行文件:

 @implmentation SomeKeyboard { id<UITextInput> _input; BOOL _tfShouldChange; BOOL _tvShouldChange; } - (id)init { self = [super init]; if (self) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkInput:) name:UITextFieldTextDidBeginEditingNotification object:nil]; } return self; } // This is used to obtain the current text field/view that is now the first responder - (void)checkInput:(NSNotification *)notification { UITextField *field = notification.object; if (field.inputView && self == field.inputView) { _input = field; _tvShouldChange = NO; _tfShouldChange = NO; if ([_input isKindOfClass:[UITextField class]]) { id<UITextFieldDelegate> del = [(UITextField *)_input delegate]; if ([del respondsToSelector:@selector(textField:shouldChangeCharactersInRange:replacementString:)]) { _tfShouldChange = YES; } } else if ([_input isKindOfClass:[UITextView class]]) { id<UITextViewDelegate> del = [(UITextView *)_input delegate]; if ([del respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) { _tvShouldChange = YES; } } } } // Call this for each button press - (void)click { [[UIDevice currentDevice] playInputClick]; } // Call this when a button on the keyboard is tapped (other than return or backspace) - (void)keyTapped:(UIButton *)button { NSString *text = ???; // determine text for the button that was tapped if ([_input respondsToSelector:@selector(shouldChangeTextInRange:replacementText:)]) { if ([_input shouldChangeTextInRange:[_input selectedTextRange] replacementText:text]) { [_input insertText:text]; } } else if (_tfShouldChange) { NSRange range = [(UITextField *)_input selectedRange]; if ([[(UITextField *)_input delegate] textField:(UITextField *)_input shouldChangeCharactersInRange:range replacementString:text]) { [_input insertText:text]; } } else if (_tvShouldChange) { NSRange range = [(UITextView *)_input selectedRange]; if ([[(UITextView *)_input delegate] textView:(UITextView *)_input shouldChangeTextInRange:range replacementText:text]) { [_input insertText:text]; } } else { [_input insertText:text]; } } // Used for a UITextField to handle the return key button - (void)returnTapped:(UIButton *)button { if ([_input isKindOfClass:[UITextField class]]) { id<UITextFieldDelegate> del = [(UITextField *)_input delegate]; if ([del respondsToSelector:@selector(textFieldShouldReturn:)]) { [del textFieldShouldReturn:(UITextField *)_input]; } } else if ([_input isKindOfClass:[UITextView class]]) { [_input insertText:@"\n"]; } } // Call this to dismiss the keyboard - (void)dismissTapped:(UIButton *)button { [(UIResponder *)_input resignFirstResponder]; } // Call this for a delete/backspace key - (void)backspaceTapped:(UIButton *)button { if ([_input respondsToSelector:@selector(shouldChangeTextInRange:replacementText:)]) { UITextRange *range = [_input selectedTextRange]; if ([range.start isEqual:range.end]) { UITextPosition *newStart = [_input positionFromPosition:range.start inDirection:UITextLayoutDirectionLeft offset:1]; range = [_input textRangeFromPosition:newStart toPosition:range.end]; } if ([_input shouldChangeTextInRange:range replacementText:@""]) { [_input deleteBackward]; } } else if (_tfShouldChange) { NSRange range = [(UITextField *)_input selectedRange]; if (range.length == 0) { if (range.location > 0) { range.location--; range.length = 1; } } if ([[(UITextField *)_input delegate] textField:(UITextField *)_input shouldChangeCharactersInRange:range replacementString:@""]) { [_input deleteBackward]; } } else if (_tvShouldChange) { NSRange range = [(UITextView *)_input selectedRange]; if (range.length == 0) { if (range.location > 0) { range.location--; range.length = 1; } } if ([[(UITextView *)_input delegate] textView:(UITextView *)_input shouldChangeTextInRange:range replacementText:@""]) { [_input deleteBackward]; } } else { [_input deleteBackward]; } [self updateShift]; } @end 

这个类需要UITextField的一个类别方法:

 @interface UITextField (CustomKeyboard) - (NSRange)selectedRange; @end @implementation UITextField (CustomKeyboard) - (NSRange)selectedRange { UITextRange *tr = [self selectedTextRange]; NSInteger spos = [self offsetFromPosition:self.beginningOfDocument toPosition:tr.start]; NSInteger epos = [self offsetFromPosition:self.beginningOfDocument toPosition:tr.end]; return NSMakeRange(spos, epos - spos); } @end 

我为iPad创build了一个完整的键盘示例,可在Github上获取:

https://github.com/lnafziger/Numberpad

Numberpad是iPad的自定义数字键盘,与UITextFieldUITextView一起工作,除了添加Numberpad类的实例作为文本字段/视图的input视图外,不需要进行任何更改。

特征:

  • 它包含在MIT许可证下,因此可以根据其条款自由复制和使用。
  • 它适用于UITextFields和UITextViews
  • 它不需要设置代理。
  • 它会自动跟踪哪个视图是第一响应者(所以你不必)
  • 您不必设置键盘的大小,也不必跟踪它。
  • 有一个共享实例,您可以使用任意数量的input视图,而无需为每个视图使用额外的内存。

用法和包含Numberpad.h一样简单,然后:

 theTextField.inputView = [Numberpad defaultNumberpad]; 

一切都自动照顾!

或者从Github(上面的链接)抓取两个类文件和xib,或者将其操作设置为类中的适当方法(numberpadNumberPressed,numberpadDeletePressed,numberpadClearPressed或numberpadDonePressed)来创buildbutton(在代码中或在storyboard / xib中) )。

以下代码已过时。 请参阅Github项目获取最新的代码。

Numberpad.h:

 #import <UIKit/UIKit.h> @interface Numberpad : UIViewController // The one and only Numberpad instance you should ever need: + (Numberpad *)defaultNumberpad; @end 

Numberpad.m:

 #import "Numberpad.h" #pragma mark - Private methods @interface Numberpad () @property (nonatomic, weak) id<UITextInput> targetTextInput; @end #pragma mark - Numberpad Implementation @implementation Numberpad @synthesize targetTextInput; #pragma mark - Shared Numberpad method + (Numberpad *)defaultNumberpad { static Numberpad *defaultNumberpad = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ defaultNumberpad = [[Numberpad alloc] init]; }); return defaultNumberpad; } #pragma mark - view lifecycle - (void)viewDidLoad { [super viewDidLoad]; // Keep track of the textView/Field that we are editing [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(editingDidBegin:) name:UITextFieldTextDidBeginEditingNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(editingDidBegin:) name:UITextViewTextDidBeginEditingNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(editingDidEnd:) name:UITextFieldTextDidEndEditingNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(editingDidEnd:) name:UITextViewTextDidEndEditingNotification object:nil]; } - (void)viewDidUnload { [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidBeginEditingNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidBeginEditingNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidEndEditingNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidEndEditingNotification object:nil]; self.targetTextInput = nil; [super viewDidUnload]; } #pragma mark - editingDidBegin/End // Editing just began, store a reference to the object that just became the firstResponder - (void)editingDidBegin:(NSNotification *)notification { if (![notification.object conformsToProtocol:@protocol(UITextInput)]) { self.targetTextInput = nil; return; } self.targetTextInput = notification.object; } // Editing just ended. - (void)editingDidEnd:(NSNotification *)notification { self.targetTextInput = nil; } #pragma mark - Keypad IBActions // A number (0-9) was just pressed on the number pad // Note that this would work just as well with letters or any other character and is not limited to numbers. - (IBAction)numberpadNumberPressed:(UIButton *)sender { if (!self.targetTextInput) { return; } NSString *numberPressed = sender.titleLabel.text; if ([numberPressed length] == 0) { return; } UITextRange *selectedTextRange = self.targetTextInput.selectedTextRange; if (!selectedTextRange) { return; } [self textInput:self.targetTextInput replaceTextAtTextRange:selectedTextRange withString:numberPressed]; } // The delete button was just pressed on the number pad - (IBAction)numberpadDeletePressed:(UIButton *)sender { if (!self.targetTextInput) { return; } UITextRange *selectedTextRange = self.targetTextInput.selectedTextRange; if (!selectedTextRange) { return; } // Calculate the selected text to delete UITextPosition *startPosition = [self.targetTextInput positionFromPosition:selectedTextRange.start offset:-1]; if (!startPosition) { return; } UITextPosition *endPosition = selectedTextRange.end; if (!endPosition) { return; } UITextRange *rangeToDelete = [self.targetTextInput textRangeFromPosition:startPosition toPosition:endPosition]; [self textInput:self.targetTextInput replaceTextAtTextRange:rangeToDelete withString:@""]; } // The clear button was just pressed on the number pad - (IBAction)numberpadClearPressed:(UIButton *)sender { if (!self.targetTextInput) { return; } UITextRange *allTextRange = [self.targetTextInput textRangeFromPosition:self.targetTextInput.beginningOfDocument toPosition:self.targetTextInput.endOfDocument]; [self textInput:self.targetTextInput replaceTextAtTextRange:allTextRange withString:@""]; } // The done button was just pressed on the number pad - (IBAction)numberpadDonePressed:(UIButton *)sender { if (!self.targetTextInput) { return; } // Call the delegate methods and resign the first responder if appropriate if ([self.targetTextInput isKindOfClass:[UITextView class]]) { UITextView *textView = (UITextView *)self.targetTextInput; if ([textView.delegate respondsToSelector:@selector(textViewShouldEndEditing:)]) { if ([textView.delegate textViewShouldEndEditing:textView]) { [textView resignFirstResponder]; } } } else if ([self.targetTextInput isKindOfClass:[UITextField class]]) { UITextField *textField = (UITextField *)self.targetTextInput; if ([textField.delegate respondsToSelector:@selector(textFieldShouldEndEditing:)]) { if ([textField.delegate textFieldShouldEndEditing:textField]) { [textField resignFirstResponder]; } } } } #pragma mark - text replacement routines // Check delegate methods to see if we should change the characters in range - (BOOL)textInput:(id <UITextInput>)textInput shouldChangeCharactersInRange:(NSRange)range withString:(NSString *)string { if (!textInput) { return NO; } if ([textInput isKindOfClass:[UITextField class]]) { UITextField *textField = (UITextField *)textInput; if ([textField.delegate respondsToSelector:@selector(textField:shouldChangeCharactersInRange:replacementString:)]) { if (![textField.delegate textField:textField shouldChangeCharactersInRange:range replacementString:string]) { return NO; } } } else if ([textInput isKindOfClass:[UITextView class]]) { UITextView *textView = (UITextView *)textInput; if ([textView.delegate respondsToSelector:@selector(textView:shouldChangeTextInRange:replacementText:)]) { if (![textView.delegate textView:textView shouldChangeTextInRange:range replacementText:string]) { return NO; } } } return YES; } // Replace the text of the textInput in textRange with string if the delegate approves - (void)textInput:(id <UITextInput>)textInput replaceTextAtTextRange:(UITextRange *)textRange withString:(NSString *)string { if (!textInput) { return; } if (!textRange) { return; } // Calculate the NSRange for the textInput text in the UITextRange textRange: int startPos = [textInput offsetFromPosition:textInput.beginningOfDocument toPosition:textRange.start]; int length = [textInput offsetFromPosition:textRange.start toPosition:textRange.end]; NSRange selectedRange = NSMakeRange(startPos, length); if ([self textInput:textInput shouldChangeCharactersInRange:selectedRange withString:string]) { // Make the replacement: [textInput replaceRange:textRange withText:string]; } } @end 

这是我的自定义键盘,我相信这完全是苹果允许的:

 // PVKeyboard.h #import <UIKit/UIKit.h> @interface PVKeyboard : UIView @property (nonatomic,assign) UITextField *textField; @end // PVKeyboard.m #import "PVKeyboard.h" @interface PVKeyboard () { UITextField *_textField; } @property (nonatomic,assign) id<UITextInput> delegate; @end @implementation PVKeyboard - (id<UITextInput>) delegate { return _textField; } - (UITextField *)textField { return _textField; } - (void)setTextField:(UITextField *)tf { _textField = tf; _textField.inputView = self; } - (IBAction)dataPress:(UIButton *)btn { [self.delegate insertText:btn.titleLabel.text]; } - (IBAction)backPress { if ([self.delegate conformsToProtocol:@protocol(UITextInput)]) { [self.delegate deleteBackward]; } else { int nLen = [_textField.text length]; if (nLen) _textField.text = [_textField.text substringToIndex:nLen-1]; } } - (IBAction)enterPress { [_textField.delegate textFieldShouldReturn:_textField]; } - (UIView *)loadWithNIB { NSArray *aNib = [[NSBundle mainBundle]loadNibNamed:NSStringFromClass([self class]) owner:self options:nil]; UIView *view = [aNib objectAtIndex:0]; [self addSubview:view]; return view; } - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) [self loadWithNIB]; return self; } @end 

在XCode 4.3和更高版本中,需要基于UIView和用户界面视图文件(用于.xib文件)创build一个Objective-Class(用于.h和.m文件)。 确保所有三个文件具有相同的名称。 使用Identity Inspector,确保将XIB的文件所有者自定义类设置为匹配新对象的名称。 使用属性检查器,将表单的大小设置为自由forms,并将状态栏设置为无。 使用尺寸检查器,设置窗体的大小,这应该与标准键盘的宽度相匹配(320人为iPhone的肖像和480人的iPhone风景),但你可以select任何你喜欢的高度。

表格已准备好使用。 添加button并将它们连接到dataPress,backPress和enterPress。 initWithFrame:和loadWithNIB函数将完成所有的function,使您可以使用在Interface Builder中devise的键盘。

要将这个键盘与UITextField myTextField一起使用,只需将以下代码添加到viewDidLoad:

 self.keyboard = [[PVKeyboard alloc]initWithFrame:CGRectMake(0,488,320,60)]; self.keyboard.textField = self.myTextField; 

由于某些限制,此键盘不可重复使用,所以您需要每个字段一个。 我几乎可以使它可重用,但我只是没有那么聪明。 键盘也仅限于UITextFields,但这主要是因为在实现input键function方面的限制,我将在下面解释。

这里的魔法应该允许你devise一个比起始框架更好的键盘。

我已经实现了这个键盘的唯一属性textField,使用谨慎的离散setter(setTextField),因为:

  1. 我们需要UITextField对象来处理input问题
  2. 我们需要UITextField,因为它符合符合UIKeyInput的UITextInput协议,这个协议完成了我们繁重的工作
  3. 这是一个方便的地方设置UITextInput的inputView字段使用此键盘。

您会注意到第二个名为delegate的私有属性,它基本上将UITextField指针转换为UITextInput指针。 我可能可以做到这一点内联,但我觉得这可能是一个有用的未来扩展function,也许包括支持UITextView。

函数dataPress是插入文本使用UIKeyInput的insertText方法input编辑的字段。 这似乎适用于所有版本回到iOS 4.对于我的键盘,我只是使用每个button的标签,这是非常正常的。 使用任何NSStrings打击你的幻想。

函数dataBack做退格,有点复杂。 当UIKeyInput deleteBackward的作品,它奇妙的工作。 虽然文档说可以回到iOS 3.2,但它似乎只能回溯到iOS 5.0,而UITextField(和UITextView)符合UITextInput协议。 所以在此之前,你是自己的。 由于iOS 4的支持是很多人关心的,所以我实现了一个直接在UITextField上工作的蹩脚退格。 如果不是这个要求,我可以使这个键盘与UITextView工作。 而这个退格不是一般的,只是删除最后一个字符,而deleteBackward将正常工作,即使用户移动光标。

enterPress函数实现了回车键,但它是一个完整的kludge,因为Apple似乎没有提供调用回车键的方法。 因此,enterPress只需调用大多数程序员实现的UITextField的委托函数textFieldShouldReturn:。 请注意,这里的委托是UITextField的UITextFieldDelegate,而不是键盘本身的委托属性。

此解决scheme围绕正常的键盘处理进行,这在UITextField中几乎没有问题,但由于现在可以在正在编辑的文本中插入换行符,所以使得该技术对于UITextView不可用。

这是非常多的。 花了24小时的时间阅读和配合,使这项工作。 我希望它能帮助别人。

(这主要取自http://blog.carbonfive.com/2012/03/12/customizing-the-ios-keyboard/

在iOS中,视图的键盘由视图inheritance链的UIResponder部分pipe理。 当任何需要键盘的UIResponder成为第一响应者(被录音或以其他方式激活)时,UIResponder在它的inputView属性中查看视图以显示键盘。 所以,为了制作一个自定义的键盘并响应它上面的事件,你必须创build一个带有字母button的视图,将视图控制器与该视图相关联,并使用button来处理这些按键,并且必须将该视图设置为一些文本框的inputView

看看链接了解更多信息。