Стандартная библиотека предоставляет для UITextField и UITextView несколько различных типов клавиатур, среди которых есть обычная, Email, URL (от обычной отличаются несколькими символами) и Phone (цифровая). Для большинства жизненных ситуаций этого достаточно, но не всегда.Представим, что в приложении есть поле ввода, могущее принимать числа и арифметические выражения.
Phone клавиатуры нам будет недостаточно — нет точки, нет всех символов операций, и т.д.
Безусловно, все необходимое есть на обычной клавиатуре, однако в данном случае 95% ее использоваться не будет (напомним, нам нужны только цифры + символы арифметических операций).
Вывод напрашивается сам собой: нужно писать свою клавиатуру,
Итак, какие символы нам нужны? ".0123456789%+-×÷", и для коллекции добавим "↵" (return) и "←" (backspace). Нарисуем макет:

ОК, выглядит вроде неплохо. Теперь нужно прикрутить сие поделие к полю текстового ввода.
Еще со времен iOS 3 у компонентов интерфейса, позволяющих ввод (таких, как
UITextField и UITextView) есть чудесные свойства inputView и inputAccessoryView.Вьюха, лежащая в
inputAccessoryView, показывается над клавиатурой (само собой, когда последняя находится на экране). Обычно это тулбар с парой кнопок и/или полем для ввода.А вот
inputView предназначено как раз для переопределения стандартной клавиатуры. Иными словами, если присвоить объект класса UIView данному свойству, то в момент, когда текстовое поле становится firstResponder, показывается не клавиатура, а тот самый объект UIView, лежащий в inputView. Показывается он в том же месте (снизу, хехе) и с теми же анимациями, что и обычная клавиатура. Более того, будут посылаться и уведомления вроде UIKeyboardWillShowNotification! Сплошные плюсы.А как же минусы? Их есть у меня.
Главный минус любого нестандартного ввода — это как организовать связь между клавиатурой и текстовым полем. Решений можно придумать немало, но, безусловно, идеальным было бы сделать свой ввод «нативным», максимально прозрачным для использования (например, какое-нибудь шаманство с протоколом
UITextInputTraits и расширение UIKeyboardType). К сожалению, над этим я особо не задумывался, хотя, когда будет время, обязательно попробую допилить.Одно из наиболее очевидных решений — делегат. Наша клавиатура будет просто сообщать своему делегату, какая кнопка нажата. И кстати о кнопках. А почему бы не хардкодить клавиатуру, а сделать ее динамически конфигурируемой? Действительно, почему бы и нет? Заведем для этого протокол Datasource.
Итак, имеем в итоге один класс и два протокола: клавиатура, delegate, datasource.
//KHKeyboard.h #import <UIKit/UIKit.h> #import "KHKeyboardDatasource.h" #import "KHKeyboardDelegate.h" @interface KHKeyboard : UIView @property (nonatomic, assign) id<KHKeyboardDatasource> datasource; @property (nonatomic, assign) id<KHKeyboardDelegate> delegate; @end //KHKeyboardDelegate.h #import <UIKit/UIKit.h> @class KHKeyboard; @protocol KHKeyboardDelegate <NSObject> @optional - (void)keyPressedInKeyboard:(KHKeyboard *)keyboard atIndex:(NSInteger)index; @end //KHKeyboardDatasource.h #import <UIKit/UIKit.h> @class KHKeyboard; @protocol KHKeyboardDatasource <NSObject> @required - (NSInteger)numberOfKeysInKeyboard:(KHKeyboard *)keyboard; - (UIButton *)buttonForKeyInKeyboard:(KHKeyboard *)keyboard atIndex:(NSInteger)index; @end
С делегатом все понятно — нажатая кнопка определяется по индексу. Datasource предназначен для конфигурирования нашей клавиатуры: он сообщает количество кнопок, а также предоставляет объекты
UIButton (собственно кнопки). Конфигурируя возвращаемую кнопку, не забываем установить свойство frame — оно определяет положение кнопки на нашей вьюхе.Инициализация:
self.rects = [NSArray arrayWithObjects: [NSValue valueWithCGRect:CGRectMake(0, 0, 64, 50)], [NSValue valueWithCGRect:CGRectMake(64, 0, 64, 50)], [NSValue valueWithCGRect:CGRectMake(128, 0, 64, 50)], [NSValue valueWithCGRect:CGRectMake(192, 0, 64, 50)], [NSValue valueWithCGRect:CGRectMake(256, 0, 64, 50)], [NSValue valueWithCGRect:CGRectMake(0, 50, 64, 50)], [NSValue valueWithCGRect:CGRectMake(64, 50, 64, 50)], [NSValue valueWithCGRect:CGRectMake(128, 50, 64, 50)], [NSValue valueWithCGRect:CGRectMake(192, 50, 64, 50)], [NSValue valueWithCGRect:CGRectMake(256, 50, 64, 50)], [NSValue valueWithCGRect:CGRectMake(0, 100, 64, 50)], [NSValue valueWithCGRect:CGRectMake(64, 100, 64, 50)], [NSValue valueWithCGRect:CGRectMake(128, 100, 64, 50)], [NSValue valueWithCGRect:CGRectMake(192, 100, 64, 50)], [NSValue valueWithCGRect:CGRectMake(256, 100, 64, 100)], [NSValue valueWithCGRect:CGRectMake(0, 150, 128, 50)], [NSValue valueWithCGRect:CGRectMake(128, 150, 64, 50)], [NSValue valueWithCGRect:CGRectMake(192, 150, 64, 50)], nil]; self.titles = [NSArray arrayWithObjects: @"7", @"8", @"9", @"×", @"←", @"4", @"5", @"6", @"÷", @"%", @"1", @"2", @"3", @"+", @"↵", @"0", @".", @"−", nil]; KHKeyboard *keyboard = [[[KHKeyboard alloc] init] autorelease]; keyboard.datasource = self; keyboard.delegate = self; self.textField.inputView = keyboard;
Настраиваем datasource:
- (NSInteger)numberOfKeysInKeyboard:(KHKeyboard *)keyboard { return [self.rects count]; } - (UIButton *)buttonForKeyInKeyboard:(KHKeyboard *)keyboard atIndex:(NSInteger)index { NSString *title = [self.titles objectAtIndex:index]; CGRect rect = [(NSValue *)[self.rects objectAtIndex:index] CGRectValue]; UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = rect; [button setTitle:title forState:UIControlStateNormal]; [button setUserInteractionEnabled:NO]; [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; [button setTitleColor:[UIColor blackColor] forState:UIControlStateHighlighted]; [button setReversesTitleShadowWhenHighlighted:YES]; if (index == 14) { // return key [button setBackgroundImage:[UIImage imageNamed:@"button_normal_2.png"] forState:UIControlStateNormal]; [button setBackgroundImage:[UIImage imageNamed:@"button_highlighted_2.png"] forState:UIControlStateHighlighted]; } else if (index == 15) { // "0" key [button setBackgroundImage:[UIImage imageNamed:@"button_normal_1.png"] forState:UIControlStateNormal]; [button setBackgroundImage:[UIImage imageNamed:@"button_highlighted_1.png"] forState:UIControlStateHighlighted]; } else { [button setBackgroundImage:[UIImage imageNamed:@"button_normal.png"] forState:UIControlStateNormal]; [button setBackgroundImage:[UIImage imageNamed:@"button_highlighted.png"] forState:UIControlStateHighlighted]; } [button.titleLabel setFont:[UIFont boldSystemFontOfSize:16]]; return button; }
И сиротливый delegate:
- (void)keyPressedInKeyboard:(KHKeyboard *)keyboard atIndex:(NSInteger)index { if (index == 14) { // return key [self.textField resignFirstResponder]; } else if (index == 4) { // backspace key NSInteger length = [self.textField.text length]; if (length == 0) { return; } else { NSString *newValue = [self.textField.text substringToIndex:length - 1]; self.textField.text = newValue; } } else { NSString *value = [self.titles objectAtIndex:index]; NSMutableString *newValue = [NSMutableString stringWithFormat:@"%@%@", self.textField.text, value]; self.textField.text = newValue; } }
Результат можно видеть на скриншоте в начале статьи, а также пощупать на github.
На этом, в принципе, можно было бы и завершить, однако остаются некоторые вопросы.
Например, данная реализация хорошо подходит, если поле ввода, требующее кастомную клавиатуру, только одно. А если их несколько? Нужно ведь как-то определять, какое из них является
firstResponder в текущий момент.Как вариант, можно следить за уже упомянутыми уведомлениями о появлении клавиатуры (не забываем, что они никуда не исчезают). Свойство
object объекта класса NSNotification будет содержать контрол, который вызвал клавиатуру. Следовательно, сохраняем его и используем впоследствии в keyPressedInKeyboard:atIndex:Буду очень благодарен за любые дельные пожелания и подсказки по улучшению и унификации сего поделия.
