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

时间:2012-11-03 01:35:35

标签: ios keyboard inputview

我需要为我的iPhone应用程序构建自定义键盘。 Previous questions and answers关于这个主题的重点是自定义键盘的可视元素,但我试图了解如何从这个键盘中检索击键。

Apple提供了inputView机制,可以轻松地将自定义键盘与UITextField或UITextView相关联,但它们不提供将生成的键击发送回关联对象的功能。基于这些对象的典型委托,我们期望三个函数:一个是普通字符,一个是退格,一个是输入。然而,似乎没有人清楚地定义这些功能或如何使用它们。

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

4 个答案:

答案 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   场/图。

特点:

  • 它受MIT许可证保护,因此可以按照其免费复制和使用。术语
  • 适用于UITextFields和UITextViews
  • 不需要设置代理。
  • 自动跟踪哪个视图是第一响应者(因此您不必)
  • 您不必设置键盘的大小,也不必跟踪它。
  • 有一个共享实例可以用于任意数量的输入视图,而不需要为每个视图使用额外的内存。

用法就像包括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的唯一属性,因为:

  1. 我们需要UITextField对象来处理输入问题
  2. 我们需要UITextField,因为它符合符合UIKeyInput的UITextInput协议,UIKeyInput完成了很多繁重的任务。
  3. 这是设置UITextInput的inputView字段以使用此键盘的便利位置。
  4. 你会注意到第二个名为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

请查看该链接以获取更多信息。