Files
keyboard/CustomKeyboard/View/KBKeyboardView.m

356 lines
16 KiB
Mathematica
Raw Normal View History

2025-10-28 10:18:10 +08:00
//
// KBKeyboardView.m
// CustomKeyboard
//
#import "KBKeyboardView.h"
#import "KBKeyButton.h"
#import "KBKey.h"
2025-11-04 16:37:24 +08:00
#import "KBResponderUtils.h" //
2025-11-04 21:01:46 +08:00
#import "KBSkinManager.h"
2025-10-28 10:18:10 +08:00
@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;
2025-11-04 16:37:24 +08:00
// 退使 NSTimer GCD
@property (nonatomic, assign) BOOL backspaceHoldActive;
2025-10-28 10:18:10 +08:00
@end
@implementation KBKeyboardView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
2025-11-04 21:01:46 +08:00
self.backgroundColor = [KBSkinManager shared].current.keyboardBackground;
2025-10-28 10:18:10 +08:00
_layoutStyle = KBKeyboardLayoutStyleLetters;
2025-10-28 19:24:35 +08:00
// Shift
_shiftOn = NO;
2025-10-28 20:03:43 +08:00
_symbolsMoreOn = NO; // 123
2025-10-28 10:18:10 +08:00
[self buildBase];
[self reloadKeys];
}
return self;
}
2025-10-28 20:03:43 +08:00
// /
- (void)setLayoutStyle:(KBKeyboardLayoutStyle)layoutStyle {
_layoutStyle = layoutStyle;
if (_layoutStyle != KBKeyboardLayoutStyleNumbers) {
_symbolsMoreOn = NO;
}
}
2025-10-28 10:18:10 +08:00
- (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 +
2025-10-28 20:03:43 +08:00
NSArray *r1 = nil;
NSArray *r2 = nil;
NSArray *r3 = nil;
if (!self.symbolsMoreOn) {
// 123
r1 = @[ [KBKey keyWithTitle:@"1" output:@"1"], [KBKey keyWithTitle:@"2" output:@"2"], [KBKey keyWithTitle:@"3" output:@"3"],
[KBKey keyWithTitle:@"4" output:@"4"], [KBKey keyWithTitle:@"5" output:@"5"], [KBKey keyWithTitle:@"6" output:@"6"],
[KBKey keyWithTitle:@"7" output:@"7"], [KBKey keyWithTitle:@"8" output:@"8"], [KBKey keyWithTitle:@"9" output:@"9"], [KBKey keyWithTitle:@"0" output:@"0"] ];
r2 = @[ [KBKey keyWithTitle:@"-" output:@"-"], [KBKey keyWithTitle:@"/" output:@"/"], [KBKey keyWithTitle:@":" output:@":"],
[KBKey keyWithTitle:@";" output:@";"], [KBKey keyWithTitle:@"(" output:@"("], [KBKey keyWithTitle:@")" output:@")"],
[KBKey keyWithTitle:@"$" output:@"$"], [KBKey keyWithTitle:@"&" output:@"&"], [KBKey keyWithTitle:@"@" output:@"@"], [KBKey keyWithTitle:@"\"" output:@"\""] ];
r3 = @[ [KBKey keyWithTitle:@"#+=" type:KBKeyTypeSymbolsToggle],
[KBKey keyWithTitle:@"," output:@","], [KBKey keyWithTitle:@"." output:@"."], [KBKey keyWithTitle:@"?" output:@"?"],
[KBKey keyWithTitle:@"!" output:@"!"], [KBKey keyWithTitle:@"'" output:@"'"],
[KBKey keyWithTitle:@"⌫" type:KBKeyTypeBackspace] ];
} else {
// #+=123
r1 = @[ [KBKey keyWithTitle:@"[" output:@"["], [KBKey keyWithTitle:@"]" output:@"]"], [KBKey keyWithTitle:@"{" output:@"{"],
[KBKey keyWithTitle:@"}" output:@"}"], [KBKey keyWithTitle:@"#" output:@"#"], [KBKey keyWithTitle:@"%" output:@"%"],
[KBKey keyWithTitle:@"^" output:@"^"], [KBKey keyWithTitle:@"*" output:@"*"], [KBKey keyWithTitle:@"+" output:@"+"],
[KBKey keyWithTitle:@"=" output:@"="] ];
r2 = @[ [KBKey keyWithTitle:@"_" output:@"_"], [KBKey keyWithTitle:@"\\" output:@"\\"], [KBKey keyWithTitle:@"|" output:@"|"],
[KBKey keyWithTitle:@"~" output:@"~"], [KBKey keyWithTitle:@"<" output:@"<"], [KBKey keyWithTitle:@">" output:@">"],
[KBKey keyWithTitle:@"$" output:@"$"], [KBKey keyWithTitle:@"€" output:@"€"], [KBKey keyWithTitle:@"£" output:@"£"],
[KBKey keyWithTitle:@"•" output:@"•"] ];
r3 = @[ [KBKey keyWithTitle:@"123" type:KBKeyTypeSymbolsToggle],
[KBKey keyWithTitle:@"," output:@","], [KBKey keyWithTitle:@"." output:@"."], [KBKey keyWithTitle:@"?" output:@"?"],
[KBKey keyWithTitle:@"!" output:@"!"], [KBKey keyWithTitle:@"'" output:@"'"],
[KBKey keyWithTitle:@"⌫" type:KBKeyTypeBackspace] ];
}
2025-10-29 12:59:22 +08:00
NSArray *r4 = @[ [KBKey keyWithTitle:@"abc" type:KBKeyTypeModeChange],
[KBKey keyWithTitle:@"AI" type:KBKeyTypeCustom],
2025-10-28 20:03:43 +08:00
[KBKey keyWithTitle:@"space" type:KBKeyTypeSpace],
2025-11-17 20:07:39 +08:00
[KBKey keyWithTitle:KBLocalized(@"Send") type:KBKeyTypeReturn] ];
2025-10-28 20:03:43 +08:00
2025-10-28 10:18:10 +08:00
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];
2025-10-28 19:24:35 +08:00
// Shift
for (NSString *s in r1) {
NSString *shown = self.shiftOn ? s : s.lowercaseString;
[row1 addObject:[KBKey keyWithTitle:shown output:shown]];
}
2025-10-28 10:18:10 +08:00
NSMutableArray *row2 = [NSMutableArray arrayWithCapacity:r2.count];
2025-10-28 19:24:35 +08:00
for (NSString *s in r2) {
NSString *shown = self.shiftOn ? s : s.lowercaseString;
[row2 addObject:[KBKey keyWithTitle:shown output:shown]];
}
2025-10-28 10:18:10 +08:00
NSMutableArray *row3 = [NSMutableArray array];
[row3 addObject:[KBKey keyWithTitle:@"⇧" type:KBKeyTypeShift]];
2025-10-28 19:24:35 +08:00
for (NSString *s in r3chars) {
NSString *shown = self.shiftOn ? s : s.lowercaseString;
[row3 addObject:[KBKey keyWithTitle:shown output:shown]];
}
2025-10-28 10:18:10 +08:00
[row3 addObject:[KBKey keyWithTitle:@"⌫" type:KBKeyTypeBackspace]];
NSArray *row4 = @[ [KBKey keyWithTitle:@"123" type:KBKeyTypeModeChange],
2025-10-29 12:59:22 +08:00
[KBKey keyWithTitle:@"AI" type:KBKeyTypeCustom],
2025-10-28 10:18:10 +08:00
[KBKey keyWithTitle:@"space" type:KBKeyTypeSpace],
2025-11-17 20:07:39 +08:00
[KBKey keyWithTitle:KBLocalized(@"Send") type:KBKeyTypeReturn] ];
2025-10-28 10:18:10 +08:00
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];
2025-11-04 16:37:24 +08:00
// 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];
}
2025-10-28 20:11:40 +08:00
// Shift
if (key.type == KBKeyTypeShift) {
btn.selected = self.shiftOn;
}
2025-10-28 10:18:10 +08:00
[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; }
}
2025-10-29 12:59:22 +08:00
// 使
if (!firstChar) {
for (KBKeyButton *b in row.subviews) { if ([b isKindOfClass:[KBKeyButton class]]) { firstChar = b; break; } }
}
2025-10-28 10:18:10 +08:00
if (firstChar) {
for (KBKeyButton *b in row.subviews) {
if (![b isKindOfClass:[KBKeyButton class]]) continue;
// firstChar
// self == self * k
if (b == firstChar) continue;
2025-10-28 10:18:10 +08:00
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;
}
2025-10-28 20:03:43 +08:00
if (key.type == KBKeyTypeSymbolsToggle) {
// 123 <-> #+=
self.symbolsMoreOn = !self.symbolsMoreOn;
[self reloadKeys];
return;
}
2025-10-28 10:18:10 +08:00
if ([self.delegate respondsToSelector:@selector(keyboardView:didTapKey:)]) {
[self.delegate keyboardView:self didTapKey:key];
}
}
2025-11-04 16:37:24 +08:00
// 退使 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];
});
}
2025-10-28 10:18:10 +08:00
#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