iOS自定义键盘

2015-10-26

前段时间下载了招商银行掌上银行 app ,发现它的密码输入时弹出的键盘是自定义的,每次数字展示的排布都不一样,所以准备自己也实现一个(强行给自己找个理由)。

演示效果

代码放在这里,欢迎 star 和 fork ~

代码目录结构

一开始的时候思路就被带歪了。。。一直以为自定义键盘需要获取键盘所在的 window ,然后从中剥离出 view ,结果。。。一个坑接着一个坑踩得不要不要的,在差点就完全陷进去的时候才看到了 textField 的两个属性: inputView inputAccessoryView ,终于爬出来了(๑‾ ꇴ ‾๑),这里附上获取键盘 view 的方法:iOS get Keyboard Window

话不多少,直接上代码,让我们一层一层来剥离这个 demo 。

零.一些零星的定义

1
2
3
4
5
6
7
8
9
10
typedef enum : NSUInteger {
WMKeyButtonTypeDel, // 按键类型:删除
WMKeyButtonTypeDone, // 按键类型:完成
WMKeyButtonTypeOther // 按键类型:其他
} WMKeyButtonType;
typedef enum : NSUInteger {
WMKeyboardOtherTypeCommon, // 常用
WMKeyboardOtherTypeAll // 全部
} WMKeyboardOtherType;

在WMKeyboardDefine.h文件中定义了键盘的类型以及特殊键盘上不同的类型,这在演示中可以体现。

一.Button

首先写了一个通用按钮WMKeyButton,定义了初始化方法以及block和一些初始化时用到的数据。通用按钮的.h文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static CGFloat const WMKeyButtonFont = 15;
typedef void(^buttonClickBlock)(WMKeyButtonType buttonType, NSString *text);
@interface WMKeyButton : UIButton
@property (assign, nonatomic) WMKeyButtonType type;
+ (instancetype)keyButtonWithFrame:(CGRect)frame;
- (instancetype)initKeyButtonWithFrame:(CGRect)frame;
// 设置block
- (void)setButtonClickBlock:(buttonClickBlock)block;
@end

当按钮被点击的时候通过通过block将按钮的类型和按钮上的文字传递,而对于特殊按钮则设置了每个按钮所包含的数据,并且重新定义了block:

1
2
3
4
5
6
7
8
9
10
11
@class Button;
typedef void(^sButtonClickBlock)(WMKeyButtonType buttonType, NSString *text, Button *button);
@interface WMKeySpecialButton : WMKeyButton
@property (strong, nonatomic) Button *button;
- (void)setSButtonClickBlock:(sButtonClickBlock)block;
@end

其中Button类是通过.xcdatamodeld文件生成的Entity实体类,包含了text和count两个属性
Button

二.KeyboardView

同样地定义了通用键盘WMKeyboardView:

1
2
3
4
5
6
7
8
9
10
11
12
static CGFloat WMKeyboardViewNumberHeight = 250;
typedef void(^WMKeyboardBlock)(WMKeyButtonType type, NSString *text);
@interface WMKeyboardView : UIView
- (instancetype)initKeyboardWithFrame:(CGRect)frame;
+ (instancetype)keyboardWithFrame:(CGRect)frame;
- (void)setWMKeyboardBlock:(WMKeyboardBlock)block;
@end

只不过这个通用View只是定义了一些初始化方法,程序中数字键盘和表情键盘继承了WMKeyboardView,并在各自的.m文件中实现view中控件的布局。为了实现数字键盘上的数字交换,在WMKeyboardNumberView中定义了交换数字的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)exchangeNumber {
NSMutableArray *numbers = [NSMutableArray array];
int startNum = 0;
int length = 10;
for (int i = startNum; i < length; i++) {
[numbers addObject:[NSString stringWithFormat:@"%d", i]];
}
for (int i = 0; i < self.numberKeys.count; i++) {
WMKeyButton *button = self.numberKeys[i];
if (i == kWMKeyboardNumberDelIndex) {
[button setTitle:DeleteText forState:UIControlStateNormal];
continue;
} else if (i == kWMKeyboardNumberDoneIndex) {
[button setTitle:DoneText forState:UIControlStateNormal];
continue;
}
int index = arc4random() % numbers.count;
[button setTitle:numbers[index] forState:UIControlStateNormal];
[numbers removeObjectAtIndex:index];
}
}

对于这个方法的调用就交给控制器去操心了~

另外,在WMKeyboardSpecialView中定义了setButtonsWithType:(WMKeyboardOtherType)type方法,在切换类型(常用or全部)时更新界面,而每次点击按钮更新button的count属性则是放在了block中。

三.控制器

在控制器中定义了两个控件,textField和textView,要实现键盘中的数字变更在textField的代理方法textFieldShouldBeginEditing:(UITextField *)textField中调用numberKeyboardView的exchangeNumber方法。至于修改textField和textView中文字的方法放在了各自的分类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 修改textField中的文字
*/
- (void)changetext:(NSString *)text {
UITextPosition *beginning = self.beginningOfDocument;
UITextPosition *start = self.selectedTextRange.start;
UITextPosition *end = self.selectedTextRange.end;
NSInteger startIndex = [self offsetFromPosition:beginning toPosition:start];
NSInteger endIndex = [self offsetFromPosition:beginning toPosition:end];
// 将输入框中的文字分成两部分,生成新字符串,判断新字符串是否满足要求
NSString *originText = self.text;
NSString *part1 = [originText substringToIndex:startIndex];
NSString *part2 = [originText substringFromIndex:endIndex];
NSInteger offset;
if (![text isEqualToString:@""]) {
offset = text.length;
} else {
if (startIndex == endIndex) { // 只删除一个字符
if (startIndex == 0) {
return;
}
offset = -1;
part1 = [part1 substringToIndex:(part1.length - 1)];
} else {
offset = 0;
}
}
NSString *newText = [NSString stringWithFormat:@"%@%@%@", part1, text, part2];
self.text = newText;
// 重置光标位置
UITextPosition *now = [self positionFromPosition:start offset:offset];
UITextRange *range = [self textRangeFromPosition:now toPosition:now];
self.selectedTextRange = range;
}

首先我们需要获取到光标的位置从而判断用户想把文字插入的地方,根据光标所在的位置(也可能是一个范围)将文字分成两个部分,判断不同的用户操作对文字进行替换之后我们需要重新设置光标的位置。这样就实现了插入和删除操作。

四.数据存储

表情的文字最初存储在plist里面,在第一次运行的时候读取出来,通过CoreData进行存储以及管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)addKeyboardButtonText {
NSEntityDescription *entity = [NSEntityDescription entityForName:CoreDataStackButton inManagedObjectContext:self.stack.managedObjectContext];
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"KeyboardButtonText" ofType:@"plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:plistPath];
NSArray *textArray = dict[@"texts"];
for (NSString *text in textArray) {
Button *button = [[Button alloc] initWithEntity:entity insertIntoManagedObjectContext:self.stack.managedObjectContext];
button.text = text;
button.count = 0;
}
[self.stack saveContext];
}

好了,整个demo的大体思路就是这样子,自己水平有限,有问题和可以改进的地方也欢迎大家指正。