292 lines
11 KiB
Mathematica
292 lines
11 KiB
Mathematica
|
|
//
|
|||
|
|
// KBKeyboardLegacyBuilder.m
|
|||
|
|
// CustomKeyboard
|
|||
|
|
//
|
|||
|
|
|
|||
|
|
#import "KBKeyboardLegacyBuilder.h"
|
|||
|
|
#import "KBKey.h"
|
|||
|
|
#import "KBKeyButton.h"
|
|||
|
|
#import "KBBackspaceLongPressHandler.h"
|
|||
|
|
#import "KBConfig.h"
|
|||
|
|
#import <Masonry/Masonry.h>
|
|||
|
|
|
|||
|
|
static const CGFloat kKBSpecialKeySquareMultiplier = 1.2;
|
|||
|
|
static const CGFloat kKBReturnWidthMultiplier = 2.4;
|
|||
|
|
static const CGFloat kKBSpaceWidthMultiplier = 3.0;
|
|||
|
|
static inline CGFloat KBLegacyRowHorizontalInset(void) {
|
|||
|
|
return KBFit(6.0f);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@implementation KBKeyboardLegacyBuilder
|
|||
|
|
|
|||
|
|
- (void)buildRow:(UIView *)row
|
|||
|
|
withKeys:(NSArray<KBKey *> *)keys
|
|||
|
|
edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier
|
|||
|
|
shiftOn:(BOOL)shiftOn
|
|||
|
|
backspaceHandler:(KBBackspaceLongPressHandler *)backspaceHandler
|
|||
|
|
target:(id)target
|
|||
|
|
action:(SEL)action {
|
|||
|
|
if (!row || keys.count == 0) { return; }
|
|||
|
|
|
|||
|
|
// 第 4 行(底部控制行)使用单独的布局规则:
|
|||
|
|
// 123/ABC、Emoji、Send 给定尺寸,Space 自动吃掉剩余宽度。
|
|||
|
|
BOOL isBottomControlRow = [self kb_isBottomControlRowWithKeys:keys];
|
|||
|
|
|
|||
|
|
CGFloat spacing = 0; // 键与键之间的间距
|
|||
|
|
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(KBLegacyRowHorizontalInset());
|
|||
|
|
make.centerY.equalTo(row);
|
|||
|
|
make.height.mas_equalTo(1);
|
|||
|
|
}];
|
|||
|
|
[rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
make.right.equalTo(row.mas_right).offset(-KBLegacyRowHorizontalInset());
|
|||
|
|
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 applyThemeForCurrentKey];
|
|||
|
|
if (target && action) {
|
|||
|
|
[btn addTarget:target action:action forControlEvents:UIControlEventTouchDown];
|
|||
|
|
}
|
|||
|
|
[row addSubview:btn];
|
|||
|
|
|
|||
|
|
if (key.type == KBKeyTypeBackspace) {
|
|||
|
|
[backspaceHandler bindDeleteButton:btn showClearLabel:YES];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Shift 按钮选中态随大小写状态变化
|
|||
|
|
if (key.type == KBKeyTypeShift) {
|
|||
|
|
btn.selected = 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(KBLegacyRowHorizontalInset());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}];
|
|||
|
|
|
|||
|
|
// 字符键:等宽
|
|||
|
|
if (key.type == KBKeyTypeCharacter) {
|
|||
|
|
if (previous && [previous isKindOfClass:[KBKeyButton class]]) {
|
|||
|
|
KBKeyButton *prevBtn = (KBKeyButton *)previous;
|
|||
|
|
if (prevBtn.key.type == KBKeyTypeCharacter) {
|
|||
|
|
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
make.width.equalTo(previous);
|
|||
|
|
}];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// special keys: 宽度在第二遍统一设置
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
previous = btn;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 右侧使用内边距或右占位
|
|||
|
|
if (previous) {
|
|||
|
|
[previous mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
if (rightSpacer) {
|
|||
|
|
make.right.equalTo(rightSpacer.mas_left).offset(-spacing);
|
|||
|
|
} else {
|
|||
|
|
make.right.equalTo(row.mas_right).offset(-KBLegacyRowHorizontalInset());
|
|||
|
|
}
|
|||
|
|
}];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 底部控制行:在第一轮已完成左右约束的前提下,仅给 123/ABC、Emoji、Send 指定宽度,
|
|||
|
|
// Space 不加宽度约束,让其自动填充剩余空间。
|
|||
|
|
if (isBottomControlRow) {
|
|||
|
|
[self kb_applyBottomControlRowWidthInRow:row];
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 第二遍:以首个字符键为基准,统一设置特殊键宽度倍数
|
|||
|
|
KBKeyButton *firstChar = nil;
|
|||
|
|
BOOL hasCharacterInRow = NO;
|
|||
|
|
for (UIView *v in row.subviews) {
|
|||
|
|
if (![v isKindOfClass:[KBKeyButton class]]) continue;
|
|||
|
|
KBKeyButton *b = (KBKeyButton *)v;
|
|||
|
|
if (b.key.type == KBKeyTypeCharacter) {
|
|||
|
|
firstChar = b;
|
|||
|
|
hasCharacterInRow = YES;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 若该行没有字符键(例如底部控制行之外的特殊行),则使用行内第一个按钮作为基准宽度
|
|||
|
|
if (!firstChar) {
|
|||
|
|
for (UIView *v in row.subviews) {
|
|||
|
|
if ([v isKindOfClass:[KBKeyButton class]]) {
|
|||
|
|
firstChar = (KBKeyButton *)v;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (firstChar) {
|
|||
|
|
// 如果该行本身没有字符键(如底部控制行),且基准按钮是 123/ABC/#+= 等,
|
|||
|
|
// 也将其约束为 1:1,避免 123/ABC 不是正方形。
|
|||
|
|
if (!hasCharacterInRow &&
|
|||
|
|
(firstChar.key.type == KBKeyTypeModeChange ||
|
|||
|
|
firstChar.key.type == KBKeyTypeSymbolsToggle ||
|
|||
|
|
firstChar.key.type == KBKeyTypeCustom)) {
|
|||
|
|
[firstChar mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
make.width.equalTo(firstChar.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
|||
|
|
}];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (UIView *v in row.subviews) {
|
|||
|
|
if (![v isKindOfClass:[KBKeyButton class]]) continue;
|
|||
|
|
KBKeyButton *b = (KBKeyButton *)v;
|
|||
|
|
|
|||
|
|
// 避免对基准按钮自身添加 self == self * k 的无效约束
|
|||
|
|
if (b == firstChar) continue;
|
|||
|
|
if (b.key.type == KBKeyTypeCharacter) continue;
|
|||
|
|
|
|||
|
|
BOOL isBottomModeKey = (b.key.type == KBKeyTypeModeChange) ||
|
|||
|
|
(b.key.type == KBKeyTypeSymbolsToggle) ||
|
|||
|
|
(b.key.type == KBKeyTypeCustom);
|
|||
|
|
// 一类键强制近似正方形(宽 ~ 高)
|
|||
|
|
if (b.key.type == KBKeyTypeShift ||
|
|||
|
|
b.key.type == KBKeyTypeBackspace ||
|
|||
|
|
isBottomModeKey) {
|
|||
|
|
[b mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
make.width.equalTo(b.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
|||
|
|
}];
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
CGFloat multiplier = 1.5;
|
|||
|
|
|
|||
|
|
// Space:宽度更大
|
|||
|
|
if (b.key.type == KBKeyTypeSpace) {
|
|||
|
|
multiplier = kKBSpaceWidthMultiplier;
|
|||
|
|
}
|
|||
|
|
// Send 按钮:宽度为基准键的 2.4 倍
|
|||
|
|
else if (b.key.type == KBKeyTypeReturn) {
|
|||
|
|
multiplier = kKBReturnWidthMultiplier;
|
|||
|
|
}
|
|||
|
|
// 其它特殊键(如 Globe)保持适度放大
|
|||
|
|
else if (b.key.type == KBKeyTypeGlobe) {
|
|||
|
|
multiplier = 1.5;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[b mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
make.width.equalTo(firstChar).multipliedBy(multiplier);
|
|||
|
|
}];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果有左右占位,则把占位宽度设置为字符键宽度的一定倍数,以实现整体居中;
|
|||
|
|
// 同时强约束左右占位宽度相等,避免在某些系统上由于布局冲突导致只压缩一侧,
|
|||
|
|
// 出现“左侧有空隙,右侧无空隙”的情况。
|
|||
|
|
if (leftSpacer && rightSpacer) {
|
|||
|
|
// 1) 左右占位宽度必须相等
|
|||
|
|
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
make.width.equalTo(rightSpacer);
|
|||
|
|
}];
|
|||
|
|
// 2) 同时都接近字符键宽度的 edgeSpacerMultiplier 倍数
|
|||
|
|
[rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
make.width.equalTo(firstChar).multipliedBy(edgeSpacerMultiplier);
|
|||
|
|
}];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#pragma mark - Row Helpers (Bottom Control Row)
|
|||
|
|
|
|||
|
|
// 判断是否为底部控制行:包含 Space + Return,且有 ModeChange/SymbolsToggle,
|
|||
|
|
// 并且不再含字符键。
|
|||
|
|
- (BOOL)kb_isBottomControlRowWithKeys:(NSArray<KBKey *> *)keys {
|
|||
|
|
BOOL hasSpace = NO;
|
|||
|
|
BOOL hasReturn = NO;
|
|||
|
|
BOOL hasModeOrSymbols = NO;
|
|||
|
|
BOOL hasCharacters = NO;
|
|||
|
|
|
|||
|
|
for (KBKey *k in keys) {
|
|||
|
|
if (k.type == KBKeyTypeSpace) {
|
|||
|
|
hasSpace = YES;
|
|||
|
|
} else if (k.type == KBKeyTypeReturn) {
|
|||
|
|
hasReturn = YES;
|
|||
|
|
} else if (k.type == KBKeyTypeModeChange || k.type == KBKeyTypeSymbolsToggle) {
|
|||
|
|
hasModeOrSymbols = YES;
|
|||
|
|
} else if (k.type == KBKeyTypeCharacter) {
|
|||
|
|
hasCharacters = YES;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return (hasSpace && hasReturn && hasModeOrSymbols && !hasCharacters);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
- (void)kb_applyBottomControlRowWidthInRow:(UIView *)row {
|
|||
|
|
if (!row) { return; }
|
|||
|
|
|
|||
|
|
KBKeyButton *modeBtn = nil;
|
|||
|
|
KBKeyButton *spaceBtn = nil;
|
|||
|
|
KBKeyButton *retBtn = nil;
|
|||
|
|
NSMutableArray<KBKeyButton *> *customButtons = [NSMutableArray array];
|
|||
|
|
|
|||
|
|
for (UIView *v in row.subviews) {
|
|||
|
|
if (![v isKindOfClass:[KBKeyButton class]]) continue;
|
|||
|
|
KBKeyButton *b = (KBKeyButton *)v;
|
|||
|
|
switch (b.key.type) {
|
|||
|
|
case KBKeyTypeModeChange:
|
|||
|
|
case KBKeyTypeSymbolsToggle:
|
|||
|
|
modeBtn = b;
|
|||
|
|
break;
|
|||
|
|
case KBKeyTypeCustom:
|
|||
|
|
[customButtons addObject:b];
|
|||
|
|
break;
|
|||
|
|
case KBKeyTypeSpace:
|
|||
|
|
spaceBtn = b;
|
|||
|
|
break;
|
|||
|
|
case KBKeyTypeReturn:
|
|||
|
|
retBtn = b;
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!modeBtn || customButtons.count == 0 || !spaceBtn || !retBtn) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
[modeBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
make.width.equalTo(row.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
|||
|
|
}];
|
|||
|
|
for (KBKeyButton *custom in customButtons) {
|
|||
|
|
[custom mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
make.width.equalTo(row.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
|||
|
|
}];
|
|||
|
|
}
|
|||
|
|
[retBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
|||
|
|
make.width.equalTo(modeBtn.mas_width).multipliedBy(2.0);
|
|||
|
|
}];
|
|||
|
|
// Space 不设置宽度;通过此前已建立的左右约束自动占满剩余宽度。
|
|||
|
|
(void)spaceBtn;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@end
|