// // KBKeyboardLegacyBuilder.m // CustomKeyboard // #import "KBKeyboardLegacyBuilder.h" #import "KBKey.h" #import "KBKeyButton.h" #import "KBBackspaceLongPressHandler.h" #import "KBConfig.h" #import 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 *)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 *)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 *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