diff --git a/.claude/settings.local.json b/.claude/settings.local.json index aa91048..3e14236 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,8 @@ "Bash(find:*)", "Bash(ls:*)", "Bash(wc:*)", - "Bash(chmod +x:*)" + "Bash(chmod +x:*)", + "Bash(python3:*)" ] } } diff --git a/CustomKeyboard/Resource/kb_keyboard_layout_config.json b/CustomKeyboard/Resource/kb_keyboard_layout_config.json index a11f43a..d1ca7bb 100644 --- a/CustomKeyboard/Resource/kb_keyboard_layout_config.json +++ b/CustomKeyboard/Resource/kb_keyboard_layout_config.json @@ -536,7 +536,7 @@ }, { "align": "left", - "insetLeft": 4, + "insetLeft": 15, "insetRight": 4, "gap": 5, "items": [ @@ -546,7 +546,7 @@ }, { "align": "left", - "insetLeft": 4, + "insetLeft": 27, "insetRight": 4, "gap": 5, "items": [ @@ -561,7 +561,7 @@ "gap": 5, "items": [ "letter:ㄈ", "letter:ㄌ", "letter:ㄏ", "letter:ㄒ", "letter:ㄖ", - "letter:ㄙ", "letter:ㄩ", "letter:ㄝ", "letter:ㄡ", "letter:ㄥ" + "letter:ㄙ", "letter:ㄩ", "letter:ㄝ", "letter:ㄡ", "letter:ㄥ", "backspace" ] }, { @@ -590,7 +590,7 @@ }, { "align": "left", - "insetLeft": 4, + "insetLeft": 15, "insetRight": 4, "gap": 5, "items": [ @@ -600,7 +600,7 @@ }, { "align": "left", - "insetLeft": 4, + "insetLeft": 27, "insetRight": 4, "gap": 5, "items": [ @@ -615,7 +615,7 @@ "gap": 5, "items": [ "letter:ㄈ", "letter:ㄌ", "letter:ㄏ", "letter:ㄒ", "letter:ㄖ", - "letter:ㄙ", "letter:ㄩ", "letter:ㄝ", "letter:ㄡ", "letter:ㄥ" + "letter:ㄙ", "letter:ㄩ", "letter:ㄝ", "letter:ㄡ", "letter:ㄥ", "backspace" ] }, { diff --git a/CustomKeyboard/Resource/kb_keyboard_layouts_i18n.json b/CustomKeyboard/Resource/kb_keyboard_layouts_i18n.json index ed4dec4..cc8464e 100644 --- a/CustomKeyboard/Resource/kb_keyboard_layouts_i18n.json +++ b/CustomKeyboard/Resource/kb_keyboard_layouts_i18n.json @@ -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": { "__comment": "繁体注音全键盘 - 数字面板(123 页)", "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": { "__comment": "繁体注音标准 - 数字面板(123 页)", "rows": [ diff --git a/CustomKeyboard/View/KBKeyboardView.m b/CustomKeyboard/View/KBKeyboardView.m index e34c23f..e6ae639 100644 --- a/CustomKeyboard/View/KBKeyboardView.m +++ b/CustomKeyboard/View/KBKeyboardView.m @@ -28,14 +28,13 @@ static const CGFloat kKBSpaceWidthMultiplier = 3.0; static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; @interface KBKeyboardView () -@property (nonatomic, strong) UIView *row1; -@property (nonatomic, strong) UIView *row2; -@property (nonatomic, strong) UIView *row3; -@property (nonatomic, strong) UIView *row4; +@property (nonatomic, strong) NSMutableArray *rowViews; @property (nonatomic, strong) NSArray *> *keysForRows; @property (nonatomic, strong) KBBackspaceLongPressHandler *backspaceHandler; @property (nonatomic, strong) KBKeyPreviewView *previewView; @property (nonatomic, strong) KBKeyboardLayoutConfig *layoutConfig; +/// 跨行统一字符键宽度(按最多字符键的行计算),0 表示不启用 +@property (nonatomic, assign) CGFloat kb_uniformCharKeyWidth; @end @implementation KBKeyboardView @@ -76,86 +75,118 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; #pragma mark - Base Layout - (void)buildBase { - [self addSubview:self.row1]; - [self addSubview:self.row2]; - [self addSubview:self.row3]; - [self addSubview:self.row4]; + KBKeyboardLayout *layout = [self kb_currentLayout]; + NSArray *rows = layout.rows ?: @[]; + if (rows.count == 0) { + // Fallback: 至少创建 4 行容器 + rows = @[[KBKeyboardRowConfig new], [KBKeyboardRowConfig new], + [KBKeyboardRowConfig new], [KBKeyboardRowConfig new]]; + } + [self kb_rebuildRowContainersForRows:rows]; +} + +/// 根据行配置数组,动态创建/重建行容器(支持 4 行、5 行等任意行数) +- (void)kb_rebuildRowContainersForRows:(NSArray *)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]; - KBKeyboardLayout *layout = [self kb_layoutForName:@"letters"]; - NSArray *rows = layout.rows ?: @[]; - 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 bottomInset = [self kb_metricValue:config.metrics.bottomInset fallback:nil defaultValue:6.0]; - CGFloat row1Height = [self kb_rowHeightForRow:(rows.count > 0 ? rows[0] : nil)]; - CGFloat row2Height = [self kb_rowHeightForRow:(rows.count > 1 ? rows[1] : nil)]; - CGFloat row3Height = [self kb_rowHeightForRow:(rows.count > 2 ? rows[2] : nil)]; - CGFloat row4Height = [self kb_rowHeightForRow:(rows.count > 3 ? rows[3] : nil)]; + UIView *firstRow = nil; + UIView *previousRow = nil; + for (NSUInteger i = 0; i < rowCount; i++) { + UIView *rowView = [UIView new]; + [self addSubview:rowView]; + [self.rowViews addObject:rowView]; - [self.row1 mas_makeConstraints:^(MASConstraintMaker *make) { - make.top.equalTo(self.mas_top).offset(topInset); - make.left.right.equalTo(self); - make.height.mas_equalTo(row1Height); - }]; - [self.row2 mas_makeConstraints:^(MASConstraintMaker *make) { - make.top.equalTo(self.row1.mas_bottom).offset(rowSpacing); - make.left.right.equalTo(self); - make.height.mas_equalTo(row2Height); - }]; - [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); - }]; + [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.left.right.equalTo(self); + // 所有行等高,自动根据可用空间分配行高 + if (firstRow) { + make.height.equalTo(firstRow); + } + }]; + + // 最后一行锚定到底部 + if (i == rowCount - 1) { + [rowView mas_makeConstraints:^(MASConstraintMaker *make) { + 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 - (void)reloadKeys { [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]; NSLog(@"[KBKeyboardView] reloadKeys: layoutName=%@ rows=%lu shiftRows=%lu shiftOn=%d", self.currentLayoutJsonId, (unsigned long)layout.rows.count, (unsigned long)layout.shiftRows.count, self.shiftOn); - + NSArray *rows = nil; - + if (self.shiftOn && layout.shiftRows.count > 0) { rows = layout.shiftRows; } else { 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) { NSLog(@"[KBKeyboardView] reloadKeys: rows.count < 4, fallback to legacy"); + self.kb_uniformCharKeyWidth = 0.0; [self kb_buildLegacyLayout]; return; } - [self buildRow:self.row1 withRowConfig:rows[0]]; - [self buildRow:self.row2 withRowConfig:rows[1]]; - [self buildRow:self.row3 withRowConfig:rows[2]]; - [self buildRow:self.row4 withRowConfig:rows[3]]; + // 计算跨行统一字符键宽度(若各行字符键数量不同,则按最多键的行为基准) + self.kb_uniformCharKeyWidth = [self kb_calculateUniformCharKeyWidthForRows:rows]; + + 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]; NSLog(@"[KBKeyboardView] reloadKeys: totalButtons=%lu", (unsigned long)totalButtons); if (totalButtons == 0) { 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)]; } [self kb_buildLegacyLayout]; @@ -180,7 +211,7 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; - (NSUInteger)kb_totalKeyButtonCount { 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; } return total; @@ -203,17 +234,16 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; - (BOOL)kb_isHitInsideKeyRows:(UIView *)hitView { if (!hitView) { return NO; } if (hitView == self) { return YES; } - if ([hitView isDescendantOfView:self.row1]) { return YES; } - if ([hitView isDescendantOfView:self.row2]) { return YES; } - if ([hitView isDescendantOfView:self.row3]) { return YES; } - if ([hitView isDescendantOfView:self.row4]) { return YES; } + for (UIView *row in self.rowViews) { + if ([hitView isDescendantOfView:row]) { return YES; } + } return NO; } - (KBKeyButton *)kb_nearestKeyButtonForPoint:(CGPoint)point { KBKeyButton *best = nil; CGFloat bestDistance = CGFLOAT_MAX; - NSArray *rows = @[self.row1, self.row2, self.row3, self.row4]; + NSArray *rows = self.rowViews; UIView *targetRow = nil; for (UIView *row in rows) { @@ -517,19 +547,22 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; gap:gap insetLeft:0 insetRight:0 - alignCenter:NO]; + alignCenter:NO + isTopLevelRow:NO]; [self kb_buildButtonsInContainer:centerContainer items:centerItems gap:gap insetLeft:0 insetRight:0 - alignCenter:NO]; + alignCenter:NO + isTopLevelRow:NO]; [self kb_buildButtonsInContainer:rightContainer items:rightItems gap:gap insetLeft:0 insetRight:0 - alignCenter:NO]; + alignCenter:NO + isTopLevelRow:NO]; return; } @@ -539,7 +572,8 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; gap:gap insetLeft:insetLeft insetRight:insetRight - alignCenter:alignCenter]; + alignCenter:alignCenter + isTopLevelRow:YES]; } - (void)kb_buildButtonsInContainer:(UIView *)container @@ -547,7 +581,8 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; gap:(CGFloat)gap insetLeft:(CGFloat)insetLeft insetRight:(CGFloat)insetRight - alignCenter:(BOOL)alignCenter { + alignCenter:(BOOL)alignCenter + isTopLevelRow:(BOOL)isTopLevelRow { if (items.count == 0) { return; } 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 *firstCharBtn = nil; // 用于非统一模式下的行内等宽约束 for (KBKeyboardRowItem *item in items) { KBKeyButton *btn = [self kb_buttonForItem:item]; if (!btn) { continue; } @@ -589,24 +628,55 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; } }]; - CGFloat width = [self kb_widthForItem:item key:btn.key]; - if (width > 0.0) { + // 字符键(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(width); + 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]; + if (width > 0.0) { + [btn mas_makeConstraints:^(MASConstraintMaker *make) { + make.width.mas_equalTo(width); + }]; + } } previous = btn; } if (!previous) { return; } - [previous mas_makeConstraints:^(MASConstraintMaker *make) { - if (rightSpacer) { - make.right.equalTo(rightSpacer.mas_left).offset(-gap); - } else { - make.right.equalTo(container.mas_right).offset(-insetRight); - } - }]; + + // 当使用统一宽度且顶层行全部为字符键时,跳过右锚约束以实现左对齐(列对齐) + 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); + } + }]; + } } - (void)buildRow:(UIView *)row withKeys:(NSArray *)keys { @@ -875,6 +945,55 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier { // Space 不设置宽度;通过此前已建立的左右约束自动占满剩余宽度。 } +#pragma mark - Uniform Character Key Width + +/// 计算跨行统一字符键宽度:遍历每行各自的 insets/gap/非字符键宽度, +/// 取各行可用字符键宽度的最小值,确保所有行都能容纳。 +/// 当各行有效宽度相同时返回 0(无需统一)。 +- (CGFloat)kb_calculateUniformCharKeyWidthForRows:(NSArray *)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 *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 - (KBKeyboardLayoutConfig *)kb_layoutConfig { @@ -931,15 +1050,16 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier { - (void)kb_buildLegacyLayout { self.keysForRows = [self buildKeysForCurrentLayout]; 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) ? 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.row4 withKeys:self.keysForRows[3]]; + [self buildRow:self.rowViews[2] withKeys:self.keysForRows[2]]; + [self buildRow:self.rowViews[3] withKeys:self.keysForRows[3]]; } - (CGFloat)kb_scaledValue:(CGFloat)designValue { @@ -1371,9 +1491,9 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier { #pragma mark - Lazy -- (UIView *)row1 { if (!_row1) _row1 = [UIView new]; return _row1; } -- (UIView *)row2 { if (!_row2) _row2 = [UIView new]; return _row2; } -- (UIView *)row3 { if (!_row3) _row3 = [UIView new]; return _row3; } -- (UIView *)row4 { if (!_row4) _row4 = [UIView new]; return _row4; } +- (NSMutableArray *)rowViews { + if (!_rowViews) _rowViews = [NSMutableArray array]; + return _rowViews; +} @end