457 lines
23 KiB
Objective-C
457 lines
23 KiB
Objective-C
//
|
||
// KBKeyboardView.m
|
||
// CustomKeyboard
|
||
//
|
||
|
||
#import "KBKeyboardView.h"
|
||
#import "KBKeyButton.h"
|
||
#import "KBKey.h"
|
||
#import "KBResponderUtils.h" // 封装的响应链工具
|
||
#import "KBSkinManager.h"
|
||
|
||
@interface KBKeyboardView ()
|
||
@property (nonatomic, strong) UIView *row1;
|
||
@property (nonatomic, strong) UIView *row2;
|
||
@property (nonatomic, strong) UIView *row3;
|
||
@property (nonatomic, strong) UIView *row4;
|
||
@property (nonatomic, strong) NSArray<NSArray<KBKey *> *> *keysForRows;
|
||
// 长按退格的一次次删除控制标记(不使用 NSTimer,仅用 GCD 递归调度)
|
||
@property (nonatomic, assign) BOOL backspaceHoldActive;
|
||
@end
|
||
|
||
@implementation KBKeyboardView
|
||
|
||
- (instancetype)initWithFrame:(CGRect)frame {
|
||
if (self = [super initWithFrame:frame]) {
|
||
self.backgroundColor = [KBSkinManager shared].current.keyboardBackground;
|
||
_layoutStyle = KBKeyboardLayoutStyleLetters;
|
||
// 默认小写:与需求一致,初始不开启 Shift
|
||
_shiftOn = NO;
|
||
_symbolsMoreOn = NO; // 数字面板默认第一页(123)
|
||
[self buildBase];
|
||
[self reloadKeys];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
// 当切换大布局(字母/数字)时,重置数字二级页状态
|
||
- (void)setLayoutStyle:(KBKeyboardLayoutStyle)layoutStyle {
|
||
_layoutStyle = layoutStyle;
|
||
if (_layoutStyle != KBKeyboardLayoutStyleNumbers) {
|
||
_symbolsMoreOn = NO;
|
||
}
|
||
}
|
||
|
||
- (void)buildBase {
|
||
[self addSubview:self.row1];
|
||
[self addSubview:self.row2];
|
||
[self addSubview:self.row3];
|
||
[self addSubview:self.row4];
|
||
|
||
CGFloat vSpacing = 8;
|
||
[self.row1 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.top.equalTo(self.mas_top).offset(8);
|
||
make.left.right.equalTo(self);
|
||
make.height.mas_equalTo(44);
|
||
}];
|
||
[self.row2 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.top.equalTo(self.row1.mas_bottom).offset(vSpacing);
|
||
make.left.right.equalTo(self);
|
||
make.height.equalTo(self.row1);
|
||
}];
|
||
[self.row3 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.top.equalTo(self.row2.mas_bottom).offset(vSpacing);
|
||
make.left.right.equalTo(self);
|
||
make.height.equalTo(self.row1);
|
||
}];
|
||
[self.row4 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.top.equalTo(self.row3.mas_bottom).offset(vSpacing);
|
||
make.left.right.equalTo(self);
|
||
make.height.equalTo(self.row1);
|
||
make.bottom.equalTo(self.mas_bottom).offset(-6);
|
||
}];
|
||
}
|
||
|
||
- (void)reloadKeys {
|
||
// 移除旧按钮
|
||
for (UIView *row in @[self.row1, self.row2, self.row3, self.row4]) {
|
||
[row.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||
}
|
||
|
||
self.keysForRows = [self buildKeysForCurrentLayout];
|
||
[self buildRow:self.row1 withKeys:self.keysForRows[0]];
|
||
// 第二行:字母布局时通过左右等宽占位让整行居中
|
||
CGFloat row2Spacer = (self.layoutStyle == KBKeyboardLayoutStyleLetters) ? 0.5 : 0.0;
|
||
[self buildRow:self.row2 withKeys:self.keysForRows[1] edgeSpacerMultiplier:row2Spacer];
|
||
[self buildRow:self.row3 withKeys:self.keysForRows[2]];
|
||
[self buildRow:self.row4 withKeys:self.keysForRows[3]];
|
||
}
|
||
|
||
- (NSArray<NSArray<KBKey *> *> *)buildKeysForCurrentLayout {
|
||
if (self.layoutStyle == KBKeyboardLayoutStyleNumbers) {
|
||
// 数字/符号布局:3 行主键 + 底部控制行
|
||
NSArray *r1 = nil;
|
||
NSArray *r2 = nil;
|
||
NSArray *r3 = nil;
|
||
|
||
if (!self.symbolsMoreOn) {
|
||
// 数字第一页(123)
|
||
r1 = @[ [KBKey keyWithIdentifier:@"digit_1" title:@"1" output:@"1" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"digit_2" title:@"2" output:@"2" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"digit_3" title:@"3" output:@"3" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"digit_4" title:@"4" output:@"4" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"digit_5" title:@"5" output:@"5" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"digit_6" title:@"6" output:@"6" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"digit_7" title:@"7" output:@"7" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"digit_8" title:@"8" output:@"8" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"digit_9" title:@"9" output:@"9" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"digit_0" title:@"0" output:@"0" type:KBKeyTypeCharacter] ];
|
||
r2 = @[ [KBKey keyWithIdentifier:@"sym_minus" title:@"-" output:@"-" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_slash" title:@"/" output:@"/" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_colon" title:@":" output:@":" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_semicolon" title:@";" output:@";" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_paren_l" title:@"(" output:@"(" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_paren_r" title:@")" output:@")" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_dollar" title:@"$" output:@"$" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_amp" title:@"&" output:@"&" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_at" title:@"@" output:@"@" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_quote_double" title:@"\"" output:@"\"" type:KBKeyTypeCharacter] ];
|
||
// 第三行:左下角是“#+=”切换键,右下角是退格键
|
||
KBKey *toggle = [KBKey keyWithIdentifier:@"symbols_toggle_more"
|
||
title:@"#+="
|
||
output:@""
|
||
type:KBKeyTypeSymbolsToggle];
|
||
KBKey *comma = [KBKey keyWithIdentifier:@"sym_comma" title:@"," output:@"," type:KBKeyTypeCharacter];
|
||
KBKey *dot = [KBKey keyWithIdentifier:@"sym_dot" title:@"." output:@"." type:KBKeyTypeCharacter];
|
||
KBKey *q = [KBKey keyWithIdentifier:@"sym_question" title:@"?" output:@"?" type:KBKeyTypeCharacter];
|
||
KBKey *ex = [KBKey keyWithIdentifier:@"sym_exclam" title:@"!" output:@"!" type:KBKeyTypeCharacter];
|
||
KBKey *quote = [KBKey keyWithIdentifier:@"sym_quote_single" title:@"'" output:@"'" type:KBKeyTypeCharacter];
|
||
KBKey *back = [KBKey keyWithIdentifier:@"backspace"
|
||
title:@"⌫"
|
||
output:@""
|
||
type:KBKeyTypeBackspace];
|
||
r3 = @[ toggle, comma, dot, q, ex, quote, back ];
|
||
} else {
|
||
// 数字第二页(#+=):前两行替换为更多符号,左下角按钮文案改为“123”
|
||
r1 = @[ [KBKey keyWithIdentifier:@"sym_bracket_l" title:@"[" output:@"[" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_bracket_r" title:@"]" output:@"]" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_brace_l" title:@"{" output:@"{" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_brace_r" title:@"}" output:@"}" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_hash" title:@"#" output:@"#" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_percent" title:@"%" output:@"%" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_caret" title:@"^" output:@"^" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_asterisk" title:@"*" output:@"*" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_plus" title:@"+" output:@"+" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_equal" title:@"=" output:@"=" type:KBKeyTypeCharacter] ];
|
||
r2 = @[ [KBKey keyWithIdentifier:@"sym_underscore" title:@"_" output:@"_" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_backslash" title:@"\\" output:@"\\" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_pipe" title:@"|" output:@"|" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_tilde" title:@"~" output:@"~" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_lt" title:@"<" output:@"<" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_gt" title:@">" output:@">" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_dollar" title:@"$" output:@"$" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_euro" title:@"€" output:@"€" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_pound" title:@"£" output:@"£" type:KBKeyTypeCharacter],
|
||
[KBKey keyWithIdentifier:@"sym_bullet" title:@"•" output:@"•" type:KBKeyTypeCharacter] ];
|
||
KBKey *toggle = [KBKey keyWithIdentifier:@"symbols_toggle_123"
|
||
title:@"123"
|
||
output:@""
|
||
type:KBKeyTypeSymbolsToggle];
|
||
KBKey *comma = [KBKey keyWithIdentifier:@"sym_comma" title:@"," output:@"," type:KBKeyTypeCharacter];
|
||
KBKey *dot = [KBKey keyWithIdentifier:@"sym_dot" title:@"." output:@"." type:KBKeyTypeCharacter];
|
||
KBKey *q = [KBKey keyWithIdentifier:@"sym_question" title:@"?" output:@"?" type:KBKeyTypeCharacter];
|
||
KBKey *ex = [KBKey keyWithIdentifier:@"sym_exclam" title:@"!" output:@"!" type:KBKeyTypeCharacter];
|
||
KBKey *quote = [KBKey keyWithIdentifier:@"sym_quote_single" title:@"'" output:@"'" type:KBKeyTypeCharacter];
|
||
KBKey *back = [KBKey keyWithIdentifier:@"backspace"
|
||
title:@"⌫"
|
||
output:@""
|
||
type:KBKeyTypeBackspace];
|
||
r3 = @[ toggle, comma, dot, q, ex, quote, back ];
|
||
}
|
||
|
||
KBKey *modeABC = [KBKey keyWithIdentifier:@"mode_abc"
|
||
title:@"abc"
|
||
output:@""
|
||
type:KBKeyTypeModeChange];
|
||
KBKey *customAI = [KBKey keyWithIdentifier:@"ai"
|
||
title:@"AI"
|
||
output:@""
|
||
type:KBKeyTypeCustom];
|
||
KBKey *space = [KBKey keyWithIdentifier:@"space"
|
||
title:@"space"
|
||
output:@" "
|
||
type:KBKeyTypeSpace];
|
||
KBKey *ret = [KBKey keyWithIdentifier:@"return"
|
||
title:KBLocalized(@"Send")
|
||
output:@"\n"
|
||
type:KBKeyTypeReturn];
|
||
|
||
NSArray *r4 = @[ modeABC, customAI, space, ret ];
|
||
|
||
return @[r1, r2, r3, r4];
|
||
}
|
||
|
||
// 字母布局(QWERTY)
|
||
NSArray *r1 = @[ @"Q", @"W", @"E", @"R", @"T", @"Y", @"U", @"I", @"O", @"P" ];
|
||
NSArray *r2 = @[ @"A", @"S", @"D", @"F", @"G", @"H", @"J", @"K", @"L" ];
|
||
NSArray *r3chars = @[ @"Z", @"X", @"C", @"V", @"B", @"N", @"M" ];
|
||
|
||
NSMutableArray *row1 = [NSMutableArray arrayWithCapacity:r1.count];
|
||
// 字母键标题与输出同时随 Shift 切换大小写,界面与输入保持一致
|
||
for (NSString *s in r1) {
|
||
NSString *shown = self.shiftOn ? s : s.lowercaseString;
|
||
NSString *identifier = [NSString stringWithFormat:@"letter_%@", s.lowercaseString];
|
||
KBKey *k = [KBKey keyWithIdentifier:identifier
|
||
title:shown
|
||
output:shown
|
||
type:KBKeyTypeCharacter];
|
||
k.caseVariant = self.shiftOn ? KBKeyCaseVariantUpper : KBKeyCaseVariantLower;
|
||
[row1 addObject:k];
|
||
}
|
||
|
||
NSMutableArray *row2 = [NSMutableArray arrayWithCapacity:r2.count];
|
||
for (NSString *s in r2) {
|
||
NSString *shown = self.shiftOn ? s : s.lowercaseString;
|
||
NSString *identifier = [NSString stringWithFormat:@"letter_%@", s.lowercaseString];
|
||
KBKey *k = [KBKey keyWithIdentifier:identifier
|
||
title:shown
|
||
output:shown
|
||
type:KBKeyTypeCharacter];
|
||
k.caseVariant = self.shiftOn ? KBKeyCaseVariantUpper : KBKeyCaseVariantLower;
|
||
[row2 addObject:k];
|
||
}
|
||
|
||
NSMutableArray *row3 = [NSMutableArray array];
|
||
KBKey *shift = [KBKey keyWithIdentifier:@"shift"
|
||
title:@"⇧"
|
||
output:@""
|
||
type:KBKeyTypeShift];
|
||
[row3 addObject:shift];
|
||
for (NSString *s in r3chars) {
|
||
NSString *shown = self.shiftOn ? s : s.lowercaseString;
|
||
NSString *identifier = [NSString stringWithFormat:@"letter_%@", s.lowercaseString];
|
||
KBKey *k = [KBKey keyWithIdentifier:identifier
|
||
title:shown
|
||
output:shown
|
||
type:KBKeyTypeCharacter];
|
||
k.caseVariant = self.shiftOn ? KBKeyCaseVariantUpper : KBKeyCaseVariantLower;
|
||
[row3 addObject:k];
|
||
}
|
||
KBKey *backspace = [KBKey keyWithIdentifier:@"backspace"
|
||
title:@"⌫"
|
||
output:@""
|
||
type:KBKeyTypeBackspace];
|
||
[row3 addObject:backspace];
|
||
|
||
KBKey *mode123 = [KBKey keyWithIdentifier:@"mode_123"
|
||
title:@"123"
|
||
output:@""
|
||
type:KBKeyTypeModeChange];
|
||
KBKey *customAI = [KBKey keyWithIdentifier:@"ai"
|
||
title:@"AI"
|
||
output:@""
|
||
type:KBKeyTypeCustom];
|
||
KBKey *space = [KBKey keyWithIdentifier:@"space"
|
||
title:@"space"
|
||
output:@" "
|
||
type:KBKeyTypeSpace];
|
||
KBKey *ret = [KBKey keyWithIdentifier:@"return"
|
||
title:KBLocalized(@"Send")
|
||
output:@"\n"
|
||
type:KBKeyTypeReturn];
|
||
|
||
NSArray *row4 = @[ mode123, customAI, space, ret ];
|
||
|
||
return @[row1.copy, row2.copy, row3.copy, row4];
|
||
}
|
||
|
||
- (void)buildRow:(UIView *)row withKeys:(NSArray<KBKey *> *)keys {
|
||
[self buildRow:row withKeys:keys edgeSpacerMultiplier:0.0];
|
||
}
|
||
|
||
- (void)buildRow:(UIView *)row withKeys:(NSArray<KBKey *> *)keys edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
|
||
CGFloat hInset = 6; // 行左右内边距
|
||
CGFloat spacing = 6; // 键与键之间的间距
|
||
UIView *previous = nil;
|
||
UIView *leftSpacer = nil;
|
||
UIView *rightSpacer = nil;
|
||
if (edgeSpacerMultiplier > 0.0) {
|
||
leftSpacer = [UIView new];
|
||
rightSpacer = [UIView new];
|
||
leftSpacer.backgroundColor = [UIColor clearColor];
|
||
rightSpacer.backgroundColor = [UIColor clearColor];
|
||
[row addSubview:leftSpacer];
|
||
[row addSubview:rightSpacer];
|
||
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.left.equalTo(row.mas_left).offset(hInset);
|
||
make.centerY.equalTo(row);
|
||
make.height.mas_equalTo(1);
|
||
}];
|
||
[rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.right.equalTo(row.mas_right).offset(-hInset);
|
||
make.centerY.equalTo(row);
|
||
make.height.mas_equalTo(1);
|
||
}];
|
||
}
|
||
for (NSInteger i = 0; i < keys.count; i++) {
|
||
KBKey *key = keys[i];
|
||
KBKeyButton *btn = [[KBKeyButton alloc] init];
|
||
btn.key = key;
|
||
[btn setTitle:key.title forState:UIControlStateNormal];
|
||
[btn addTarget:self action:@selector(onKeyTapped:) forControlEvents:UIControlEventTouchUpInside];
|
||
[row addSubview:btn];
|
||
|
||
// ⌫ 长按:开始连续逐个删除(无需 NSTimer)。使用 UILongPressGestureRecognizer 识别长按,
|
||
// 在开始态触发递归的轻量调度,每次删除 1 个字符,直到松手或无内容。
|
||
if (key.type == KBKeyTypeBackspace) {
|
||
UILongPressGestureRecognizer *lp = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onBackspaceLongPress:)];
|
||
// 稍短的判定时间,提升响应(默认约 0.5s)。
|
||
lp.minimumPressDuration = 0.35;
|
||
lp.cancelsTouchesInView = YES; // 被识别为长按时,取消普通点击
|
||
[btn addGestureRecognizer:lp];
|
||
}
|
||
|
||
// Shift 按钮选中态随大小写状态变化
|
||
if (key.type == KBKeyTypeShift) {
|
||
btn.selected = self.shiftOn;
|
||
}
|
||
|
||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.top.bottom.equalTo(row);
|
||
if (previous) {
|
||
make.left.equalTo(previous.mas_right).offset(spacing);
|
||
} else {
|
||
if (leftSpacer) {
|
||
make.left.equalTo(leftSpacer.mas_right).offset(spacing);
|
||
} else {
|
||
make.left.equalTo(row.mas_left).offset(hInset);
|
||
}
|
||
}
|
||
}];
|
||
|
||
// 宽度规则:字符键等宽;特殊键按倍数放大
|
||
if (key.type == KBKeyTypeCharacter) {
|
||
if (previous && previous != nil) {
|
||
if (((KBKeyButton *)previous).key.type == KBKeyTypeCharacter) {
|
||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(previous);
|
||
}];
|
||
}
|
||
}
|
||
} else {
|
||
// special keys: give 1.5x of a character key by deferring constraint equalities after loop
|
||
}
|
||
|
||
previous = btn;
|
||
}
|
||
// 右侧使用内边距或右占位
|
||
[previous mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
if (rightSpacer) {
|
||
make.right.equalTo(rightSpacer.mas_left).offset(-spacing);
|
||
} else {
|
||
make.right.equalTo(row.mas_right).offset(-hInset);
|
||
}
|
||
}];
|
||
|
||
// 第二遍:以首个字符键为基准,统一设置特殊键宽度倍数
|
||
KBKeyButton *firstChar = nil;
|
||
for (KBKeyButton *b in row.subviews) {
|
||
if ([b isKindOfClass:[KBKeyButton class]] && b.key.type == KBKeyTypeCharacter) { firstChar = b; break; }
|
||
}
|
||
// 若该行没有字符键(例如底部控制行),则使用行内第一个按钮作为基准宽度
|
||
if (!firstChar) {
|
||
for (KBKeyButton *b in row.subviews) { if ([b isKindOfClass:[KBKeyButton class]]) { firstChar = b; break; } }
|
||
}
|
||
if (firstChar) {
|
||
for (KBKeyButton *b in row.subviews) {
|
||
if (![b isKindOfClass:[KBKeyButton class]]) continue;
|
||
// 当本行没有字符键时,firstChar 可能是一个“特殊键”,
|
||
// 避免对基准按钮自身添加 self == self * k 的无效约束
|
||
if (b == firstChar) continue;
|
||
if (b.key.type == KBKeyTypeCharacter) continue;
|
||
CGFloat multiplier = 1.5;
|
||
if (b.key.type == KBKeyTypeSpace) multiplier = 4.0;
|
||
if (b.key.type == KBKeyTypeReturn) multiplier = 1.8;
|
||
if (b.key.type == KBKeyTypeModeChange || b.key.type == KBKeyTypeGlobe || b.key.type == KBKeyTypeShift || b.key.type == KBKeyTypeBackspace) {
|
||
multiplier = 1.5;
|
||
}
|
||
[b mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(firstChar).multipliedBy(multiplier);
|
||
}];
|
||
}
|
||
// 如果有左右占位,则把占位宽度设置为字符键宽度的一定倍数,以实现整体居中
|
||
if (leftSpacer && rightSpacer) {
|
||
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(firstChar).multipliedBy(edgeSpacerMultiplier);
|
||
}];
|
||
[rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.width.equalTo(firstChar).multipliedBy(edgeSpacerMultiplier);
|
||
}];
|
||
}
|
||
}
|
||
}
|
||
|
||
#pragma mark - Actions
|
||
|
||
- (void)onKeyTapped:(KBKeyButton *)sender {
|
||
KBKey *key = sender.key;
|
||
if (key.type == KBKeyTypeShift) {
|
||
self.shiftOn = !self.shiftOn;
|
||
[self reloadKeys];
|
||
return;
|
||
}
|
||
if (key.type == KBKeyTypeSymbolsToggle) {
|
||
// 在数字布局内切换 123 <-> #+=
|
||
self.symbolsMoreOn = !self.symbolsMoreOn;
|
||
[self reloadKeys];
|
||
return;
|
||
}
|
||
if ([self.delegate respondsToSelector:@selector(keyboardView:didTapKey:)]) {
|
||
[self.delegate keyboardView:self didTapKey:key];
|
||
}
|
||
}
|
||
|
||
// 长按退格:按住时以小间隔逐个删除;松手停止。(不使用 NSTimer/DisplayLink)
|
||
- (void)onBackspaceLongPress:(UILongPressGestureRecognizer *)gr {
|
||
switch (gr.state) {
|
||
case UIGestureRecognizerStateBegan: {
|
||
self.backspaceHoldActive = YES;
|
||
[self kb_backspaceStep];
|
||
} break;
|
||
case UIGestureRecognizerStateEnded:
|
||
case UIGestureRecognizerStateCancelled:
|
||
case UIGestureRecognizerStateFailed: {
|
||
self.backspaceHoldActive = NO;
|
||
} break;
|
||
default: break;
|
||
}
|
||
}
|
||
|
||
#pragma mark - Helpers
|
||
|
||
// 单步删除并在需要时安排下一次,直到松手或无内容
|
||
- (void)kb_backspaceStep {
|
||
if (!self.backspaceHoldActive) { return; }
|
||
UIInputViewController *ivc = KBFindInputViewController(self);
|
||
if (!ivc) { self.backspaceHoldActive = NO; return; }
|
||
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
||
NSString *before = proxy.documentContextBeforeInput ?: @"";
|
||
if (before.length <= 0) { self.backspaceHoldActive = NO; return; }
|
||
[proxy deleteBackward]; // 每次仅删 1 个
|
||
|
||
__weak typeof(self) weakSelf = self;
|
||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.06 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||
__strong typeof(weakSelf) selfStrong = weakSelf;
|
||
[selfStrong kb_backspaceStep];
|
||
});
|
||
}
|
||
|
||
#pragma mark - Lazy
|
||
|
||
- (UIView *)row1 { if (!_row1) _row1 = [UIView new]; return _row1; }
|
||
- (UIView *)row2 { if (!_row2) _row2 = [UIView new]; return _row2; }
|
||
- (UIView *)row3 { if (!_row3) _row3 = [UIView new]; return _row3; }
|
||
- (UIView *)row4 { if (!_row4) _row4 = [UIView new]; return _row4; }
|
||
|
||
@end
|