Files
keyboard/CustomKeyboard/View/KBKeyboardView/KBKeyboardLegacyBuilder.m
2026-03-04 12:54:57 +08:00

292 lines
11 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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