From 3e2dc4bcb6feabbd45130eab8685c990743879a0 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Tue, 4 Nov 2025 16:37:24 +0800 Subject: [PATCH] 1 --- CustomKeyboard/View/KBFullAccessGuideView.m | 14 ++---- CustomKeyboard/View/KBFunctionView.m | 21 +++------ CustomKeyboard/View/KBKeyboardView.m | 49 +++++++++++++++++++++ CustomKeyboard/View/KBResponderUtils.h | 28 ++++++++++++ keyBoard.xcodeproj/project.pbxproj | 2 + keyBoard/keyBoard.entitlements | 4 ++ 6 files changed, 92 insertions(+), 26 deletions(-) create mode 100644 CustomKeyboard/View/KBResponderUtils.h diff --git a/CustomKeyboard/View/KBFullAccessGuideView.m b/CustomKeyboard/View/KBFullAccessGuideView.m index 6b7b49e..4ad0c5a 100644 --- a/CustomKeyboard/View/KBFullAccessGuideView.m +++ b/CustomKeyboard/View/KBFullAccessGuideView.m @@ -5,6 +5,7 @@ #import "KBFullAccessGuideView.h" #import "Masonry.h" +#import "KBResponderUtils.h" // 统一查找 UIInputViewController 的工具 @interface KBFullAccessGuideView () @property (nonatomic, strong) UIControl *backdrop; @@ -146,21 +147,12 @@ #pragma mark - Actions -- (UIInputViewController *)kb_findInputController { - UIResponder *res = self; - while (res) { - if ([res isKindOfClass:[UIInputViewController class]]) { - return (UIInputViewController *)res; - } - res = res.nextResponder; - } - return nil; -} +// 工具方法已提取到 KBResponderUtils.h - (void)onTapGoEnable { // 在扩展中无法使用 UIApplication。改为委托宿主打开链接: // 方案:优先拉起主 App 并由主 App 打开设置页,避免宿主拦截。 - UIInputViewController *ivc = [self kb_findInputController]; + UIInputViewController *ivc = KBFindInputViewController(self); if (!ivc) { [self dismiss]; return; } // 先尝试 Universal Link(如未配置可改为你的域名),失败再用自定义 scheme。 diff --git a/CustomKeyboard/View/KBFunctionView.m b/CustomKeyboard/View/KBFunctionView.m index 08c11f1..520ba9c 100644 --- a/CustomKeyboard/View/KBFunctionView.m +++ b/CustomKeyboard/View/KBFunctionView.m @@ -6,6 +6,7 @@ // #import "KBFunctionView.h" +#import "KBResponderUtils.h" // 统一查找 UIInputViewController 的工具 #import "KBFunctionBarView.h" #import "KBFunctionPasteView.h" #import "KBFunctionTagCell.h" @@ -169,7 +170,7 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; [KBHUD showInfo:@"处理中…"]; // return; - UIInputViewController *ivc = [self findInputViewController]; + UIInputViewController *ivc = KBFindInputViewController(self); if (!ivc) return; NSString *title = (indexPath.item < self.itemsInternal.count) ? self.itemsInternal[indexPath.item] : @""; @@ -268,14 +269,14 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; } - (void)onTapDelete { NSLog(@"点击:删除"); - UIInputViewController *ivc = [self findInputViewController]; + UIInputViewController *ivc = KBFindInputViewController(self); id proxy = ivc.textDocumentProxy; [proxy deleteBackward]; } - (void)onTapClear { NSLog(@"点击:清空"); // 连续删除:仅清空光标之前的输入(不改动 pasteView 的内容) - UIInputViewController *ivc = [self findInputViewController]; + UIInputViewController *ivc = KBFindInputViewController(self); id proxy = ivc.textDocumentProxy; // 逐批读取 documentContextBeforeInput 并删除,避免 50 字符窗口限制带来的残留 NSInteger guard = 0; // 上限保护,避免极端情况下长时间阻塞 @@ -292,7 +293,7 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; - (void)onTapSend { NSLog(@"点击:发送"); // 发送:插入换行。大多数聊天类 App 会把回车视为“发送” - UIInputViewController *ivc = [self findInputViewController]; + UIInputViewController *ivc = KBFindInputViewController(self); id proxy = ivc.textDocumentProxy; [proxy insertText:@"\n"]; } @@ -417,16 +418,6 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; #pragma mark - Find Owner Controller -// 在视图的响应链中查找宿主 UIInputViewController(KeyboardViewController) -- (UIInputViewController *)findInputViewController { - UIResponder *responder = self; - while (responder) { - if ([responder isKindOfClass:[UIInputViewController class]]) { - return (UIInputViewController *)responder; - } - responder = responder.nextResponder; - } - return nil; -} +// 工具方法已提取到 KBResponderUtils.h @end diff --git a/CustomKeyboard/View/KBKeyboardView.m b/CustomKeyboard/View/KBKeyboardView.m index 3ab30ec..bc1c48f 100644 --- a/CustomKeyboard/View/KBKeyboardView.m +++ b/CustomKeyboard/View/KBKeyboardView.m @@ -6,6 +6,7 @@ #import "KBKeyboardView.h" #import "KBKeyButton.h" #import "KBKey.h" +#import "KBResponderUtils.h" // 封装的响应链工具 @interface KBKeyboardView () @property (nonatomic, strong) UIView *row1; @@ -13,6 +14,8 @@ @property (nonatomic, strong) UIView *row3; @property (nonatomic, strong) UIView *row4; @property (nonatomic, strong) NSArray *> *keysForRows; +// 长按退格的一次次删除控制标记(不使用 NSTimer,仅用 GCD 递归调度) +@property (nonatomic, assign) BOOL backspaceHoldActive; @end @implementation KBKeyboardView @@ -196,6 +199,16 @@ [btn addTarget:self action:@selector(onKeyTapped:) forControlEvents:UIControlEventTouchUpInside]; [row addSubview:btn]; + // ⌫ 长按:开始连续逐个删除(无需 NSTimer)。使用 UILongPressGestureRecognizer 识别长按, + // 在开始态触发递归的轻量调度,每次删除 1 个字符,直到松手或无内容。 + if (key.type == KBKeyTypeBackspace) { + UILongPressGestureRecognizer *lp = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onBackspaceLongPress:)]; + // 稍短的判定时间,提升响应(默认约 0.5s)。 + lp.minimumPressDuration = 0.35; + lp.cancelsTouchesInView = YES; // 被识别为长按时,取消普通点击 + [btn addGestureRecognizer:lp]; + } + // Shift 按钮选中态随大小写状态变化 if (key.type == KBKeyTypeShift) { btn.selected = self.shiftOn; @@ -293,6 +306,42 @@ } } +// 长按退格:按住时以小间隔逐个删除;松手停止。(不使用 NSTimer/DisplayLink) +- (void)onBackspaceLongPress:(UILongPressGestureRecognizer *)gr { + switch (gr.state) { + case UIGestureRecognizerStateBegan: { + self.backspaceHoldActive = YES; + [self kb_backspaceStep]; + } break; + case UIGestureRecognizerStateEnded: + case UIGestureRecognizerStateCancelled: + case UIGestureRecognizerStateFailed: { + self.backspaceHoldActive = NO; + } break; + default: break; + } +} + +#pragma mark - Helpers + +// 单步删除并在需要时安排下一次,直到松手或无内容 +- (void)kb_backspaceStep { + if (!self.backspaceHoldActive) { return; } + UIInputViewController *ivc = KBFindInputViewController(self); + if (!ivc) { self.backspaceHoldActive = NO; return; } + id proxy = ivc.textDocumentProxy; + NSString *before = proxy.documentContextBeforeInput ?: @""; + if (before.length <= 0) { self.backspaceHoldActive = NO; return; } + [proxy deleteBackward]; // 每次仅删 1 个 + + // 轻量递归调度下一次;不使用 NSTimer,避免复杂状态管理 + __weak typeof(self) weakSelf = self; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.06 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + __strong typeof(weakSelf) selfStrong = weakSelf; + [selfStrong kb_backspaceStep]; + }); +} + #pragma mark - Lazy - (UIView *)row1 { if (!_row1) _row1 = [UIView new]; return _row1; } diff --git a/CustomKeyboard/View/KBResponderUtils.h b/CustomKeyboard/View/KBResponderUtils.h new file mode 100644 index 0000000..802b1b2 --- /dev/null +++ b/CustomKeyboard/View/KBResponderUtils.h @@ -0,0 +1,28 @@ +// +// KBResponderUtils.h +// CustomKeyboard +// +// 统一封装:从任意 UIView/UIResponder 起,向响应链上查找 UIInputViewController。 +// 作为 header‑only 的工具,便于多处直接引入使用。 +// + +#import + +#ifndef KBResponderUtils_h +#define KBResponderUtils_h + +/// 从给定 responder 开始,沿响应链查找宿主 UIInputViewController。 +/// 用法:UIInputViewController *ivc = KBFindInputViewController(self); +static inline UIInputViewController *KBFindInputViewController(UIResponder *start) { + UIResponder *responder = start; + while (responder) { + if ([responder isKindOfClass:[UIInputViewController class]]) { + return (UIInputViewController *)responder; + } + responder = responder.nextResponder; + } + return nil; +} + +#endif /* KBResponderUtils_h */ + diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 3cd45b9..71ca812 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -87,6 +87,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBResponderUtils.h; sourceTree = ""; }; 04A9FE102EB4D0D20020DB6D /* KBFullAccessManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFullAccessManager.h; sourceTree = ""; }; 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFullAccessManager.m; sourceTree = ""; }; 04A9FE142EB873C80020DB6D /* UIViewController+Extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+Extension.h"; sourceTree = ""; }; @@ -266,6 +267,7 @@ children = ( 04C6EADB2EAF8CEB0089C901 /* KBToolBar.h */, 04C6EADC2EAF8CEB0089C901 /* KBToolBar.m */, + 04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */, 04FC95682EB05497007BD342 /* KBKeyButton.h */, 04FC95692EB05497007BD342 /* KBKeyButton.m */, 04FC956B2EB054B7007BD342 /* KBKeyboardView.h */, diff --git a/keyBoard/keyBoard.entitlements b/keyBoard/keyBoard.entitlements index 34d0a44..eabbbc8 100644 --- a/keyBoard/keyBoard.entitlements +++ b/keyBoard/keyBoard.entitlements @@ -6,6 +6,10 @@ Default + com.apple.developer.associated-domains + + applinks:app.tknb.net + keychain-access-groups $(AppIdentifierPrefix)com.loveKey.nyx.shared