固定键盘高度250
优化kbkeyboardview 优化ui
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
- (void)kb_consumePendingShopSkin;
|
||||
@end
|
||||
|
||||
static CGFloat KEYBOARDHEIGHT = 256 + 20;
|
||||
static CGFloat KEYBOARDHEIGHT = 200 + 50;
|
||||
|
||||
static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
void *observer,
|
||||
@@ -84,10 +84,17 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
|
||||
|
||||
- (void)setupUI {
|
||||
// 固定键盘整体高度;为减少与系统自调整高度产生的告警,将优先级设置为 High
|
||||
NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:KEYBOARDHEIGHT];
|
||||
h.priority = UILayoutPriorityDefaultHigh; // 750
|
||||
h.active = YES;
|
||||
self.view.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
|
||||
CGFloat keyboardHeight = KEYBOARDHEIGHT; // 250
|
||||
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
|
||||
|
||||
NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:keyboardHeight];
|
||||
NSLayoutConstraint *w = [self.view.widthAnchor constraintEqualToConstant:screenWidth];
|
||||
|
||||
h.priority = UILayoutPriorityRequired;
|
||||
w.priority = UILayoutPriorityRequired;
|
||||
[NSLayoutConstraint activateConstraints:@[h, w]];
|
||||
// 关闭 UIInputView 自适应(某些系统版本会尝试放大为全屏高度导致冲突)
|
||||
if ([self.view isKindOfClass:[UIInputView class]]) {
|
||||
UIInputView *iv = (UIInputView *)self.view;
|
||||
@@ -200,7 +207,9 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center,
|
||||
|
||||
- (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapToolActionAtIndex:(NSInteger)index {
|
||||
if (index == 0) {
|
||||
[self showFunctionPanel:YES];
|
||||
/// 充值操作
|
||||
[KBHUD showInfo:KBLocalized(@"Recharge Now")];
|
||||
// [self showFunctionPanel:YES];
|
||||
} else {
|
||||
[self showFunctionPanel:NO];
|
||||
}
|
||||
|
||||
@@ -23,16 +23,12 @@
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.backgroundColor = [KBSkinManager shared].current.keyboardBackground;
|
||||
|
||||
|
||||
// 顶部栏
|
||||
self.topBar = [[KBToolBar alloc] init];
|
||||
self.topBar.delegate = self;
|
||||
[self addSubview:self.topBar];
|
||||
[self.topBar mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self);
|
||||
make.top.equalTo(self.mas_top).offset(6);
|
||||
make.height.mas_equalTo(40);
|
||||
}];
|
||||
|
||||
// 键盘区域
|
||||
self.keyboardView = [[KBKeyboardView alloc] init];
|
||||
self.keyboardView.delegate = self;
|
||||
@@ -44,6 +40,14 @@
|
||||
make.bottom.equalTo(self.mas_bottom).offset(-4);
|
||||
}];
|
||||
|
||||
|
||||
[self.topBar mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.right.equalTo(self);
|
||||
make.top.equalTo(self.mas_top).offset(0);
|
||||
// make.height.mas_equalTo(40);
|
||||
make.bottom.equalTo(self.keyboardView.mas_top).offset(-6);
|
||||
}];
|
||||
|
||||
// 功能面板切换交由外部控制器处理;此处不直接创建/管理
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,24 @@
|
||||
#import "KBSkinManager.h"
|
||||
#import "KBKeyPreviewView.h"
|
||||
|
||||
// UI 常量统一管理,方便后续调试样式
|
||||
static const CGFloat kKBRowVerticalSpacing = 8.0;
|
||||
static const CGFloat kKBRowHorizontalInset = 6.0;
|
||||
static const CGFloat kKBRowHeight = 40.0;
|
||||
|
||||
static const NSTimeInterval kKBBackspaceLongPressMinDuration = 0.35;
|
||||
static const NSTimeInterval kKBBackspaceRepeatInterval = 0.06;
|
||||
|
||||
static const NSTimeInterval kKBPreviewShowDuration = 0.08;
|
||||
static const NSTimeInterval kKBPreviewHideDuration = 0.06;
|
||||
|
||||
static const CGFloat kKBSpecialKeySquareMultiplier = 1.2;
|
||||
static const CGFloat kKBReturnWidthMultiplier = 2.4;
|
||||
static const CGFloat kKBSpaceWidthMultiplier = 3.0;
|
||||
|
||||
// 第二行字母行的左右占位比例(用于居中)
|
||||
static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
||||
|
||||
@interface KBKeyboardView ()
|
||||
@property (nonatomic, strong) UIView *row1;
|
||||
@property (nonatomic, strong) UIView *row2;
|
||||
@@ -44,36 +62,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Base Layout
|
||||
|
||||
- (void)buildBase {
|
||||
[self addSubview:self.row1];
|
||||
[self addSubview:self.row2];
|
||||
[self addSubview:self.row3];
|
||||
[self addSubview:self.row4];
|
||||
|
||||
CGFloat vSpacing = 8;
|
||||
[self.row1 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.mas_top).offset(8);
|
||||
make.top.equalTo(self.mas_top).offset(kKBRowVerticalSpacing);
|
||||
make.left.right.equalTo(self);
|
||||
make.height.mas_equalTo(40);
|
||||
make.height.mas_equalTo(kKBRowHeight);
|
||||
}];
|
||||
[self.row2 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.row1.mas_bottom).offset(vSpacing);
|
||||
make.top.equalTo(self.row1.mas_bottom).offset(kKBRowVerticalSpacing);
|
||||
make.left.right.equalTo(self);
|
||||
make.height.equalTo(self.row1);
|
||||
}];
|
||||
[self.row3 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.row2.mas_bottom).offset(vSpacing);
|
||||
make.top.equalTo(self.row2.mas_bottom).offset(kKBRowVerticalSpacing);
|
||||
make.left.right.equalTo(self);
|
||||
make.height.equalTo(self.row1);
|
||||
}];
|
||||
[self.row4 mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.equalTo(self.row3.mas_bottom).offset(vSpacing);
|
||||
make.top.equalTo(self.row3.mas_bottom).offset(kKBRowVerticalSpacing);
|
||||
make.left.right.equalTo(self);
|
||||
make.height.equalTo(self.row1);
|
||||
make.bottom.equalTo(self.mas_bottom).offset(-6);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)reloadKeys {
|
||||
// 移除旧按钮
|
||||
for (UIView *row in @[self.row1, self.row2, self.row3, self.row4]) {
|
||||
@@ -81,16 +102,78 @@
|
||||
}
|
||||
|
||||
self.keysForRows = [self buildKeysForCurrentLayout];
|
||||
if (self.keysForRows.count < 4) return;
|
||||
|
||||
[self buildRow:self.row1 withKeys:self.keysForRows[0]];
|
||||
|
||||
// 第二行:字母布局时通过左右等宽占位让整行居中
|
||||
CGFloat row2Spacer = (self.layoutStyle == KBKeyboardLayoutStyleLetters) ? 0.5 : 0.0;
|
||||
CGFloat row2Spacer = (self.layoutStyle == KBKeyboardLayoutStyleLetters)
|
||||
? kKBLettersRow2EdgeSpacerMultiplier : 0.0;
|
||||
[self buildRow:self.row2 withKeys:self.keysForRows[1] edgeSpacerMultiplier:row2Spacer];
|
||||
|
||||
[self buildRow:self.row3 withKeys:self.keysForRows[2]];
|
||||
[self buildRow:self.row4 withKeys:self.keysForRows[3]];
|
||||
}
|
||||
|
||||
#pragma mark - Key Model Construction
|
||||
|
||||
// 创建当前布局下各行的 KBKey 列表
|
||||
- (NSArray<NSArray<KBKey *> *> *)buildKeysForCurrentLayout {
|
||||
if (self.layoutStyle == KBKeyboardLayoutStyleNumbers) {
|
||||
return [self buildKeysForNumbersLayout];
|
||||
} else {
|
||||
return [self buildKeysForLettersLayout];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Letters Layout
|
||||
|
||||
- (NSArray<NSArray<KBKey *> *> *)buildKeysForLettersLayout {
|
||||
// 字母布局(QWERTY)
|
||||
NSArray *r1Letters = @[ @"q", @"w", @"e", @"r", @"t", @"y", @"u", @"i", @"o", @"p" ];
|
||||
NSArray *r2Letters = @[ @"a", @"s", @"d", @"f", @"g", @"h", @"j", @"k", @"l" ];
|
||||
NSArray *r3Letters = @[ @"z", @"x", @"c", @"v", @"b", @"n", @"m" ];
|
||||
|
||||
NSMutableArray *row1 = [NSMutableArray arrayWithCapacity:r1Letters.count];
|
||||
for (NSString *s in r1Letters) {
|
||||
[row1 addObject:[self kb_letterKeyWithChar:s]];
|
||||
}
|
||||
|
||||
NSMutableArray *row2 = [NSMutableArray arrayWithCapacity:r2Letters.count];
|
||||
for (NSString *s in r2Letters) {
|
||||
[row2 addObject:[self kb_letterKeyWithChar:s]];
|
||||
}
|
||||
|
||||
// 第三行:Shift + Z...M + Backspace
|
||||
NSMutableArray *row3 = [NSMutableArray array];
|
||||
KBKey *shift = [KBKey keyWithIdentifier:@"shift"
|
||||
title:@"⇧"
|
||||
output:@""
|
||||
type:KBKeyTypeShift];
|
||||
// Shift 键也支持大小写两套皮肤图:
|
||||
// - shift.caseVariant = Lower 时,使用 KBSkinIconMap 中 "shift" 对应的短名;
|
||||
// - shift.caseVariant = Upper 时,使用 "shift_upper" 对应的短名。
|
||||
shift.caseVariant = self.shiftOn ? KBKeyCaseVariantUpper : KBKeyCaseVariantLower;
|
||||
[row3 addObject:shift];
|
||||
|
||||
for (NSString *s in r3Letters) {
|
||||
[row3 addObject:[self kb_letterKeyWithChar:s]];
|
||||
}
|
||||
|
||||
KBKey *backspace = [KBKey keyWithIdentifier:@"backspace"
|
||||
title:@"⌫"
|
||||
output:@""
|
||||
type:KBKeyTypeBackspace];
|
||||
[row3 addObject:backspace];
|
||||
|
||||
NSArray *row4 = [self kb_bottomControlRowKeysForLettersLayout];
|
||||
|
||||
return @[row1.copy, row2.copy, row3.copy, row4];
|
||||
}
|
||||
|
||||
#pragma mark - Numbers / Symbols Layout
|
||||
|
||||
- (NSArray<NSArray<KBKey *> *> *)buildKeysForNumbersLayout {
|
||||
// 数字/符号布局:3 行主键 + 底部控制行
|
||||
NSArray *r1 = nil;
|
||||
NSArray *r2 = nil;
|
||||
@@ -118,21 +201,8 @@
|
||||
[KBKey keyWithIdentifier:@"sym_amp" title:@"&" output:@"&" type:KBKeyTypeCharacter],
|
||||
[KBKey keyWithIdentifier:@"sym_at" title:@"@" output:@"@" type:KBKeyTypeCharacter],
|
||||
[KBKey keyWithIdentifier:@"sym_quote_double" title:@"\"" output:@"\"" type:KBKeyTypeCharacter] ];
|
||||
// 第三行:左下角是“#+=”切换键,右下角是退格键
|
||||
KBKey *toggle = [KBKey keyWithIdentifier:@"symbols_toggle_more"
|
||||
title:@"#+="
|
||||
output:@""
|
||||
type:KBKeyTypeSymbolsToggle];
|
||||
KBKey *comma = [KBKey keyWithIdentifier:@"sym_comma" title:@"," output:@"," type:KBKeyTypeCharacter];
|
||||
KBKey *dot = [KBKey keyWithIdentifier:@"sym_dot" title:@"." output:@"." type:KBKeyTypeCharacter];
|
||||
KBKey *q = [KBKey keyWithIdentifier:@"sym_question" title:@"?" output:@"?" type:KBKeyTypeCharacter];
|
||||
KBKey *ex = [KBKey keyWithIdentifier:@"sym_exclam" title:@"!" output:@"!" type:KBKeyTypeCharacter];
|
||||
KBKey *quote = [KBKey keyWithIdentifier:@"sym_quote_single" title:@"'" output:@"'" type:KBKeyTypeCharacter];
|
||||
KBKey *back = [KBKey keyWithIdentifier:@"backspace"
|
||||
title:@"⌫"
|
||||
output:@""
|
||||
type:KBKeyTypeBackspace];
|
||||
r3 = @[ toggle, comma, dot, q, ex, quote, back ];
|
||||
|
||||
r3 = [self kb_symbolsCommonThirdRowWithToggleIsMore:NO];
|
||||
} else {
|
||||
// 数字第二页(#+=):前两行替换为更多符号,左下角按钮文案改为“123”
|
||||
r1 = @[ [KBKey keyWithIdentifier:@"sym_bracket_l" title:@"[" output:@"[" type:KBKeyTypeCharacter],
|
||||
@@ -155,8 +225,40 @@
|
||||
[KBKey keyWithIdentifier:@"sym_euro" title:@"€" output:@"€" type:KBKeyTypeCharacter],
|
||||
[KBKey keyWithIdentifier:@"sym_pound" title:@"£" output:@"£" type:KBKeyTypeCharacter],
|
||||
[KBKey keyWithIdentifier:@"sym_bullet" title:@"•" output:@"•" type:KBKeyTypeCharacter] ];
|
||||
KBKey *toggle = [KBKey keyWithIdentifier:@"symbols_toggle_123"
|
||||
title:@"123"
|
||||
|
||||
r3 = [self kb_symbolsCommonThirdRowWithToggleIsMore:YES];
|
||||
}
|
||||
|
||||
NSArray *r4 = [self kb_bottomControlRowKeysForNumbersLayout];
|
||||
return @[r1, r2, r3, r4];
|
||||
}
|
||||
|
||||
#pragma mark - Key Factories
|
||||
|
||||
// 字母键工厂:根据 shiftOn 决定显示与输出大小写
|
||||
- (KBKey *)kb_letterKeyWithChar:(NSString *)charString {
|
||||
NSParameterAssert(charString.length == 1);
|
||||
NSString *lower = charString.lowercaseString;
|
||||
NSString *upper = charString.uppercaseString;
|
||||
|
||||
NSString *shown = self.shiftOn ? upper : lower;
|
||||
NSString *identifier = [NSString stringWithFormat:@"letter_%@", lower];
|
||||
|
||||
KBKey *k = [KBKey keyWithIdentifier:identifier
|
||||
title:shown
|
||||
output:shown
|
||||
type:KBKeyTypeCharacter];
|
||||
k.caseVariant = self.shiftOn ? KBKeyCaseVariantUpper : KBKeyCaseVariantLower;
|
||||
return k;
|
||||
}
|
||||
|
||||
// 数字布局第三行公共部分(左下角是 123 或 #+=)
|
||||
- (NSArray<KBKey *> *)kb_symbolsCommonThirdRowWithToggleIsMore:(BOOL)isMorePage {
|
||||
NSString *identifier = isMorePage ? @"symbols_toggle_123" : @"symbols_toggle_more";
|
||||
NSString *title = isMorePage ? @"123" : @"#+=";
|
||||
|
||||
KBKey *toggle = [KBKey keyWithIdentifier:identifier
|
||||
title:title
|
||||
output:@""
|
||||
type:KBKeyTypeSymbolsToggle];
|
||||
KBKey *comma = [KBKey keyWithIdentifier:@"sym_comma" title:@"," output:@"," type:KBKeyTypeCharacter];
|
||||
@@ -168,87 +270,11 @@
|
||||
title:@"⌫"
|
||||
output:@""
|
||||
type:KBKeyTypeBackspace];
|
||||
r3 = @[ toggle, comma, dot, q, ex, quote, back ];
|
||||
}
|
||||
|
||||
KBKey *modeABC = [KBKey keyWithIdentifier:@"mode_abc"
|
||||
title:@"abc"
|
||||
output:@""
|
||||
type:KBKeyTypeModeChange];
|
||||
KBKey *customAI = [KBKey keyWithIdentifier:@"ai"
|
||||
title:@"AI"
|
||||
output:@""
|
||||
type:KBKeyTypeCustom];
|
||||
KBKey *space = [KBKey keyWithIdentifier:@"space"
|
||||
title:@"space"
|
||||
output:@" "
|
||||
type:KBKeyTypeSpace];
|
||||
KBKey *ret = [KBKey keyWithIdentifier:@"return"
|
||||
title:KBLocalized(@"Send")
|
||||
output:@"\n"
|
||||
type:KBKeyTypeReturn];
|
||||
|
||||
NSArray *r4 = @[ modeABC, customAI, space, ret ];
|
||||
|
||||
return @[r1, r2, r3, r4];
|
||||
}
|
||||
|
||||
// 字母布局(QWERTY)
|
||||
NSArray *r1 = @[ @"Q", @"W", @"E", @"R", @"T", @"Y", @"U", @"I", @"O", @"P" ];
|
||||
NSArray *r2 = @[ @"A", @"S", @"D", @"F", @"G", @"H", @"J", @"K", @"L" ];
|
||||
NSArray *r3chars = @[ @"Z", @"X", @"C", @"V", @"B", @"N", @"M" ];
|
||||
|
||||
NSMutableArray *row1 = [NSMutableArray arrayWithCapacity:r1.count];
|
||||
// 字母键标题与输出同时随 Shift 切换大小写,界面与输入保持一致
|
||||
for (NSString *s in r1) {
|
||||
NSString *shown = self.shiftOn ? s : s.lowercaseString;
|
||||
NSString *identifier = [NSString stringWithFormat:@"letter_%@", s.lowercaseString];
|
||||
KBKey *k = [KBKey keyWithIdentifier:identifier
|
||||
title:shown
|
||||
output:shown
|
||||
type:KBKeyTypeCharacter];
|
||||
k.caseVariant = self.shiftOn ? KBKeyCaseVariantUpper : KBKeyCaseVariantLower;
|
||||
[row1 addObject:k];
|
||||
}
|
||||
|
||||
NSMutableArray *row2 = [NSMutableArray arrayWithCapacity:r2.count];
|
||||
for (NSString *s in r2) {
|
||||
NSString *shown = self.shiftOn ? s : s.lowercaseString;
|
||||
NSString *identifier = [NSString stringWithFormat:@"letter_%@", s.lowercaseString];
|
||||
KBKey *k = [KBKey keyWithIdentifier:identifier
|
||||
title:shown
|
||||
output:shown
|
||||
type:KBKeyTypeCharacter];
|
||||
k.caseVariant = self.shiftOn ? KBKeyCaseVariantUpper : KBKeyCaseVariantLower;
|
||||
[row2 addObject:k];
|
||||
}
|
||||
|
||||
NSMutableArray *row3 = [NSMutableArray array];
|
||||
KBKey *shift = [KBKey keyWithIdentifier:@"shift"
|
||||
title:@"⇧"
|
||||
output:@""
|
||||
type:KBKeyTypeShift];
|
||||
// Shift 键也支持大小写两套皮肤图:
|
||||
// - shift.caseVariant = Lower 时,使用 KBSkinIconMap 中 "shift" 对应的短名;
|
||||
// - shift.caseVariant = Upper 时,使用 "shift_upper" 对应的短名。
|
||||
shift.caseVariant = self.shiftOn ? KBKeyCaseVariantUpper : KBKeyCaseVariantLower;
|
||||
[row3 addObject:shift];
|
||||
for (NSString *s in r3chars) {
|
||||
NSString *shown = self.shiftOn ? s : s.lowercaseString;
|
||||
NSString *identifier = [NSString stringWithFormat:@"letter_%@", s.lowercaseString];
|
||||
KBKey *k = [KBKey keyWithIdentifier:identifier
|
||||
title:shown
|
||||
output:shown
|
||||
type:KBKeyTypeCharacter];
|
||||
k.caseVariant = self.shiftOn ? KBKeyCaseVariantUpper : KBKeyCaseVariantLower;
|
||||
[row3 addObject:k];
|
||||
}
|
||||
KBKey *backspace = [KBKey keyWithIdentifier:@"backspace"
|
||||
title:@"⌫"
|
||||
output:@""
|
||||
type:KBKeyTypeBackspace];
|
||||
[row3 addObject:backspace];
|
||||
return @[ toggle, comma, dot, q, ex, quote, back ];
|
||||
}
|
||||
|
||||
// 底部控制行(字母布局)
|
||||
- (NSArray<KBKey *> *)kb_bottomControlRowKeysForLettersLayout {
|
||||
KBKey *mode123 = [KBKey keyWithIdentifier:@"mode_123"
|
||||
title:@"123"
|
||||
output:@""
|
||||
@@ -265,26 +291,49 @@
|
||||
title:KBLocalized(@"Send")
|
||||
output:@"\n"
|
||||
type:KBKeyTypeReturn];
|
||||
|
||||
NSArray *row4 = @[ mode123, customAI, space, ret ];
|
||||
|
||||
return @[row1.copy, row2.copy, row3.copy, row4];
|
||||
return @[ mode123, customAI, space, ret ];
|
||||
}
|
||||
|
||||
// 底部控制行(数字布局)
|
||||
- (NSArray<KBKey *> *)kb_bottomControlRowKeysForNumbersLayout {
|
||||
KBKey *modeABC = [KBKey keyWithIdentifier:@"mode_abc"
|
||||
title:@"abc"
|
||||
output:@""
|
||||
type:KBKeyTypeModeChange];
|
||||
KBKey *customAI = [KBKey keyWithIdentifier:@"ai"
|
||||
title:@"AI"
|
||||
output:@""
|
||||
type:KBKeyTypeCustom];
|
||||
KBKey *space = [KBKey keyWithIdentifier:@"space"
|
||||
title:@"space"
|
||||
output:@" "
|
||||
type:KBKeyTypeSpace];
|
||||
KBKey *ret = [KBKey keyWithIdentifier:@"return"
|
||||
title:KBLocalized(@"Send")
|
||||
output:@"\n"
|
||||
type:KBKeyTypeReturn];
|
||||
return @[ modeABC, customAI, space, ret ];
|
||||
}
|
||||
|
||||
#pragma mark - Row Building
|
||||
|
||||
- (void)buildRow:(UIView *)row withKeys:(NSArray<KBKey *> *)keys {
|
||||
[self buildRow:row withKeys:keys edgeSpacerMultiplier:0.0];
|
||||
}
|
||||
|
||||
- (void)buildRow:(UIView *)row withKeys:(NSArray<KBKey *> *)keys edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
|
||||
- (void)buildRow:(UIView *)row
|
||||
withKeys:(NSArray<KBKey *> *)keys
|
||||
edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
|
||||
|
||||
// 第 4 行(底部控制行)使用单独的布局规则:
|
||||
// 123/ABC、AI、Send 给定尺寸,Space 自动吃掉剩余宽度。
|
||||
BOOL isBottomControlRow = [self kb_isBottomControlRowWithKeys:keys];
|
||||
|
||||
CGFloat hInset = 6; // 行左右内边距
|
||||
CGFloat spacing = 0; // 键与键之间的间距
|
||||
UIView *previous = nil;
|
||||
UIView *leftSpacer = nil;
|
||||
UIView *rightSpacer = nil;
|
||||
|
||||
if (edgeSpacerMultiplier > 0.0) {
|
||||
leftSpacer = [UIView new];
|
||||
rightSpacer = [UIView new];
|
||||
@@ -293,16 +342,17 @@
|
||||
[row addSubview:leftSpacer];
|
||||
[row addSubview:rightSpacer];
|
||||
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(row.mas_left).offset(hInset);
|
||||
make.left.equalTo(row.mas_left).offset(kKBRowHorizontalInset);
|
||||
make.centerY.equalTo(row);
|
||||
make.height.mas_equalTo(1);
|
||||
}];
|
||||
[rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(row.mas_right).offset(-hInset);
|
||||
make.right.equalTo(row.mas_right).offset(-kKBRowHorizontalInset);
|
||||
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];
|
||||
@@ -313,12 +363,12 @@
|
||||
[btn addTarget:self action:@selector(onKeyTapped:) forControlEvents:UIControlEventTouchUpInside];
|
||||
[row addSubview:btn];
|
||||
|
||||
// ⌫ 长按:开始连续逐个删除(无需 NSTimer)。使用 UILongPressGestureRecognizer 识别长按,
|
||||
// 在开始态触发递归的轻量调度,每次删除 1 个字符,直到松手或无内容。
|
||||
// ⌫ 长按:开始连续逐个删除(无需 NSTimer)。使用 UILongPressGestureRecognizer 识别长按
|
||||
if (key.type == KBKeyTypeBackspace) {
|
||||
UILongPressGestureRecognizer *lp = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onBackspaceLongPress:)];
|
||||
// 稍短的判定时间,提升响应(默认约 0.5s)。
|
||||
lp.minimumPressDuration = 0.35;
|
||||
UILongPressGestureRecognizer *lp =
|
||||
[[UILongPressGestureRecognizer alloc] initWithTarget:self
|
||||
action:@selector(onBackspaceLongPress:)];
|
||||
lp.minimumPressDuration = kKBBackspaceLongPressMinDuration;
|
||||
lp.cancelsTouchesInView = YES; // 被识别为长按时,取消普通点击
|
||||
[btn addGestureRecognizer:lp];
|
||||
}
|
||||
@@ -336,35 +386,38 @@
|
||||
if (leftSpacer) {
|
||||
make.left.equalTo(leftSpacer.mas_right).offset(spacing);
|
||||
} else {
|
||||
make.left.equalTo(row.mas_left).offset(hInset);
|
||||
make.left.equalTo(row.mas_left).offset(kKBRowHorizontalInset);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
// 宽度规则:字符键等宽;特殊键按倍数放大
|
||||
// 字符键:等宽
|
||||
if (key.type == KBKeyTypeCharacter) {
|
||||
if (previous && previous != nil) {
|
||||
if (((KBKeyButton *)previous).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: give 1.5x of a character key by deferring constraint equalities after loop
|
||||
// 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(-hInset);
|
||||
make.right.equalTo(row.mas_right).offset(-kKBRowHorizontalInset);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
// 底部控制行:在第一轮已完成左右约束的前提下,仅给 123/ABC、AI、Send 指定宽度,
|
||||
// Space 不加宽度约束,让其自动填充剩余空间。
|
||||
@@ -376,19 +429,20 @@
|
||||
// 第二遍:以首个字符键为基准,统一设置特殊键宽度倍数
|
||||
KBKeyButton *firstChar = nil;
|
||||
BOOL hasCharacterInRow = NO;
|
||||
for (KBKeyButton *b in row.subviews) {
|
||||
if (![b isKindOfClass:[KBKeyButton class]]) continue;
|
||||
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 (KBKeyButton *b in row.subviews) {
|
||||
if ([b isKindOfClass:[KBKeyButton class]]) {
|
||||
firstChar = b;
|
||||
for (UIView *v in row.subviews) {
|
||||
if ([v isKindOfClass:[KBKeyButton class]]) {
|
||||
firstChar = (KBKeyButton *)v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -401,41 +455,40 @@
|
||||
firstChar.key.type == KBKeyTypeSymbolsToggle ||
|
||||
firstChar.key.type == KBKeyTypeCustom)) {
|
||||
[firstChar mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.equalTo(firstChar.mas_height);
|
||||
make.width.equalTo(firstChar.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
||||
}];
|
||||
}
|
||||
for (KBKeyButton *b in row.subviews) {
|
||||
if (![b isKindOfClass:[KBKeyButton class]]) continue;
|
||||
// 当本行没有字符键时,firstChar 可能是一个“特殊键”,
|
||||
|
||||
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;
|
||||
|
||||
// 一类键强制宽高比 1:1:
|
||||
// - 第三行:Shift、Backspace
|
||||
// - 第四行:123/ABC(ModeChange)、#+=(SymbolsToggle)、AI(Custom)
|
||||
// (真正的底部控制行在上面已单独处理,这里主要服务于第三行的 #+= 等)
|
||||
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(1.2); // 宽度 = 高度,做成正方形
|
||||
make.width.equalTo(b.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
||||
}];
|
||||
continue;
|
||||
}
|
||||
|
||||
CGFloat multiplier = 1.5;
|
||||
|
||||
// Space:根据行宽可压缩,适当缩小宽度(原来是 4.0,略微调低到 3.0)
|
||||
// Space:宽度更大
|
||||
if (b.key.type == KBKeyTypeSpace) {
|
||||
multiplier = 3.0;
|
||||
multiplier = kKBSpaceWidthMultiplier;
|
||||
}
|
||||
// Send 按钮:宽度为基准键的 2.4 倍
|
||||
else if (b.key.type == KBKeyTypeReturn) {
|
||||
multiplier = 2.4;
|
||||
multiplier = kKBReturnWidthMultiplier;
|
||||
}
|
||||
// 其它特殊键(如 Globe)保持适度放大
|
||||
else if (b.key.type == KBKeyTypeGlobe) {
|
||||
@@ -446,6 +499,7 @@
|
||||
make.width.equalTo(firstChar).multipliedBy(multiplier);
|
||||
}];
|
||||
}
|
||||
|
||||
// 如果有左右占位,则把占位宽度设置为字符键宽度的一定倍数,以实现整体居中
|
||||
if (leftSpacer && rightSpacer) {
|
||||
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
@@ -482,8 +536,8 @@
|
||||
}
|
||||
|
||||
// 为底部控制行设置宽度:
|
||||
// - 123/ABC、AI:正方形(宽 = 行高)
|
||||
// - Send:宽 = 模式键宽度的 2.4 倍
|
||||
// - 123/ABC、AI:正方形(宽 = 行高 * multiplier)
|
||||
// - Send:宽 = 模式键宽度的 2 倍
|
||||
// - Space:不加宽度约束,依靠左右约束自动填充剩余空间。
|
||||
- (void)kb_applyBottomControlRowWidthInRow:(UIView *)row {
|
||||
KBKeyButton *modeBtn = nil;
|
||||
@@ -519,14 +573,14 @@
|
||||
|
||||
// 行高由外部约束为固定值(等于 row1 高度),这里用行高作为“正方形”的边长。
|
||||
[modeBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.equalTo(row.mas_height).multipliedBy(1.2);
|
||||
make.width.equalTo(row.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
||||
}];
|
||||
[aiBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.width.equalTo(row.mas_height).multipliedBy(1.2);
|
||||
make.width.equalTo(row.mas_height).multipliedBy(kKBSpecialKeySquareMultiplier);
|
||||
}];
|
||||
[retBtn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
// Send 按钮:宽度为模式键的 2.4 倍
|
||||
make.width.equalTo(modeBtn.mas_width).multipliedBy(2);
|
||||
// Send 按钮:宽度为模式键的 2 倍
|
||||
make.width.equalTo(modeBtn.mas_width).multipliedBy(2.0);
|
||||
}];
|
||||
// Space 不设置宽度;通过此前已建立的左右约束自动占满剩余宽度。
|
||||
}
|
||||
@@ -571,12 +625,13 @@
|
||||
CGFloat centerX = CGRectGetMidX(btnFrameInSelf);
|
||||
CGFloat centerY = CGRectGetMinY(btnFrameInSelf) - previewHeight * 0.6;
|
||||
|
||||
self.previewView.frame = CGRectMake(0, 0, 40, previewHeight);
|
||||
// 修复:原来写死 40,这里用真正计算出的 previewWidth
|
||||
self.previewView.frame = CGRectMake(0, 0, previewWidth, previewHeight);
|
||||
self.previewView.center = CGPointMake(centerX, centerY);
|
||||
self.previewView.alpha = 0.0;
|
||||
self.previewView.hidden = NO;
|
||||
|
||||
[UIView animateWithDuration:0.08
|
||||
[UIView animateWithDuration:kKBPreviewShowDuration
|
||||
delay:0
|
||||
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut
|
||||
animations:^{
|
||||
@@ -587,7 +642,7 @@
|
||||
|
||||
- (void)hidePreview {
|
||||
if (!self.previewView || self.previewView.isHidden) return;
|
||||
[UIView animateWithDuration:0.06
|
||||
[UIView animateWithDuration:kKBPreviewHideDuration
|
||||
delay:0
|
||||
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn
|
||||
animations:^{
|
||||
@@ -627,7 +682,9 @@
|
||||
[proxy deleteBackward]; // 每次仅删 1 个
|
||||
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.06 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
|
||||
(int64_t)(kKBBackspaceRepeatInterval * NSEC_PER_SEC)),
|
||||
dispatch_get_main_queue(), ^{
|
||||
__strong typeof(weakSelf) selfStrong = weakSelf;
|
||||
[selfStrong kb_backspaceStep];
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@interface KBToolBar ()
|
||||
@property (nonatomic, strong) UIView *leftContainer;
|
||||
@property (nonatomic, strong) NSArray<UIButton *> *leftButtonsInternal;
|
||||
@property (nonatomic, strong) UIButton *settingsButtonInternal;
|
||||
//@property (nonatomic, strong) UIButton *settingsButtonInternal;
|
||||
@property (nonatomic, strong) UIButton *globeButtonInternal; // 可选:系统“切换输入法”键
|
||||
@end
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
- (instancetype)initWithFrame:(CGRect)frame{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.backgroundColor = [UIColor clearColor];
|
||||
_leftButtonTitles = @[@"Item1", @"Item2", @"Item3", @"Item4"]; // 默认标题
|
||||
_leftButtonTitles = @[KBLocalized(@"Recharge Now")]; // 默认标题
|
||||
[self setupUI];
|
||||
}
|
||||
return self;
|
||||
@@ -33,9 +33,9 @@
|
||||
return self.leftButtonsInternal;
|
||||
}
|
||||
|
||||
- (UIButton *)settingsButton {
|
||||
return self.settingsButtonInternal;
|
||||
}
|
||||
//- (UIButton *)settingsButton {
|
||||
// return self.settingsButtonInternal;
|
||||
//}
|
||||
|
||||
- (void)setLeftButtonTitles:(NSArray<NSString *> *)leftButtonTitles {
|
||||
_leftButtonTitles = [leftButtonTitles copy];
|
||||
@@ -51,15 +51,15 @@
|
||||
|
||||
- (void)setupUI {
|
||||
[self addSubview:self.leftContainer];
|
||||
[self addSubview:self.settingsButtonInternal];
|
||||
// [self addSubview:self.settingsButtonInternal];
|
||||
[self addSubview:self.globeButtonInternal];
|
||||
|
||||
// 右侧设置按钮
|
||||
[self.settingsButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.mas_right).offset(-12);
|
||||
make.centerY.equalTo(self.mas_centerY);
|
||||
make.width.height.mas_equalTo(32);
|
||||
}];
|
||||
// [self.settingsButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
// make.right.equalTo(self.mas_right).offset(-12);
|
||||
// make.centerY.equalTo(self.mas_centerY);
|
||||
// make.width.height.mas_equalTo(32);
|
||||
// }];
|
||||
|
||||
// 左侧地球键(按需显示)
|
||||
[self.globeButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
@@ -71,33 +71,41 @@
|
||||
// 左侧容器占用剩余空间
|
||||
[self.leftContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.globeButtonInternal.mas_right).offset(8);
|
||||
make.right.equalTo(self.settingsButtonInternal.mas_left).offset(-12);
|
||||
make.right.equalTo(self).offset(-12);
|
||||
make.centerY.equalTo(self.mas_centerY);
|
||||
make.height.mas_equalTo(32);
|
||||
}];
|
||||
|
||||
// 在左侧容器中创建 4 个等宽按钮
|
||||
NSMutableArray<UIButton *> *buttons = [NSMutableArray arrayWithCapacity:4];
|
||||
// 在左侧容器中创建按钮(数量 = 标题数量)
|
||||
NSMutableArray<UIButton *> *buttons = [NSMutableArray arrayWithCapacity:_leftButtonTitles.count];
|
||||
UIView *previous = nil;
|
||||
for (NSInteger i = 0; i < 4; i++) {
|
||||
for (NSInteger i = 0; i < _leftButtonTitles.count; i++) {
|
||||
UIButton *btn = [self buildActionButtonAtIndex:i];
|
||||
[self.leftContainer addSubview:btn];
|
||||
[buttons addObject:btn];
|
||||
|
||||
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.top.bottom.equalTo(self.leftContainer);
|
||||
|
||||
if (previous) {
|
||||
make.left.equalTo(previous.mas_right).offset(8);
|
||||
make.width.equalTo(previous);
|
||||
} else {
|
||||
make.left.equalTo(self.leftContainer.mas_left);
|
||||
}
|
||||
make.top.bottom.equalTo(self.leftContainer);
|
||||
// 不再设置宽度约束,交给 intrinsicContentSize
|
||||
}];
|
||||
|
||||
previous = btn;
|
||||
}
|
||||
// 最后一个按钮贴右侧
|
||||
|
||||
// 可选:最后一个不要一定贴右边,避免被拉伸
|
||||
// 如果你希望它最多到右边,可以这样:
|
||||
if (previous) {
|
||||
[previous mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.right.equalTo(self.leftContainer.mas_right);
|
||||
make.right.lessThanOrEqualTo(self.leftContainer.mas_right);
|
||||
}];
|
||||
}
|
||||
|
||||
self.leftButtonsInternal = buttons.copy;
|
||||
|
||||
// 初始刷新地球键的可见性与事件绑定
|
||||
@@ -110,9 +118,16 @@
|
||||
btn.layer.masksToBounds = YES;
|
||||
btn.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
|
||||
btn.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
|
||||
NSString *title = (idx < self.leftButtonTitles.count) ? self.leftButtonTitles[idx] : [NSString stringWithFormat:@"Item%ld", (long)(idx+1)];
|
||||
|
||||
NSString *title = (idx < self.leftButtonTitles.count) ? self.leftButtonTitles[idx] : @"";
|
||||
[btn setTitle:title forState:UIControlStateNormal];
|
||||
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
||||
|
||||
// 关键:左右各 10,按钮宽度 = 文本宽度 + 20
|
||||
btn.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 10);
|
||||
[btn setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
|
||||
[btn setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
|
||||
|
||||
btn.tag = idx;
|
||||
[btn addTarget:self action:@selector(onLeftAction:) forControlEvents:UIControlEventTouchUpInside];
|
||||
return btn;
|
||||
@@ -142,18 +157,18 @@
|
||||
return _leftContainer;
|
||||
}
|
||||
|
||||
- (UIButton *)settingsButtonInternal {
|
||||
if (!_settingsButtonInternal) {
|
||||
_settingsButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
_settingsButtonInternal.layer.cornerRadius = 16;
|
||||
_settingsButtonInternal.layer.masksToBounds = YES;
|
||||
_settingsButtonInternal.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
|
||||
[_settingsButtonInternal setTitle:@"⚙︎" forState:UIControlStateNormal]; // 简单的齿轮符号
|
||||
[_settingsButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
||||
[_settingsButtonInternal addTarget:self action:@selector(onSettings) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return _settingsButtonInternal;
|
||||
}
|
||||
//- (UIButton *)settingsButtonInternal {
|
||||
// if (!_settingsButtonInternal) {
|
||||
// _settingsButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
// _settingsButtonInternal.layer.cornerRadius = 16;
|
||||
// _settingsButtonInternal.layer.masksToBounds = YES;
|
||||
// _settingsButtonInternal.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
|
||||
// [_settingsButtonInternal setTitle:@"⚙︎" forState:UIControlStateNormal]; // 简单的齿轮符号
|
||||
// [_settingsButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
|
||||
// [_settingsButtonInternal addTarget:self action:@selector(onSettings) forControlEvents:UIControlEventTouchUpInside];
|
||||
// }
|
||||
// return _settingsButtonInternal;
|
||||
//}
|
||||
|
||||
- (UIButton *)globeButtonInternal {
|
||||
if (!_globeButtonInternal) {
|
||||
@@ -186,7 +201,7 @@
|
||||
} else {
|
||||
make.left.equalTo(self.mas_left).offset(12);
|
||||
}
|
||||
make.right.equalTo(self.settingsButtonInternal.mas_left).offset(-12);
|
||||
make.right.equalTo(self).offset(-12);
|
||||
make.centerY.equalTo(self.mas_centerY);
|
||||
make.height.mas_equalTo(32);
|
||||
}];
|
||||
|
||||
@@ -297,7 +297,7 @@ static NSString * const kKBJfPayCellId = @"kKBJfPayCellId";
|
||||
- (UILabel *)rechargeLabel {
|
||||
if (!_rechargeLabel) {
|
||||
_rechargeLabel = [UILabel new];
|
||||
_rechargeLabel.text = @"Recharge Now";
|
||||
_rechargeLabel.text = KBLocalized(@"Recharge Now");
|
||||
_rechargeLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
|
||||
_rechargeLabel.textColor = [UIColor colorWithHex:KBBlackValue];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user