// // KBKeyboardRowBuilder.m // CustomKeyboard // #import "KBKeyboardRowBuilder.h" #import "KBKeyboardLayoutConfig.h" #import "KBKeyboardLayoutEngine.h" #import "KBKeyboardKeyFactory.h" #import "KBKeyButton.h" #import "KBKey.h" #import "KBBackspaceLongPressHandler.h" #import "KBSkinManager.h" #import @interface KBKeyboardRowBuilder () @property (nonatomic, strong) KBKeyboardLayoutConfig *layoutConfig; @property (nonatomic, strong) KBKeyboardLayoutEngine *layoutEngine; @property (nonatomic, strong) KBKeyboardKeyFactory *keyFactory; @end @implementation KBKeyboardRowBuilder - (instancetype)initWithLayoutConfig:(KBKeyboardLayoutConfig *)layoutConfig layoutEngine:(KBKeyboardLayoutEngine *)layoutEngine keyFactory:(KBKeyboardKeyFactory *)keyFactory { self = [super init]; if (self) { _layoutConfig = layoutConfig; _layoutEngine = layoutEngine; _keyFactory = keyFactory; } return self; } - (void)buildRow:(UIView *)row withRowConfig:(KBKeyboardRowConfig *)rowConfig uniformCharWidth:(CGFloat)uniformCharWidth shiftOn:(BOOL)shiftOn backspaceHandler:(KBBackspaceLongPressHandler *)backspaceHandler target:(id)target action:(SEL)action { if (!row || !rowConfig) { return; } CGFloat gap = [self.layoutEngine gapForRow:rowConfig]; CGFloat insetLeft = [self.layoutEngine insetLeftForRow:rowConfig]; CGFloat insetRight = [self.layoutEngine insetRightForRow:rowConfig]; if (rowConfig.segments) { KBKeyboardRowSegments *segments = rowConfig.segments; NSArray *leftItems = [segments leftItems]; NSArray *centerItems = [segments centerItems]; NSArray *rightItems = [segments rightItems]; UIView *leftContainer = [UIView new]; UIView *centerContainer = [UIView new]; UIView *rightContainer = [UIView new]; [row addSubview:leftContainer]; [row addSubview:centerContainer]; [row addSubview:rightContainer]; [leftContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(row.mas_left).offset(insetLeft); make.top.bottom.equalTo(row); }]; [rightContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(row.mas_right).offset(-insetRight); make.top.bottom.equalTo(row); }]; [centerContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(row); make.top.bottom.equalTo(row); make.left.greaterThanOrEqualTo(leftContainer.mas_right).offset(gap); make.right.lessThanOrEqualTo(rightContainer.mas_left).offset(-gap); }]; if (leftItems.count == 0) { [leftContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(0); }]; } if (centerItems.count == 0) { [centerContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(0); }]; } if (rightItems.count == 0) { [rightContainer mas_makeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(0); }]; } [self kb_buildButtonsInContainer:leftContainer items:leftItems gap:gap insetLeft:0 insetRight:0 alignCenter:NO isTopLevelRow:NO uniformCharWidth:uniformCharWidth shiftOn:shiftOn backspaceHandler:backspaceHandler target:target action:action]; [self kb_buildButtonsInContainer:centerContainer items:centerItems gap:gap insetLeft:0 insetRight:0 alignCenter:NO isTopLevelRow:NO uniformCharWidth:uniformCharWidth shiftOn:shiftOn backspaceHandler:backspaceHandler target:target action:action]; [self kb_buildButtonsInContainer:rightContainer items:rightItems gap:gap insetLeft:0 insetRight:0 alignCenter:NO isTopLevelRow:NO uniformCharWidth:uniformCharWidth shiftOn:shiftOn backspaceHandler:backspaceHandler target:target action:action]; return; } BOOL alignCenter = [rowConfig.align.lowercaseString isEqualToString:@"center"]; [self kb_buildButtonsInContainer:row items:[rowConfig resolvedItems] gap:gap insetLeft:insetLeft insetRight:insetRight alignCenter:alignCenter isTopLevelRow:YES uniformCharWidth:uniformCharWidth shiftOn:shiftOn backspaceHandler:backspaceHandler target:target action:action]; } #pragma mark - Private - (void)kb_buildButtonsInContainer:(UIView *)container items:(NSArray *)items gap:(CGFloat)gap insetLeft:(CGFloat)insetLeft insetRight:(CGFloat)insetRight alignCenter:(BOOL)alignCenter isTopLevelRow:(BOOL)isTopLevelRow uniformCharWidth:(CGFloat)uniformCharWidth shiftOn:(BOOL)shiftOn backspaceHandler:(KBBackspaceLongPressHandler *)backspaceHandler target:(id)target action:(SEL)action { if (items.count == 0) { return; } UIView *leftSpacer = nil; UIView *rightSpacer = nil; if (alignCenter) { leftSpacer = [UIView new]; rightSpacer = [UIView new]; [container addSubview:leftSpacer]; [container addSubview:rightSpacer]; [leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(container.mas_left).offset(insetLeft); make.top.bottom.equalTo(container); }]; [rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(container.mas_right).offset(-insetRight); make.top.bottom.equalTo(container); }]; [leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(rightSpacer); }]; } BOOL usingUniformWidth = (uniformCharWidth > 0.0); BOOL allCharacterKeys = YES; // 跟踪该容器内是否全部为字符键 KBKeyButton *previous = nil; KBKeyButton *firstCharBtn = nil; // 用于非统一模式下的行内等宽约束 for (KBKeyboardRowItem *item in items) { KBKeyButton *btn = [self kb_buttonForItem:item shiftOn:shiftOn backspaceHandler:backspaceHandler target:target action:action]; if (!btn) { continue; } [container addSubview:btn]; [btn mas_makeConstraints:^(MASConstraintMaker *make) { make.top.bottom.equalTo(container); if (previous) { make.left.equalTo(previous.mas_right).offset(gap); } else { if (leftSpacer) { make.left.equalTo(leftSpacer.mas_right).offset(gap); } else { make.left.equalTo(container.mas_left).offset(insetLeft); } } }]; // 字符键(letter/digit/sym)使用等宽约束,自动分配可用空间; // 功能键(shift/backspace/mode 等)使用固定宽度。 BOOL isCharacterKey = [item.itemId hasPrefix:@"letter:"] || [item.itemId hasPrefix:@"digit:"] || [item.itemId hasPrefix:@"sym:"]; if (!isCharacterKey) { allCharacterKeys = NO; } if (isCharacterKey && usingUniformWidth) { // 使用跨行统一的固定宽度 CGFloat w = uniformCharWidth; [btn mas_makeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(w); }]; } else if (isCharacterKey) { // 行内等宽:所有字符键与第一个字符键等宽 if (firstCharBtn) { [btn mas_makeConstraints:^(MASConstraintMaker *make) { make.width.equalTo(firstCharBtn); }]; } else { firstCharBtn = btn; } } else { CGFloat width = [self.layoutEngine widthForItem:item key:btn.key]; if (width > 0.0) { [btn mas_makeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(width); }]; } } previous = btn; } if (!previous) { return; } // 当使用统一宽度且顶层行全部为字符键时,跳过右锚约束以实现左对齐(列对齐) BOOL skipRightAnchor = isTopLevelRow && usingUniformWidth && allCharacterKeys; if (!skipRightAnchor) { [previous mas_makeConstraints:^(MASConstraintMaker *make) { if (rightSpacer) { make.right.equalTo(rightSpacer.mas_left).offset(-gap); } else { make.right.equalTo(container.mas_right).offset(-insetRight); } }]; } } - (KBKeyButton *)kb_buttonForItem:(KBKeyboardRowItem *)item shiftOn:(BOOL)shiftOn backspaceHandler:(KBBackspaceLongPressHandler *)backspaceHandler target:(id)target action:(SEL)action { if (item.itemId.length == 0) { return nil; } KBKeyboardKeyDef *def = [self.layoutConfig keyDefForIdentifier:item.itemId]; KBKey *key = [self.keyFactory keyForItemId:item.itemId shiftOn:shiftOn]; if (!key) { return nil; } KBKeyButton *btn = [[KBKeyButton alloc] init]; btn.key = key; [btn setTitle:key.title forState:UIControlStateNormal]; UIColor *bgColor = [self kb_backgroundColorForItem:item keyDef:def]; if (bgColor) { btn.customBackgroundColor = bgColor; } CGFloat fontSize = [self.layoutEngine fontSizeForItem:item key:key]; if (fontSize > 0.0) { btn.titleLabel.font = [UIFont systemFontOfSize:fontSize weight:UIFontWeightSemibold]; } [btn applyThemeForCurrentKey]; if (target && action) { [btn addTarget:target action:action forControlEvents:UIControlEventTouchDown]; } if (key.type == KBKeyTypeBackspace) { [backspaceHandler bindDeleteButton:btn showClearLabel:YES]; } if (key.type == KBKeyTypeShift) { btn.selected = shiftOn; } [self kb_applySymbolIfNeededForButton:btn keyDef:def fontSize:fontSize]; return btn; } - (UIColor *)kb_backgroundColorForItem:(KBKeyboardRowItem *)item keyDef:(KBKeyboardKeyDef *)def { NSString *hex = def.backgroundColor; if (hex.length == 0) { hex = self.layoutConfig.defaultKeyBackground; } if (hex.length == 0) { return nil; } return [KBSkinManager colorFromHexString:hex defaultColor:nil]; } - (void)kb_applySymbolIfNeededForButton:(KBKeyButton *)button keyDef:(KBKeyboardKeyDef *)def fontSize:(CGFloat)fontSize { if (!button || !def) { return; } if (button.iconView.image != nil) { return; } NSString *symbolName = button.isSelected ? def.selectedSymbolName : def.symbolName; if (symbolName.length == 0) { return; } UIImage *image = [UIImage systemImageNamed:symbolName]; if (!image) { return; } UIImageSymbolConfiguration *config = [UIImageSymbolConfiguration configurationWithPointSize:fontSize weight:UIFontWeightSemibold]; image = [image imageWithConfiguration:config]; button.iconView.image = image; button.iconView.hidden = NO; button.iconView.contentMode = UIViewContentModeCenter; button.titleLabel.hidden = YES; UIColor *textColor = [KBSkinManager shared].current.keyTextColor ?: [UIColor blackColor]; button.iconView.tintColor = button.isSelected ? [UIColor blackColor] : textColor; } @end