📜 ⬆️ ⬇️

We write the keyboard for non-standard input under iOS

The standard library provides for UITextField and UITextView several different types of keyboards, among which are the usual, Email, URL (differ from the usual several characters) and Phone (digital). For most life situations, this is enough, but not always.

Imagine that an application has an input field that can accept numbers and arithmetic expressions.

Phone keypad will not be enough for us - there is no full stop, no all operations characters, etc.
')
Of course, everything you need is on a regular keyboard, but in this case 95% of it will not be used (recall, we only need numbers + symbols of arithmetic operations).

The conclusion suggests itself: you need to write your keyboard, well, you know what !



So, what characters do we need? ".0123456789% + - × ÷", and for the collection, add "↵" (return) and "←" (backspace). Draw a layout:



OK, it looks pretty good. Now you need to tie this section to the text input field.

Since the days of iOS 3, interface components that allow input (such as UITextField and UITextView ) have wonderful inputView and inputAccessoryView .

The view lying in inputAccessoryView is shown above the keyboard (of course, when the latter is on the screen). This is usually a toolbar with a pair of buttons and / or an input field.

But inputView intended just to override the standard keyboard. In other words, if you assign an object of the UIView class UIView this property, then at the moment when the text field becomes firstResponder , it is not the keyboard that is shown, but the very UIView object that lies in the inputView . It is shown in the same place (bottom, hehe) and with the same animations as a regular keyboard. Moreover, notifications like UIKeyboardWillShowNotification will be sent! Solid pluses.

But what about the cons? I have them.

The main disadvantage of any non-standard input is how to organize the connection between the keyboard and the text field. You can come up with a lot of solutions, but, of course, it would be ideal to make your input “native”, as transparent as possible for use (for example, some shamanism with the UITextInputTraits protocol and the UIKeyboardType extension). Unfortunately, I didn’t really think about it, although when I have time, I’ll definitely try to finish it.

One of the most obvious decisions is the delegate. Our keyboard will simply tell your delegate which button is down. And by the way about the buttons. And why not hardcode the keyboard, but make it dynamically configurable? Indeed, why not? Let's get for this protocol Datasource.

So, we have as a result one class and two protocols: keyboard, 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 

With the delegate, everything is clear - the pressed button is determined by the index. Datasource is designed to configure our keyboard: it reports the number of buttons, and also provides UIButton objects (the actual buttons). Configuring the returned button, do not forget to set the frame property - it determines the position of the button on our view.

Initialization:
 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; 

Configure 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; } 

And the lonely 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; } } 

The result can be seen in the screenshot at the beginning of the article, as well as on github .

This, in principle, could be completed, but some questions remain.

For example, this implementation is well suited if the input field that requires a custom keyboard is only one. And if there are several? It is necessary to somehow determine which of them is the firstResponder at the moment.

Alternatively, you can follow the already mentioned notifications about the appearance of the keyboard (do not forget that they do not disappear anywhere). The object property of an NSNotification class NSNotification will contain the control that caused the keyboard. Therefore, we save it and use it later in keyPressedInKeyboard:atIndex:

I would be very grateful for any practical suggestions and tips for improving and unifying this product.

Source: https://habr.com/ru/post/139937/


All Articles