diff --git a/CustomKeyboard/View/KBKeyButton.h b/CustomKeyboard/View/KBKeyButton.h index 7c214a9..2d43d08 100644 --- a/CustomKeyboard/View/KBKeyButton.h +++ b/CustomKeyboard/View/KBKeyButton.h @@ -10,6 +10,7 @@ @interface KBKeyButton : UIButton @property (nonatomic, strong) KBKey *key; +@property (nonatomic, strong) UIImageView *iconView; /// 配置基础样式(背景、圆角等)。创建按钮时调用。 - (void)applyDefaultStyle; diff --git a/CustomKeyboard/View/KBKeyButton.m b/CustomKeyboard/View/KBKeyButton.m index 0d7bc73..2ef4771 100644 --- a/CustomKeyboard/View/KBKeyButton.m +++ b/CustomKeyboard/View/KBKeyButton.m @@ -8,7 +8,8 @@ #import "KBSkinManager.h" @interface KBKeyButton () -@property (nonatomic, strong) UIImageView *iconView; +// 内部缓存:便于从按钮查找到所属的 KBKeyboardView +@property (nonatomic, weak, readonly) UIView *kb_keyboardContainer; @end @implementation KBKeyButton @@ -63,10 +64,8 @@ - (void)setHighlighted:(BOOL)highlighted { [super setHighlighted:highlighted]; - // 按下时整体做一个等比缩放动画,不改背景色和透明度。 // 这样无论是纯文字键还是整块皮肤图,都有统一的“按下”视觉反馈。 - NSLog(@"来了老弟====="); CGFloat scale = highlighted ? 0.9 : 1.0; // 可根据手感微调 0.9~0.95 [UIView animateWithDuration:0.08 delay:0 @@ -75,6 +74,23 @@ self.transform = CGAffineTransformMakeScale(scale, scale); } completion:nil]; + + // 将“按下/抬起”事件转发给键盘视图,用于显示/隐藏顶部预览气泡。 + UIView *container = self.kb_keyboardContainer; + if ([container respondsToSelector:@selector(showPreviewForButton:)] && + [container respondsToSelector:@selector(hidePreview)]) { + if (highlighted) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [container performSelector:@selector(showPreviewForButton:) withObject:self]; +#pragma clang diagnostic pop + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [container performSelector:@selector(hidePreview)]; +#pragma clang diagnostic pop + } + } } - (void)setSelected:(BOOL)selected { @@ -124,3 +140,19 @@ } @end + +@implementation KBKeyButton (KBKeyboardContainer) + +- (UIView *)kb_keyboardContainer { + UIView *v = self.superview; + while (v) { + // KBKeyboardView 是当前容器类型,这里用类名字符串避免直接引用头文件死循环 + if ([NSStringFromClass(v.class) isEqualToString:@"KBKeyboardView"]) { + return v; + } + v = v.superview; + } + return nil; +} + +@end diff --git a/CustomKeyboard/View/KBKeyPreviewView.h b/CustomKeyboard/View/KBKeyPreviewView.h new file mode 100644 index 0000000..94d8f50 --- /dev/null +++ b/CustomKeyboard/View/KBKeyPreviewView.h @@ -0,0 +1,21 @@ +// +// KBKeyPreviewView.h +// CustomKeyboard +// + +#import + +@class KBKey; + +NS_ASSUME_NONNULL_BEGIN + +/// 按键按下时显示的气泡预览视图(类似系统键盘上方弹出的放大字母)。 +@interface KBKeyPreviewView : UIView + +/// 配置预览内容:字符与可选图标。 +- (void)configureWithKey:(KBKey *)key icon:(nullable UIImage *)icon; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/CustomKeyboard/View/KBKeyPreviewView.m b/CustomKeyboard/View/KBKeyPreviewView.m new file mode 100644 index 0000000..4d8595b --- /dev/null +++ b/CustomKeyboard/View/KBKeyPreviewView.m @@ -0,0 +1,56 @@ +// +// KBKeyPreviewView.m +// CustomKeyboard +// + +#import "KBKeyPreviewView.h" +#import "KBKey.h" + +@interface KBKeyPreviewView () +@property (nonatomic, strong) UILabel *label; +@property (nonatomic, strong) UIImageView *iconView; +@end + +@implementation KBKeyPreviewView + +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + self.backgroundColor = [UIColor colorWithWhite:1 alpha:1.0]; + self.layer.cornerRadius = 8.0; + self.layer.masksToBounds = YES; + self.layer.borderWidth = 0.5; + self.layer.borderColor = [UIColor colorWithWhite:0 alpha:0.15].CGColor; + self.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.3].CGColor; + self.layer.shadowOpacity = 0.6; + self.layer.shadowOffset = CGSizeMake(0, 2); + self.layer.shadowRadius = 4.0; + +// _iconView = [[UIImageView alloc] initWithFrame:CGRectZero]; +// _iconView.contentMode = UIViewContentModeScaleAspectFit; +// _iconView.translatesAutoresizingMaskIntoConstraints = NO; +// [self addSubview:_iconView]; + + _label = [[UILabel alloc] initWithFrame:CGRectZero]; + _label.font = [UIFont systemFontOfSize:28 weight:UIFontWeightSemibold]; + _label.textAlignment = NSTextAlignmentCenter; + _label.textColor = [UIColor blackColor]; + _label.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_label]; + + // 预览只使用文本:尽量靠近顶部显示,上下留少量安全距离。 + [NSLayoutConstraint activateConstraints:@[ + [_label.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:4], + [_label.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-4], + [_label.topAnchor constraintEqualToAnchor:self.topAnchor constant:2], + [_label.bottomAnchor constraintEqualToAnchor:self.bottomAnchor constant:-2], + ]]; + } + return self; +} + +- (void)configureWithKey:(KBKey *)key icon:(UIImage *)icon { + NSString *text = key.output.length > 0 ? key.output : key.title; + self.label.text = text; +} + +@end diff --git a/CustomKeyboard/View/KBKeyboardView.m b/CustomKeyboard/View/KBKeyboardView.m index 2cb036b..6029806 100644 --- a/CustomKeyboard/View/KBKeyboardView.m +++ b/CustomKeyboard/View/KBKeyboardView.m @@ -8,6 +8,7 @@ #import "KBKey.h" #import "KBResponderUtils.h" // 封装的响应链工具 #import "KBSkinManager.h" +#import "KBKeyPreviewView.h" @interface KBKeyboardView () @property (nonatomic, strong) UIView *row1; @@ -17,6 +18,7 @@ @property (nonatomic, strong) NSArray *> *keysForRows; // 长按退格的一次次删除控制标记(不使用 NSTimer,仅用 GCD 递归调度) @property (nonatomic, assign) BOOL backspaceHoldActive; +@property (nonatomic, strong) KBKeyPreviewView *previewView; @end @implementation KBKeyboardView @@ -463,6 +465,53 @@ } } +// 在字符键按下时,显示一个上方气泡预览(类似系统键盘)。 +- (void)showPreviewForButton:(KBKeyButton *)button { + KBKey *key = button.key; + if (key.type != KBKeyTypeCharacter) return; + + if (!self.previewView) { + self.previewView = [[KBKeyPreviewView alloc] initWithFrame:CGRectZero]; + self.previewView.hidden = YES; + [self addSubview:self.previewView]; + } + + [self.previewView configureWithKey:key icon:button.iconView.image]; + + // 计算预览视图位置:在按钮上方稍微偏上 + CGRect btnFrameInSelf = [button convertRect:button.bounds toView:self]; + CGFloat previewWidth = MAX(CGRectGetWidth(btnFrameInSelf) * 1.4, 42.0); + CGFloat previewHeight = CGRectGetHeight(btnFrameInSelf) * 1.2; + CGFloat centerX = CGRectGetMidX(btnFrameInSelf); + CGFloat centerY = CGRectGetMinY(btnFrameInSelf) - previewHeight * 0.6; + + 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 + delay:0 + options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut + animations:^{ + self.previewView.alpha = 1.0; + } + completion:nil]; +} + +- (void)hidePreview { + if (!self.previewView || self.previewView.isHidden) return; + [UIView animateWithDuration:0.06 + delay:0 + options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn + animations:^{ + self.previewView.alpha = 0.0; + } + completion:^(BOOL finished) { + self.previewView.hidden = YES; + }]; +} + // 长按退格:按住时以小间隔逐个删除;松手停止。(不使用 NSTimer/DisplayLink) - (void)onBackspaceLongPress:(UILongPressGestureRecognizer *)gr { switch (gr.state) { diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 7014ab7..8507fcb 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; }; 0459D1B82EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; }; 046131112ECF3A6E00A6FADF /* fense.zip in Resources */ = {isa = PBXBuildFile; fileRef = 046131102ECF3A6E00A6FADF /* fense.zip */; }; + 046131142ECF454500A6FADF /* KBKeyPreviewView.m in Sources */ = {isa = PBXBuildFile; fileRef = 046131132ECF454500A6FADF /* KBKeyPreviewView.m */; }; 0477BD952EBAFF4E0055D639 /* KBURLOpenBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BD932EBAFF4E0055D639 /* KBURLOpenBridge.m */; }; 0477BDF02EBB76E30055D639 /* HomeSheetVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDEF2EBB76E30055D639 /* HomeSheetVC.m */; }; 0477BDF32EBB7B850055D639 /* KBDirectionIndicatorView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BDF22EBB7B850055D639 /* KBDirectionIndicatorView.m */; }; @@ -230,6 +231,8 @@ 0459D1B52EBA287900F2D189 /* KBSkinManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinManager.h; sourceTree = ""; }; 0459D1B62EBA287900F2D189 /* KBSkinManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinManager.m; sourceTree = ""; }; 046131102ECF3A6E00A6FADF /* fense.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = fense.zip; sourceTree = ""; }; + 046131122ECF454500A6FADF /* KBKeyPreviewView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyPreviewView.h; sourceTree = ""; }; + 046131132ECF454500A6FADF /* KBKeyPreviewView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBKeyPreviewView.m; sourceTree = ""; }; 0477BD922EBAFF4E0055D639 /* KBURLOpenBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBURLOpenBridge.h; sourceTree = ""; }; 0477BD932EBAFF4E0055D639 /* KBURLOpenBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBURLOpenBridge.m; sourceTree = ""; }; 0477BDEE2EBB76E30055D639 /* HomeSheetVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeSheetVC.h; sourceTree = ""; }; @@ -871,6 +874,8 @@ 04FC95692EB05497007BD342 /* KBKeyButton.m */, 04FC956B2EB054B7007BD342 /* KBKeyboardView.h */, 04FC956C2EB054B7007BD342 /* KBKeyboardView.m */, + 046131122ECF454500A6FADF /* KBKeyPreviewView.h */, + 046131132ECF454500A6FADF /* KBKeyPreviewView.m */, 04FC95772EB09BC8007BD342 /* KBKeyBoardMainView.h */, 04FC95782EB09BC8007BD342 /* KBKeyBoardMainView.m */, 04FC956E2EB09516007BD342 /* KBFunctionView.h */, @@ -1524,6 +1529,7 @@ 04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */, 04C6EADD2EAF8CEB0089C901 /* KBToolBar.m in Sources */, 04FC95792EB09BC8007BD342 /* KBKeyBoardMainView.m in Sources */, + 046131142ECF454500A6FADF /* KBKeyPreviewView.m in Sources */, 04FC95732EB09570007BD342 /* KBFunctionBarView.m in Sources */, 04C6EAD82EAF870B0089C901 /* KeyboardViewController.m in Sources */, 0459D1B82EBA287900F2D189 /* KBSkinManager.m in Sources */,