我需要为我的iPhone应用程序构建自定义键盘。 Previous questions and answers关于这个主题的重点是自定义键盘的可视元素,但我试图了解如何从这个键盘中检索击键。
Apple提供了inputView机制,可以轻松地将自定义键盘与UITextField或UITextView相关联,但它们不提供将生成的键击发送回关联对象的功能。基于这些对象的典型委托,我们期望三个函数:一个是普通字符,一个是退格,一个是输入。然而,似乎没有人清楚地定义这些功能或如何使用它们。
如何为我的iOS应用程序构建自定义键盘并从中检索击键?
答案 0 :(得分:18)
Greg的方法应该有效,但我的方法不需要键盘被告知文本字段或文本视图。实际上,您可以创建单个键盘实例并将其分配给多个文本字段和/或文本视图。键盘处理知道哪一个是第一响应者。
这是我的方法。我不打算显示任何用于创建键盘布局的代码。这很容易。此代码显示了所有管道。
修改:已更新,以便正确处理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
答案 1 :(得分:14)
我已经为iPad创建了一个完整的键盘示例,可以在Github上找到:
https://github.com/lnafziger/Numberpad
Numberpad是适用于iPad的自定义数字键盘
UITextField
&{39}和UITextView
都不需要进行任何更改 添加Numberpad类的实例作为文本的inputView 场/图。
特点:
用法就像包括Numberpad.h一样简单,然后:
theTextField.inputView = [Numberpad defaultNumberpad];
其他一切都会自动处理!
从Github获取两个类文件和xib(上面的链接),或创建按钮(在代码中或在storyboard / xib中),将其操作设置为类中的相应方法(numberpadNumberPressed,numberpadDeletePressed,numberpadClearPressed ,或numberpadDonePressed)。
以下代码已过期。有关最新代码,请参阅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
答案 2 :(得分:12)
这是我的自定义键盘,我认为这完全符合Apple的要求:
// 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文件)创建目标类(用于.h和.m文件)。确保所有三个文件具有相同的名称。使用Identity Inspector,确保将XIB的文件所有者自定义类设置为与新对象的名称相匹配。使用Attributes Inspector,将表单的大小设置为Freeform,并将状态栏设置为none。使用“大小”检查器,设置表单的大小,该大小应与标准键盘的宽度相匹配(iPhone纵向为320,iPhone横向为480),但您可以选择任何高度。
表格已准备好使用。添加按钮并根据需要将它们连接到dataPress,backPress和enterPress。 initWithFrame:和loadWithNIB函数将完成所有魔术,允许您使用Interface Builder中设计的键盘。
要将此键盘与UITextField myTextField一起使用,只需将以下代码添加到viewDidLoad:
self.keyboard = [[PVKeyboard alloc]initWithFrame:CGRectMake(0,488,320,60)];
self.keyboard.textField = self.myTextField;
由于某些限制,此键盘不可重复使用,因此每个字段需要一个键盘。我几乎可以让它重复使用,但我只是觉得不那么聪明。键盘也仅限于UITextFields,但这主要是因为实现回车键功能的限制,我将在下面解释。
这里应该让你设计一个比这个起动器框架更好的键盘......
我使用了一个谨慎的离散设置器(setTextField)实现了这个键盘textField的唯一属性,因为:
你会注意到第二个名为delegate的私有属性,它实质上是将UITextField指针强制转换为UITextInput指针。我可能已经完成了内联播放,但我觉得这可能有助于未来的扩展,也许包括对UITextView的支持。
函数dataPress是使用UIKeyInput的insertText方法插入文本输入编辑字段的内容。这似乎适用于所有版本的iOS 4.对于我的键盘,我只是使用每个按钮的标签,这很正常。使用任何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,而不是键盘本身的委托属性。
这个解决方案适用于普通的键盘处理,这在UITextField的情况下几乎不重要,但是这种技术在UITextView中无法使用,因为现在可以在正在编辑的文本中插入换行符。
这就是它。这需要24小时的阅读和拼凑来完成这项工作。我希望它有所帮助。
答案 3 :(得分:0)
(这主要来自http://blog.carbonfive.com/2012/03/12/customizing-the-ios-keyboard/)
在iOS中,视图的键盘由视图继承链的UIResponder
部分管理。当任何需要键盘的UIResponder成为第一个响应者(被录音或以其他方式激活)时,UIResponder会在其inputView
属性中查找该视图以显示为键盘。因此,要制作自定义键盘并对其上的事件做出响应,您必须创建一个带有字母按钮的视图,将视图控制器与该视图关联,并使用按钮来处理印刷机,您必须将该视图设置为某个文本框的inputView
。
请查看该链接以获取更多信息。