Подтвердить что ты не робот

Как получить нажатия клавиш с пользовательской клавиатуры в приложении iOS?

Мне нужно создать пользовательскую клавиатуру для моего iPhone-приложения. Предыдущие вопросы и ответы по теме были сосредоточены на визуальных элементах пользовательской клавиатуры, но я пытаюсь понять, как извлекать нажатия клавиш с этой клавиатуры.

Apple предоставляет механизм inputView, который упрощает связывание пользовательской клавиатуры с UITextField или UITextView, но они не предоставляют функции для отправки сгенерированных нажатий клавиш на связанный объект. На основе типичного делегирования для этих объектов мы ожидаем три функции: один из нормальных символов, один для backspace и один для ввода. Тем не менее, никто, кажется, не четко определяет эти функции или как их использовать.

Как создать пользовательскую клавиатуру для моего приложения iOS и получить от нее нажатия клавиш?

4b9b3361

Ответ 1

Здесь моя пользовательская клавиатура, которая, я считаю, адресована им полностью, как 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 и более поздних версиях вам необходимо создать объектный класс (для файлов .h и .m) на основе файла UIView и файла интерфейса пользователя (для файла .xib). Убедитесь, что все три файла имеют одинаковое имя. Используя Identity Inspector, убедитесь, что пользовательский класс владельца файла XIB соответствует имени нового объекта. Используя Attributes Inspector, установите размер формы в Freeform и установите для строки состояния значение none. Используя Size Inspector, установите размер формы, который должен соответствовать ширине стандартной клавиатуры (320 для iPhone и 480 для iPhone), но вы можете выбрать любую нужную вам высоту.

Форма готова к использованию. Добавьте кнопки и подключите их к 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, но в основном из-за ограничений в реализации функциональных клавиш ввода, которые я объясню ниже.

Здесь волшебство, которое должно позволить вам создать лучшую клавиатуру, чем эта стартовая рама...

Я реализовал единственное свойство этой клавиатуры textField, используя дискретный дискретный setter (setTextField), потому что:

  • нам нужен объект UITextField для обработки проблемы ввода
  • нам нужен UITextField, потому что он соответствует протоколу UITextInput, который соответствует UIKeyInput, что делает большую часть нашего тяжелого подъема
  • Это было удобное место для установки поля ввода Input UITextInput для использования этой клавиатуры.

Вы увидите второе частное свойство с именем delegate, которое, по сути, выводит указатель UITextField на указатель UITextInput. Возможно, я мог бы сделать это, но я понял, что это может быть полезно как функция для будущего расширения, возможно, чтобы включить поддержку UITextView.

Функция dataPress - это то, что вставляет текст, вводит отредактированное поле, используя метод insertText для UIKeyInput. Кажется, это работает во всех версиях для iOS 4. Для моей клавиатуры я просто использую метку каждой кнопки, что довольно нормально. Используйте все, что NSStrings поражает вашей фантазией.

Функция dataBack выполняет обратное пространство и немного сложнее. Когда UIKeyInput deleteBackward работает, он работает чудесно. И хотя в документации говорится, что она работает с iOS 3.2, похоже, что она работает только с iOS 5.0, когда UITextField (и UITextView) соответствует протоколу UITextInput. Поэтому до этого вы сами. Поскольку поддержка iOS 4 является проблемой для многих, я реализовал хромое обратное пространство, которое работает непосредственно с UITextField. Если бы не это требование, я мог бы заставить эту клавиатуру работать с UITextView. И это backspace не является общим, а только удаляет последний символ, а deleteBackward будет работать правильно, даже если пользователь перемещает курсор.

Функция enterPress реализует ключ ввода, но является полным kludge, потому что Apple, похоже, не дает метода для вызова ключа ввода. Таким образом, enterPress просто вызывает функцию делегата UITextField textFieldShouldReturn:, которую большинство программистов реализует. Обратите внимание, что делегатом здесь является UITextFieldDelegate для UITextField и НЕ свойство делегата для самой клавиатуры.

Это решение распространяется на обычную обработку клавиатуры, что вряд ли имеет значение в случае UITextField, но делает эту технику непригодной для использования с UITextView, поскольку теперь есть способ вставить разрывы строк в редактируемом тексте.

Это в значительной степени. Для выполнения этой работы потребовалось 24 часа чтения и мощения. Надеюсь, это поможет кому-то.

Ответ 2

Подход Грега должен работать, но у меня есть подход, который не требует, чтобы клавиатура была рассказана о текстовом поле или текстовом представлении. Фактически, вы можете создать один экземпляр клавиатуры и назначить его нескольким текстовым полям и/или текстовым представлениям. Клавиатура обрабатывает информацию о том, какой из них является первым ответчиком.

Вот мой подход. Я не собираюсь показывать код для создания раскладки клавиатуры. Это легкая часть. Этот код показывает всю сантехнику.

Изменить. Это обновление было обновлено для правильной обработки 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

Ответ 3

Я создал полный рабочий пример клавиатуры для iPad, доступный на Github здесь:

https://github.com/lnafziger/Numberpad

Numpad - это обычная цифровая клавиатура для iPad, которая работает с как UITextField и UITextView, не требующие никаких изменений, кроме добавление экземпляра класса Numpad в качестве входного вида текста Поле/вид.

Особенности:

  • Он распространяется под лицензией MIT, поэтому его можно свободно копировать и использовать на своих "условиях".
  • Он работает с UITextFields и UITextViews
  • Для этого не требуется устанавливать делегат.
  • Он автоматически отслеживает, какой вид является первым ответчиком (так что вам не нужно)
  • Вам не нужно устанавливать размер клавиатуры или отслеживать ее.
  • Существует общий экземпляр, который вы можете использовать для такого количества входных представлений, сколько хотите, без использования дополнительной памяти для каждого из них.

Использование так же просто, как и Numpad.h, а затем:

theTextField.inputView  = [Numberpad defaultNumberpad];

Все остальное заботится автоматически!

Либо возьмите два файла класса и xib из Github (ссылка выше), либо создайте кнопки (в коде или в раскадровке /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

Ответ 4

(В основном это берется из http://blog.carbonfive.com/2012/03/12/customizing-the-ios-keyboard/)

В iOS клавиатура для представления управляется частью UIResponder цепи наследования вида. Когда любой UIResponder, который нуждается в клавиатуре, становится первым ответчиком (записан на магнитной ленте или иным образом активирован), UIResponder выглядит в свойстве inputView для отображения, отображаемого в виде клавиатуры. Итак, чтобы создать пользовательскую клавиатуру и ответить на событие, вам нужно создать представление с помощью кнопок с буквой, связать контроллер представления с этим представлением и кнопками для обработки прессов, и вы должны установить это представление как inputView некоторого текстового поля.

Взгляните на ссылку для получения дополнительной информации.