4
This commit is contained in:
@@ -8,7 +8,8 @@
|
|||||||
"Bash(find:*)",
|
"Bash(find:*)",
|
||||||
"Bash(ls:*)",
|
"Bash(ls:*)",
|
||||||
"Bash(wc:*)",
|
"Bash(wc:*)",
|
||||||
"Bash(chmod +x:*)"
|
"Bash(chmod +x:*)",
|
||||||
|
"Bash(python3:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -536,7 +536,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"align": "left",
|
"align": "left",
|
||||||
"insetLeft": 4,
|
"insetLeft": 15,
|
||||||
"insetRight": 4,
|
"insetRight": 4,
|
||||||
"gap": 5,
|
"gap": 5,
|
||||||
"items": [
|
"items": [
|
||||||
@@ -546,7 +546,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"align": "left",
|
"align": "left",
|
||||||
"insetLeft": 4,
|
"insetLeft": 27,
|
||||||
"insetRight": 4,
|
"insetRight": 4,
|
||||||
"gap": 5,
|
"gap": 5,
|
||||||
"items": [
|
"items": [
|
||||||
@@ -561,7 +561,7 @@
|
|||||||
"gap": 5,
|
"gap": 5,
|
||||||
"items": [
|
"items": [
|
||||||
"letter:ㄈ", "letter:ㄌ", "letter:ㄏ", "letter:ㄒ", "letter:ㄖ",
|
"letter:ㄈ", "letter:ㄌ", "letter:ㄏ", "letter:ㄒ", "letter:ㄖ",
|
||||||
"letter:ㄙ", "letter:ㄩ", "letter:ㄝ", "letter:ㄡ", "letter:ㄥ"
|
"letter:ㄙ", "letter:ㄩ", "letter:ㄝ", "letter:ㄡ", "letter:ㄥ", "backspace"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -590,7 +590,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"align": "left",
|
"align": "left",
|
||||||
"insetLeft": 4,
|
"insetLeft": 15,
|
||||||
"insetRight": 4,
|
"insetRight": 4,
|
||||||
"gap": 5,
|
"gap": 5,
|
||||||
"items": [
|
"items": [
|
||||||
@@ -600,7 +600,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"align": "left",
|
"align": "left",
|
||||||
"insetLeft": 4,
|
"insetLeft": 27,
|
||||||
"insetRight": 4,
|
"insetRight": 4,
|
||||||
"gap": 5,
|
"gap": 5,
|
||||||
"items": [
|
"items": [
|
||||||
@@ -615,7 +615,7 @@
|
|||||||
"gap": 5,
|
"gap": 5,
|
||||||
"items": [
|
"items": [
|
||||||
"letter:ㄈ", "letter:ㄌ", "letter:ㄏ", "letter:ㄒ", "letter:ㄖ",
|
"letter:ㄈ", "letter:ㄌ", "letter:ㄏ", "letter:ㄒ", "letter:ㄖ",
|
||||||
"letter:ㄙ", "letter:ㄩ", "letter:ㄝ", "letter:ㄡ", "letter:ㄥ"
|
"letter:ㄙ", "letter:ㄩ", "letter:ㄝ", "letter:ㄡ", "letter:ㄥ", "backspace"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -687,48 +687,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
"letters_bopomofo_full": {
|
|
||||||
"__comment": "繁体注音全键盘布局",
|
|
||||||
"__comment_layout": "第一行:ㄅㄉˇˋㄓˊ˙ㄚㄞㄢㄦ | 第二行:ㄆㄊㄍㄐㄔㄗㄧㄛㄟㄣ | 第三行:ㄇㄋㄎㄑㄕㄘㄨㄜㄠㄤ | 第四行:ㄈㄌㄏㄒㄖㄙㄩㄝㄡㄥ",
|
|
||||||
"rows": [
|
|
||||||
{
|
|
||||||
"align": "left",
|
|
||||||
"insetLeft": 4,
|
|
||||||
"insetRight": 4,
|
|
||||||
"gap": 5,
|
|
||||||
"items": ["letter:ㄅ", "letter:ㄉ", "letter:ˇ", "letter:ˋ", "letter:ㄓ", "letter:ˊ", "letter:˙", "letter:ㄚ", "letter:ㄞ", "letter:ㄢ", "letter:ㄦ"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"align": "left",
|
|
||||||
"insetLeft": 4,
|
|
||||||
"insetRight": 4,
|
|
||||||
"gap": 5,
|
|
||||||
"items": ["letter:ㄆ", "letter:ㄊ", "letter:ㄍ", "letter:ㄐ", "letter:ㄔ", "letter:ㄗ", "letter:ㄧ", "letter:ㄛ", "letter:ㄟ", "letter:ㄣ"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"align": "left",
|
|
||||||
"insetLeft": 4,
|
|
||||||
"insetRight": 4,
|
|
||||||
"gap": 5,
|
|
||||||
"items": ["letter:ㄇ", "letter:ㄋ", "letter:ㄎ", "letter:ㄑ", "letter:ㄕ", "letter:ㄘ", "letter:ㄨ", "letter:ㄜ", "letter:ㄠ", "letter:ㄤ"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"align": "left",
|
|
||||||
"insetLeft": 4,
|
|
||||||
"insetRight": 4,
|
|
||||||
"gap": 5,
|
|
||||||
"items": ["letter:ㄈ", "letter:ㄌ", "letter:ㄏ", "letter:ㄒ", "letter:ㄖ", "letter:ㄙ", "letter:ㄩ", "letter:ㄝ", "letter:ㄡ", "letter:ㄥ"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"align": "left",
|
|
||||||
"insetLeft": 4,
|
|
||||||
"insetRight": 4,
|
|
||||||
"gap": 5,
|
|
||||||
"items": ["mode_123", "emoji", "space", "send"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"letters_bopomofo_full_numbers": {
|
"letters_bopomofo_full_numbers": {
|
||||||
"__comment": "繁体注音全键盘 - 数字面板(123 页)",
|
"__comment": "繁体注音全键盘 - 数字面板(123 页)",
|
||||||
"rows": [
|
"rows": [
|
||||||
@@ -817,47 +775,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
"letters_bopomofo_standard": {
|
|
||||||
"__comment": "繁体注音标准布局(与全键盘相同)",
|
|
||||||
"rows": [
|
|
||||||
{
|
|
||||||
"align": "left",
|
|
||||||
"insetLeft": 4,
|
|
||||||
"insetRight": 4,
|
|
||||||
"gap": 5,
|
|
||||||
"items": ["letter:ㄅ", "letter:ㄉ", "letter:ˇ", "letter:ˋ", "letter:ㄓ", "letter:ˊ", "letter:˙", "letter:ㄚ", "letter:ㄞ", "letter:ㄢ", "letter:ㄦ"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"align": "left",
|
|
||||||
"insetLeft": 4,
|
|
||||||
"insetRight": 4,
|
|
||||||
"gap": 5,
|
|
||||||
"items": ["letter:ㄆ", "letter:ㄊ", "letter:ㄍ", "letter:ㄐ", "letter:ㄔ", "letter:ㄗ", "letter:ㄧ", "letter:ㄛ", "letter:ㄟ", "letter:ㄣ"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"align": "left",
|
|
||||||
"insetLeft": 4,
|
|
||||||
"insetRight": 4,
|
|
||||||
"gap": 5,
|
|
||||||
"items": ["letter:ㄇ", "letter:ㄋ", "letter:ㄎ", "letter:ㄑ", "letter:ㄕ", "letter:ㄘ", "letter:ㄨ", "letter:ㄜ", "letter:ㄠ", "letter:ㄤ"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"align": "left",
|
|
||||||
"insetLeft": 4,
|
|
||||||
"insetRight": 4,
|
|
||||||
"gap": 5,
|
|
||||||
"items": ["letter:ㄈ", "letter:ㄌ", "letter:ㄏ", "letter:ㄒ", "letter:ㄖ", "letter:ㄙ", "letter:ㄩ", "letter:ㄝ", "letter:ㄡ", "letter:ㄥ"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"align": "left",
|
|
||||||
"insetLeft": 4,
|
|
||||||
"insetRight": 4,
|
|
||||||
"gap": 5,
|
|
||||||
"items": ["mode_123", "emoji", "space", "send"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"letters_bopomofo_standard_numbers": {
|
"letters_bopomofo_standard_numbers": {
|
||||||
"__comment": "繁体注音标准 - 数字面板(123 页)",
|
"__comment": "繁体注音标准 - 数字面板(123 页)",
|
||||||
"rows": [
|
"rows": [
|
||||||
|
|||||||
@@ -28,14 +28,13 @@ static const CGFloat kKBSpaceWidthMultiplier = 3.0;
|
|||||||
static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
||||||
|
|
||||||
@interface KBKeyboardView ()
|
@interface KBKeyboardView ()
|
||||||
@property (nonatomic, strong) UIView *row1;
|
@property (nonatomic, strong) NSMutableArray<UIView *> *rowViews;
|
||||||
@property (nonatomic, strong) UIView *row2;
|
|
||||||
@property (nonatomic, strong) UIView *row3;
|
|
||||||
@property (nonatomic, strong) UIView *row4;
|
|
||||||
@property (nonatomic, strong) NSArray<NSArray<KBKey *> *> *keysForRows;
|
@property (nonatomic, strong) NSArray<NSArray<KBKey *> *> *keysForRows;
|
||||||
@property (nonatomic, strong) KBBackspaceLongPressHandler *backspaceHandler;
|
@property (nonatomic, strong) KBBackspaceLongPressHandler *backspaceHandler;
|
||||||
@property (nonatomic, strong) KBKeyPreviewView *previewView;
|
@property (nonatomic, strong) KBKeyPreviewView *previewView;
|
||||||
@property (nonatomic, strong) KBKeyboardLayoutConfig *layoutConfig;
|
@property (nonatomic, strong) KBKeyboardLayoutConfig *layoutConfig;
|
||||||
|
/// 跨行统一字符键宽度(按最多字符键的行计算),0 表示不启用
|
||||||
|
@property (nonatomic, assign) CGFloat kb_uniformCharKeyWidth;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation KBKeyboardView
|
@implementation KBKeyboardView
|
||||||
@@ -76,55 +75,73 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
#pragma mark - Base Layout
|
#pragma mark - Base Layout
|
||||||
|
|
||||||
- (void)buildBase {
|
- (void)buildBase {
|
||||||
[self addSubview:self.row1];
|
KBKeyboardLayout *layout = [self kb_currentLayout];
|
||||||
[self addSubview:self.row2];
|
NSArray<KBKeyboardRowConfig *> *rows = layout.rows ?: @[];
|
||||||
[self addSubview:self.row3];
|
if (rows.count == 0) {
|
||||||
[self addSubview:self.row4];
|
// Fallback: 至少创建 4 行容器
|
||||||
|
rows = @[[KBKeyboardRowConfig new], [KBKeyboardRowConfig new],
|
||||||
|
[KBKeyboardRowConfig new], [KBKeyboardRowConfig new]];
|
||||||
|
}
|
||||||
|
[self kb_rebuildRowContainersForRows:rows];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据行配置数组,动态创建/重建行容器(支持 4 行、5 行等任意行数)
|
||||||
|
- (void)kb_rebuildRowContainersForRows:(NSArray<KBKeyboardRowConfig *> *)rowConfigs {
|
||||||
|
// 移除旧的行容器
|
||||||
|
for (UIView *row in self.rowViews) {
|
||||||
|
[row removeFromSuperview];
|
||||||
|
}
|
||||||
|
[self.rowViews removeAllObjects];
|
||||||
|
|
||||||
|
NSUInteger rowCount = rowConfigs.count;
|
||||||
|
if (rowCount == 0) return;
|
||||||
|
|
||||||
KBKeyboardLayoutConfig *config = [self kb_layoutConfig];
|
KBKeyboardLayoutConfig *config = [self kb_layoutConfig];
|
||||||
KBKeyboardLayout *layout = [self kb_layoutForName:@"letters"];
|
|
||||||
NSArray<KBKeyboardRowConfig *> *rows = layout.rows ?: @[];
|
|
||||||
|
|
||||||
CGFloat rowSpacing = [self kb_metricValue:config.metrics.rowSpacing fallback:nil defaultValue:8.0];
|
CGFloat rowSpacing = [self kb_metricValue:config.metrics.rowSpacing fallback:nil defaultValue:8.0];
|
||||||
CGFloat topInset = [self kb_metricValue:config.metrics.topInset fallback:nil defaultValue:8.0];
|
CGFloat topInset = [self kb_metricValue:config.metrics.topInset fallback:nil defaultValue:8.0];
|
||||||
CGFloat bottomInset = [self kb_metricValue:config.metrics.bottomInset fallback:nil defaultValue:6.0];
|
CGFloat bottomInset = [self kb_metricValue:config.metrics.bottomInset fallback:nil defaultValue:6.0];
|
||||||
|
|
||||||
CGFloat row1Height = [self kb_rowHeightForRow:(rows.count > 0 ? rows[0] : nil)];
|
UIView *firstRow = nil;
|
||||||
CGFloat row2Height = [self kb_rowHeightForRow:(rows.count > 1 ? rows[1] : nil)];
|
UIView *previousRow = nil;
|
||||||
CGFloat row3Height = [self kb_rowHeightForRow:(rows.count > 2 ? rows[2] : nil)];
|
for (NSUInteger i = 0; i < rowCount; i++) {
|
||||||
CGFloat row4Height = [self kb_rowHeightForRow:(rows.count > 3 ? rows[3] : nil)];
|
UIView *rowView = [UIView new];
|
||||||
|
[self addSubview:rowView];
|
||||||
|
[self.rowViews addObject:rowView];
|
||||||
|
|
||||||
[self.row1 mas_makeConstraints:^(MASConstraintMaker *make) {
|
[rowView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
if (previousRow) {
|
||||||
|
make.top.equalTo(previousRow.mas_bottom).offset(rowSpacing);
|
||||||
|
} else {
|
||||||
make.top.equalTo(self.mas_top).offset(topInset);
|
make.top.equalTo(self.mas_top).offset(topInset);
|
||||||
|
}
|
||||||
make.left.right.equalTo(self);
|
make.left.right.equalTo(self);
|
||||||
make.height.mas_equalTo(row1Height);
|
// 所有行等高,自动根据可用空间分配行高
|
||||||
|
if (firstRow) {
|
||||||
|
make.height.equalTo(firstRow);
|
||||||
|
}
|
||||||
}];
|
}];
|
||||||
[self.row2 mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
||||||
make.top.equalTo(self.row1.mas_bottom).offset(rowSpacing);
|
// 最后一行锚定到底部
|
||||||
make.left.right.equalTo(self);
|
if (i == rowCount - 1) {
|
||||||
make.height.mas_equalTo(row2Height);
|
[rowView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
}];
|
|
||||||
[self.row3 mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
||||||
make.top.equalTo(self.row2.mas_bottom).offset(rowSpacing);
|
|
||||||
make.left.right.equalTo(self);
|
|
||||||
make.height.mas_equalTo(row3Height);
|
|
||||||
}];
|
|
||||||
[self.row4 mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
||||||
make.top.equalTo(self.row3.mas_bottom).offset(rowSpacing);
|
|
||||||
make.left.right.equalTo(self);
|
|
||||||
make.height.mas_equalTo(row4Height);
|
|
||||||
make.bottom.equalTo(self.mas_bottom).offset(-bottomInset);
|
make.bottom.equalTo(self.mas_bottom).offset(-bottomInset);
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!firstRow) firstRow = rowView;
|
||||||
|
previousRow = rowView;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保预览气泡视图在最上层
|
||||||
|
if (self.previewView && self.previewView.superview == self) {
|
||||||
|
[self bringSubviewToFront:self.previewView];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Public
|
#pragma mark - Public
|
||||||
|
|
||||||
- (void)reloadKeys {
|
- (void)reloadKeys {
|
||||||
[self.backspaceHandler bindDeleteButton:nil showClearLabel:NO];
|
[self.backspaceHandler bindDeleteButton:nil showClearLabel:NO];
|
||||||
// 移除旧按钮
|
|
||||||
for (UIView *row in @[self.row1, self.row2, self.row3, self.row4]) {
|
|
||||||
[row.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
|
||||||
}
|
|
||||||
|
|
||||||
KBKeyboardLayout *layout = [self kb_currentLayout];
|
KBKeyboardLayout *layout = [self kb_currentLayout];
|
||||||
NSLog(@"[KBKeyboardView] reloadKeys: layoutName=%@ rows=%lu shiftRows=%lu shiftOn=%d",
|
NSLog(@"[KBKeyboardView] reloadKeys: layoutName=%@ rows=%lu shiftRows=%lu shiftOn=%d",
|
||||||
@@ -138,24 +155,38 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
rows = layout.rows ?: @[];
|
rows = layout.rows ?: @[];
|
||||||
}
|
}
|
||||||
|
|
||||||
NSLog(@"[KBKeyboardView] reloadKeys: usingRows=%lu", (unsigned long)rows.count);
|
NSLog(@"[KBKeyboardView] reloadKeys: usingRows=%lu currentContainers=%lu",
|
||||||
|
(unsigned long)rows.count, (unsigned long)self.rowViews.count);
|
||||||
|
|
||||||
|
// 行数变化时(如从 4 行布局切到 5 行注音布局),重建行容器
|
||||||
|
if (rows.count >= 4 && rows.count != self.rowViews.count) {
|
||||||
|
[self kb_rebuildRowContainersForRows:rows];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除旧按钮
|
||||||
|
for (UIView *row in self.rowViews) {
|
||||||
|
[row.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||||
|
}
|
||||||
|
|
||||||
if (rows.count < 4) {
|
if (rows.count < 4) {
|
||||||
NSLog(@"[KBKeyboardView] reloadKeys: rows.count < 4, fallback to legacy");
|
NSLog(@"[KBKeyboardView] reloadKeys: rows.count < 4, fallback to legacy");
|
||||||
|
self.kb_uniformCharKeyWidth = 0.0;
|
||||||
[self kb_buildLegacyLayout];
|
[self kb_buildLegacyLayout];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
[self buildRow:self.row1 withRowConfig:rows[0]];
|
// 计算跨行统一字符键宽度(若各行字符键数量不同,则按最多键的行为基准)
|
||||||
[self buildRow:self.row2 withRowConfig:rows[1]];
|
self.kb_uniformCharKeyWidth = [self kb_calculateUniformCharKeyWidthForRows:rows];
|
||||||
[self buildRow:self.row3 withRowConfig:rows[2]];
|
|
||||||
[self buildRow:self.row4 withRowConfig:rows[3]];
|
for (NSUInteger i = 0; i < rows.count && i < self.rowViews.count; i++) {
|
||||||
|
[self buildRow:self.rowViews[i] withRowConfig:rows[i]];
|
||||||
|
}
|
||||||
|
|
||||||
NSUInteger totalButtons = [self kb_totalKeyButtonCount];
|
NSUInteger totalButtons = [self kb_totalKeyButtonCount];
|
||||||
NSLog(@"[KBKeyboardView] reloadKeys: totalButtons=%lu", (unsigned long)totalButtons);
|
NSLog(@"[KBKeyboardView] reloadKeys: totalButtons=%lu", (unsigned long)totalButtons);
|
||||||
if (totalButtons == 0) {
|
if (totalButtons == 0) {
|
||||||
NSLog(@"[KBKeyboardView] config layout produced no keys, fallback to legacy.");
|
NSLog(@"[KBKeyboardView] config layout produced no keys, fallback to legacy.");
|
||||||
for (UIView *row in @[self.row1, self.row2, self.row3, self.row4]) {
|
for (UIView *row in self.rowViews) {
|
||||||
[row.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
[row.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
|
||||||
}
|
}
|
||||||
[self kb_buildLegacyLayout];
|
[self kb_buildLegacyLayout];
|
||||||
@@ -180,7 +211,7 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
|
|
||||||
- (NSUInteger)kb_totalKeyButtonCount {
|
- (NSUInteger)kb_totalKeyButtonCount {
|
||||||
NSUInteger total = 0;
|
NSUInteger total = 0;
|
||||||
for (UIView *row in @[self.row1, self.row2, self.row3, self.row4]) {
|
for (UIView *row in self.rowViews) {
|
||||||
total += [self kb_collectKeyButtonsInView:row].count;
|
total += [self kb_collectKeyButtonsInView:row].count;
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
@@ -203,17 +234,16 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
- (BOOL)kb_isHitInsideKeyRows:(UIView *)hitView {
|
- (BOOL)kb_isHitInsideKeyRows:(UIView *)hitView {
|
||||||
if (!hitView) { return NO; }
|
if (!hitView) { return NO; }
|
||||||
if (hitView == self) { return YES; }
|
if (hitView == self) { return YES; }
|
||||||
if ([hitView isDescendantOfView:self.row1]) { return YES; }
|
for (UIView *row in self.rowViews) {
|
||||||
if ([hitView isDescendantOfView:self.row2]) { return YES; }
|
if ([hitView isDescendantOfView:row]) { return YES; }
|
||||||
if ([hitView isDescendantOfView:self.row3]) { return YES; }
|
}
|
||||||
if ([hitView isDescendantOfView:self.row4]) { return YES; }
|
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (KBKeyButton *)kb_nearestKeyButtonForPoint:(CGPoint)point {
|
- (KBKeyButton *)kb_nearestKeyButtonForPoint:(CGPoint)point {
|
||||||
KBKeyButton *best = nil;
|
KBKeyButton *best = nil;
|
||||||
CGFloat bestDistance = CGFLOAT_MAX;
|
CGFloat bestDistance = CGFLOAT_MAX;
|
||||||
NSArray<UIView *> *rows = @[self.row1, self.row2, self.row3, self.row4];
|
NSArray<UIView *> *rows = self.rowViews;
|
||||||
|
|
||||||
UIView *targetRow = nil;
|
UIView *targetRow = nil;
|
||||||
for (UIView *row in rows) {
|
for (UIView *row in rows) {
|
||||||
@@ -517,19 +547,22 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
gap:gap
|
gap:gap
|
||||||
insetLeft:0
|
insetLeft:0
|
||||||
insetRight:0
|
insetRight:0
|
||||||
alignCenter:NO];
|
alignCenter:NO
|
||||||
|
isTopLevelRow:NO];
|
||||||
[self kb_buildButtonsInContainer:centerContainer
|
[self kb_buildButtonsInContainer:centerContainer
|
||||||
items:centerItems
|
items:centerItems
|
||||||
gap:gap
|
gap:gap
|
||||||
insetLeft:0
|
insetLeft:0
|
||||||
insetRight:0
|
insetRight:0
|
||||||
alignCenter:NO];
|
alignCenter:NO
|
||||||
|
isTopLevelRow:NO];
|
||||||
[self kb_buildButtonsInContainer:rightContainer
|
[self kb_buildButtonsInContainer:rightContainer
|
||||||
items:rightItems
|
items:rightItems
|
||||||
gap:gap
|
gap:gap
|
||||||
insetLeft:0
|
insetLeft:0
|
||||||
insetRight:0
|
insetRight:0
|
||||||
alignCenter:NO];
|
alignCenter:NO
|
||||||
|
isTopLevelRow:NO];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,7 +572,8 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
gap:gap
|
gap:gap
|
||||||
insetLeft:insetLeft
|
insetLeft:insetLeft
|
||||||
insetRight:insetRight
|
insetRight:insetRight
|
||||||
alignCenter:alignCenter];
|
alignCenter:alignCenter
|
||||||
|
isTopLevelRow:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)kb_buildButtonsInContainer:(UIView *)container
|
- (void)kb_buildButtonsInContainer:(UIView *)container
|
||||||
@@ -547,7 +581,8 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
gap:(CGFloat)gap
|
gap:(CGFloat)gap
|
||||||
insetLeft:(CGFloat)insetLeft
|
insetLeft:(CGFloat)insetLeft
|
||||||
insetRight:(CGFloat)insetRight
|
insetRight:(CGFloat)insetRight
|
||||||
alignCenter:(BOOL)alignCenter {
|
alignCenter:(BOOL)alignCenter
|
||||||
|
isTopLevelRow:(BOOL)isTopLevelRow {
|
||||||
if (items.count == 0) { return; }
|
if (items.count == 0) { return; }
|
||||||
|
|
||||||
UIView *leftSpacer = nil;
|
UIView *leftSpacer = nil;
|
||||||
@@ -570,7 +605,11 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOL usingUniformWidth = (self.kb_uniformCharKeyWidth > 0.0);
|
||||||
|
BOOL allCharacterKeys = YES; // 跟踪该容器内是否全部为字符键
|
||||||
|
|
||||||
KBKeyButton *previous = nil;
|
KBKeyButton *previous = nil;
|
||||||
|
KBKeyButton *firstCharBtn = nil; // 用于非统一模式下的行内等宽约束
|
||||||
for (KBKeyboardRowItem *item in items) {
|
for (KBKeyboardRowItem *item in items) {
|
||||||
KBKeyButton *btn = [self kb_buttonForItem:item];
|
KBKeyButton *btn = [self kb_buttonForItem:item];
|
||||||
if (!btn) { continue; }
|
if (!btn) { continue; }
|
||||||
@@ -589,17 +628,47 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
// 字符键(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 = self.kb_uniformCharKeyWidth;
|
||||||
|
[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 kb_widthForItem:item key:btn.key];
|
CGFloat width = [self kb_widthForItem:item key:btn.key];
|
||||||
if (width > 0.0) {
|
if (width > 0.0) {
|
||||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
make.width.mas_equalTo(width);
|
make.width.mas_equalTo(width);
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
previous = btn;
|
previous = btn;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!previous) { return; }
|
if (!previous) { return; }
|
||||||
|
|
||||||
|
// 当使用统一宽度且顶层行全部为字符键时,跳过右锚约束以实现左对齐(列对齐)
|
||||||
|
BOOL skipRightAnchor = isTopLevelRow && usingUniformWidth && allCharacterKeys;
|
||||||
|
|
||||||
|
if (!skipRightAnchor) {
|
||||||
[previous mas_makeConstraints:^(MASConstraintMaker *make) {
|
[previous mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
if (rightSpacer) {
|
if (rightSpacer) {
|
||||||
make.right.equalTo(rightSpacer.mas_left).offset(-gap);
|
make.right.equalTo(rightSpacer.mas_left).offset(-gap);
|
||||||
@@ -608,6 +677,7 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (void)buildRow:(UIView *)row withKeys:(NSArray<KBKey *> *)keys {
|
- (void)buildRow:(UIView *)row withKeys:(NSArray<KBKey *> *)keys {
|
||||||
[self buildRow:row withKeys:keys edgeSpacerMultiplier:0.0];
|
[self buildRow:row withKeys:keys edgeSpacerMultiplier:0.0];
|
||||||
@@ -875,6 +945,55 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
|
|||||||
// Space 不设置宽度;通过此前已建立的左右约束自动占满剩余宽度。
|
// Space 不设置宽度;通过此前已建立的左右约束自动占满剩余宽度。
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Uniform Character Key Width
|
||||||
|
|
||||||
|
/// 计算跨行统一字符键宽度:遍历每行各自的 insets/gap/非字符键宽度,
|
||||||
|
/// 取各行可用字符键宽度的最小值,确保所有行都能容纳。
|
||||||
|
/// 当各行有效宽度相同时返回 0(无需统一)。
|
||||||
|
- (CGFloat)kb_calculateUniformCharKeyWidthForRows:(NSArray<KBKeyboardRowConfig *> *)rows {
|
||||||
|
CGFloat minWidth = CGFLOAT_MAX;
|
||||||
|
CGFloat maxWidth = 0.0;
|
||||||
|
BOOL hasCharRow = NO;
|
||||||
|
CGFloat containerWidth = KBScreenWidth();
|
||||||
|
|
||||||
|
for (KBKeyboardRowConfig *row in rows) {
|
||||||
|
if (row.segments) { continue; } // 跳过分段行
|
||||||
|
NSArray<KBKeyboardRowItem *> *items = [row resolvedItems];
|
||||||
|
NSUInteger charCount = 0;
|
||||||
|
CGFloat nonCharWidth = 0.0;
|
||||||
|
for (KBKeyboardRowItem *item in items) {
|
||||||
|
BOOL isChar = [item.itemId hasPrefix:@"letter:"] ||
|
||||||
|
[item.itemId hasPrefix:@"digit:"] ||
|
||||||
|
[item.itemId hasPrefix:@"sym:"];
|
||||||
|
if (isChar) {
|
||||||
|
charCount++;
|
||||||
|
} else {
|
||||||
|
KBKey *key = [self kb_keyForItemId:item.itemId];
|
||||||
|
CGFloat w = [self kb_widthForItem:item key:key];
|
||||||
|
nonCharWidth += w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (charCount == 0) { continue; } // 跳过无字符键的行(如底部控制行)
|
||||||
|
hasCharRow = YES;
|
||||||
|
|
||||||
|
// 使用每行各自配置的 insets 和 gap
|
||||||
|
CGFloat gap = [self kb_gapForRow:row];
|
||||||
|
CGFloat insetLeft = [self kb_insetLeftForRow:row];
|
||||||
|
CGFloat insetRight = [self kb_insetRightForRow:row];
|
||||||
|
CGFloat totalGaps = (items.count > 1) ? (items.count - 1) * gap : 0.0;
|
||||||
|
CGFloat available = containerWidth - insetLeft - insetRight - totalGaps - nonCharWidth;
|
||||||
|
CGFloat width = available / charCount;
|
||||||
|
|
||||||
|
if (width < minWidth) { minWidth = width; }
|
||||||
|
if (width > maxWidth) { maxWidth = width; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasCharRow || minWidth <= 0.0 || minWidth >= CGFLOAT_MAX) { return 0.0; }
|
||||||
|
// 各行有效宽度相同时无需统一
|
||||||
|
if (fabs(maxWidth - minWidth) < 0.5) { return 0.0; }
|
||||||
|
return minWidth;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - Config Helpers
|
#pragma mark - Config Helpers
|
||||||
|
|
||||||
- (KBKeyboardLayoutConfig *)kb_layoutConfig {
|
- (KBKeyboardLayoutConfig *)kb_layoutConfig {
|
||||||
@@ -931,15 +1050,16 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
|
|||||||
- (void)kb_buildLegacyLayout {
|
- (void)kb_buildLegacyLayout {
|
||||||
self.keysForRows = [self buildKeysForCurrentLayout];
|
self.keysForRows = [self buildKeysForCurrentLayout];
|
||||||
if (self.keysForRows.count < 4) { return; }
|
if (self.keysForRows.count < 4) { return; }
|
||||||
|
if (self.rowViews.count < 4) { return; }
|
||||||
|
|
||||||
[self buildRow:self.row1 withKeys:self.keysForRows[0]];
|
[self buildRow:self.rowViews[0] withKeys:self.keysForRows[0]];
|
||||||
|
|
||||||
CGFloat row2Spacer = (self.layoutStyle == KBKeyboardLayoutStyleLetters)
|
CGFloat row2Spacer = (self.layoutStyle == KBKeyboardLayoutStyleLetters)
|
||||||
? kKBLettersRow2EdgeSpacerMultiplier : 0.0;
|
? kKBLettersRow2EdgeSpacerMultiplier : 0.0;
|
||||||
[self buildRow:self.row2 withKeys:self.keysForRows[1] edgeSpacerMultiplier:row2Spacer];
|
[self buildRow:self.rowViews[1] withKeys:self.keysForRows[1] edgeSpacerMultiplier:row2Spacer];
|
||||||
|
|
||||||
[self buildRow:self.row3 withKeys:self.keysForRows[2]];
|
[self buildRow:self.rowViews[2] withKeys:self.keysForRows[2]];
|
||||||
[self buildRow:self.row4 withKeys:self.keysForRows[3]];
|
[self buildRow:self.rowViews[3] withKeys:self.keysForRows[3]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGFloat)kb_scaledValue:(CGFloat)designValue {
|
- (CGFloat)kb_scaledValue:(CGFloat)designValue {
|
||||||
@@ -1371,9 +1491,9 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
|
|||||||
|
|
||||||
#pragma mark - Lazy
|
#pragma mark - Lazy
|
||||||
|
|
||||||
- (UIView *)row1 { if (!_row1) _row1 = [UIView new]; return _row1; }
|
- (NSMutableArray<UIView *> *)rowViews {
|
||||||
- (UIView *)row2 { if (!_row2) _row2 = [UIView new]; return _row2; }
|
if (!_rowViews) _rowViews = [NSMutableArray array];
|
||||||
- (UIView *)row3 { if (!_row3) _row3 = [UIView new]; return _row3; }
|
return _rowViews;
|
||||||
- (UIView *)row4 { if (!_row4) _row4 = [UIView new]; return _row4; }
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
Reference in New Issue
Block a user