From 7a1b17d0601526f75171b850cf033bcde9045239 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Wed, 5 Nov 2025 18:10:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=84=E7=90=86=E9=94=AE=E7=9B=98=E4=B8=8D?= =?UTF-8?q?=E8=83=BD=E6=8B=89=E8=B5=B7=E4=B8=BBapp=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CustomKeyboard/Info.plist | 4 + CustomKeyboard/KeyboardViewController.m | 16 +++- CustomKeyboard/PrefixHeader.pch | 8 +- CustomKeyboard/Utils/KBURLOpenBridge.h | 30 ++++++ CustomKeyboard/Utils/KBURLOpenBridge.m | 46 ++++++++++ CustomKeyboard/View/KBFullAccessGuideView.m | 92 +++++++++++++++---- CustomKeyboard/View/KBFunctionView.m | 61 ++++++++---- CustomKeyboard/View/KBKeyboardView.m | 3 + Shared/KBConfig.h | 10 +- keyBoard.xcodeproj/project.pbxproj | 18 +++- .../xcshareddata/xcschemes/keyBoard.xcscheme | 11 +-- keyBoard/AppDelegate.m | 17 +++- keyBoard/Info.plist | 4 +- keyBoard/KeyBoardPrefixHeader.pch | 2 +- 14 files changed, 270 insertions(+), 52 deletions(-) create mode 100644 CustomKeyboard/Utils/KBURLOpenBridge.h create mode 100644 CustomKeyboard/Utils/KBURLOpenBridge.m diff --git a/CustomKeyboard/Info.plist b/CustomKeyboard/Info.plist index c25c1ec..6b0b101 100644 --- a/CustomKeyboard/Info.plist +++ b/CustomKeyboard/Info.plist @@ -2,6 +2,10 @@ + LSApplicationQueriesSchemes + + kbkeyboardAppExtension + NSExtension NSExtensionAttributes diff --git a/CustomKeyboard/KeyboardViewController.m b/CustomKeyboard/KeyboardViewController.m index aa3670d..e27dcff 100644 --- a/CustomKeyboard/KeyboardViewController.m +++ b/CustomKeyboard/KeyboardViewController.m @@ -52,8 +52,17 @@ static CGFloat KEYBOARDHEIGHT = 256 + 20; - (void)setupUI { - // 固定键盘整体高度 - [self.view.heightAnchor constraintEqualToConstant:KEYBOARDHEIGHT].active = YES; + // 固定键盘整体高度;为减少与系统自调整高度产生的告警,将优先级设置为 High + NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:KEYBOARDHEIGHT]; + h.priority = UILayoutPriorityDefaultHigh; // 750 + h.active = YES; + // 关闭 UIInputView 自适应(某些系统版本会尝试放大为全屏高度导致冲突) + if ([self.view isKindOfClass:[UIInputView class]]) { + UIInputView *iv = (UIInputView *)self.view; + if ([iv respondsToSelector:@selector(setAllowsSelfSizing:)]) { + iv.allowsSelfSizing = NO; + } + } // 背景图铺底 [self.view addSubview:self.bgImageView]; [self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) { @@ -222,7 +231,8 @@ static CGFloat KEYBOARDHEIGHT = 256 + 20; } - (void)kb_tryOpenContainerForLoginIfNeeded { - NSURL *url = [NSURL URLWithString:@"kbkeyboard://login?src=keyboard"]; + // 使用与主 App 一致的自定义 Scheme + NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//login?src=keyboard", KB_APP_SCHEME]]; if (!url) return; __weak typeof(self) weakSelf = self; [self.extensionContext openURL:url completionHandler:^(__unused BOOL success) { diff --git a/CustomKeyboard/PrefixHeader.pch b/CustomKeyboard/PrefixHeader.pch index 5d96a48..4623ba0 100644 --- a/CustomKeyboard/PrefixHeader.pch +++ b/CustomKeyboard/PrefixHeader.pch @@ -23,9 +23,15 @@ // 通用链接(Universal Links)统一配置 // 配置好 AASA 与 Associated Domains 后,只需修改这里即可切换域名/path。 -#define KB_UL_BASE @"https://your.domain/ul" // 替换为你的真实域名与前缀路径 +#define KB_UL_BASE @"https://app.tknb.net/ul" // 与 Associated Domains 一致 #define KB_UL_LOGIN KB_UL_BASE @"/login" #define KB_UL_SETTINGS KB_UL_BASE @"/settings" +// 在扩展内,启用 URL Bridge(仅在显式的用户点击动作中使用) +// 这样即便宿主 App(如备忘录)拒绝 extensionContext 的 openURL,仍可通过响应链兜底拉起容器 App。 +#ifndef KB_URL_BRIDGE_ENABLE +#define KB_URL_BRIDGE_ENABLE 1 +#endif + #endif /* PrefixHeader_pch */ diff --git a/CustomKeyboard/Utils/KBURLOpenBridge.h b/CustomKeyboard/Utils/KBURLOpenBridge.h new file mode 100644 index 0000000..9237c7b --- /dev/null +++ b/CustomKeyboard/Utils/KBURLOpenBridge.h @@ -0,0 +1,30 @@ +// +// KBURLOpenBridge.h +// 非公开:通过响应链查找 `openURL:` 选择器,尝试在扩展环境中打开自定义 scheme。 +// 警告:存在审核风险。默认仅 Debug 启用(见 KB_URL_BRIDGE_ENABLE)。 +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +#ifndef KB_URL_BRIDGE_ENABLE +#if DEBUG +#define KB_URL_BRIDGE_ENABLE 1 +#else +#define KB_URL_BRIDGE_ENABLE 0 +#endif +#endif + +@interface KBURLOpenBridge : NSObject + +/// 尝试通过响应链调用 openURL:(仅在 KB_URL_BRIDGE_ENABLE 为 1 时执行)。 +/// @param url 自定义 scheme,如 kbkeyboard://settings +/// @param start 起始 responder(传 self 或任意视图) +/// @return 是否看起来已发起打开动作(不保证一定成功) ++ (BOOL)openURLViaResponder:(NSURL *)url from:(UIResponder *)start; + +@end + +NS_ASSUME_NONNULL_END + diff --git a/CustomKeyboard/Utils/KBURLOpenBridge.m b/CustomKeyboard/Utils/KBURLOpenBridge.m new file mode 100644 index 0000000..84aaaed --- /dev/null +++ b/CustomKeyboard/Utils/KBURLOpenBridge.m @@ -0,0 +1,46 @@ +// +// KBURLOpenBridge.m +// + +#import "KBURLOpenBridge.h" +#import + +@implementation KBURLOpenBridge + ++ (BOOL)openURLViaResponder:(NSURL *)url from:(UIResponder *)start { +#if KB_URL_BRIDGE_ENABLE + if (!url || !start) return NO; + SEL sel = NSSelectorFromString(@"openURL:"); + UIResponder *responder = start; + while (responder) { + @try { + if ([responder respondsToSelector:sel]) { + // 尽量按签名调用;若失败则回退 performSelector + BOOL handled = NO; + // 尝试 (BOOL)openURL:(NSURL *) + BOOL (*funcBool)(id, SEL, NSURL *) = (BOOL (*)(id, SEL, NSURL *))objc_msgSend; + if (funcBool) { + handled = funcBool(responder, sel, url); + } else { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [responder performSelector:sel withObject:url]; + handled = YES; +#pragma clang diagnostic pop + } + return handled; + } + } @catch (__unused NSException *e) { + // ignore and continue + } + responder = responder.nextResponder; + } + return NO; +#else + (void)url; (void)start; + return NO; +#endif +} + +@end + diff --git a/CustomKeyboard/View/KBFullAccessGuideView.m b/CustomKeyboard/View/KBFullAccessGuideView.m index 4ad0c5a..248641d 100644 --- a/CustomKeyboard/View/KBFullAccessGuideView.m +++ b/CustomKeyboard/View/KBFullAccessGuideView.m @@ -6,10 +6,14 @@ #import "KBFullAccessGuideView.h" #import "Masonry.h" #import "KBResponderUtils.h" // 统一查找 UIInputViewController 的工具 +#import "KBHUD.h" +#import "KBURLOpenBridge.h" @interface KBFullAccessGuideView () @property (nonatomic, strong) UIControl *backdrop; @property (nonatomic, strong) UIView *card; +// 预先保存当前键盘控制器,避免运行时通过响应链找不到 +@property (nonatomic, weak) UIInputViewController *ivc; @end @implementation KBFullAccessGuideView @@ -110,7 +114,8 @@ } - (void)presentIn:(UIView *)parent { - UIView *container = parent.window ?: parent; + if (!parent) return; + UIView *container = parent; // 关键:加到键盘视图树中,而不是 window self.frame = container.bounds; self.alpha = 0; [container addSubview:self]; @@ -126,15 +131,19 @@ + (void)showInView:(UIView *)parent { if (!parent) return; - // 避免重复 - for (UIView *v in (parent.window ?: parent).subviews) { + // 避免重复(仅在 parent 层级检查) + for (UIView *v in parent.subviews) { if ([v isKindOfClass:[KBFullAccessGuideView class]]) return; } - [[KBFullAccessGuideView build] presentIn:parent]; + KBFullAccessGuideView *view = [KBFullAccessGuideView build]; + // 预取 ivc + view.ivc = KBFindInputViewController(parent); + [view presentIn:parent]; } + (void)dismissFromView:(UIView *)parent { - UIView *container = parent.window ?: parent; + UIView *container = parent; + if (!container) return; for (UIView *v in container.subviews) { if ([v isKindOfClass:[KBFullAccessGuideView class]]) { [(KBFullAccessGuideView *)v dismiss]; @@ -148,30 +157,73 @@ #pragma mark - Actions // 工具方法已提取到 KBResponderUtils.h - +// 打开主 App,引导用户去系统设置开启完全访问:优先 Scheme,失败再试 UL;仍失败则提示手动路径。 - (void)onTapGoEnable { - // 在扩展中无法使用 UIApplication。改为委托宿主打开链接: - // 方案:优先拉起主 App 并由主 App 打开设置页,避免宿主拦截。 UIInputViewController *ivc = KBFindInputViewController(self); if (!ivc) { [self dismiss]; return; } - - // 先尝试 Universal Link(如未配置可改为你的域名),失败再用自定义 scheme。 + + // 自定义 Scheme(App 里在 openURL 中转到设置页) + // 统一使用主 App 的自定义 Scheme + NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//settings?src=kb_extension", KB_APP_SCHEME]]; + // Universal Link(需 AASA/Associated Domains 配置且 KB_UL_BASE 与域名一致) NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=kb_extension", KB_UL_SETTINGS]]; - void (^fallback)(void) = ^{ - NSURL *scheme = [NSURL URLWithString:@"kbkeyboard://settings?src=kb_extension"]; // 主App在 openURL 中处理 - [ivc.extensionContext openURL:scheme completionHandler:^(__unused BOOL ok2) { - // 无论成功与否,都收起当前提示层,避免遮挡 - [self dismiss]; - }]; + + void (^finish)(BOOL) = ^(BOOL ok){ + if (ok) { [self dismiss]; } + else { + [KBHUD showInfo:@"无法自动打开,请按路径:设置→通用→键盘→键盘→恋爱键盘→允许完全访问"]; // 失败兜底提示 + } }; - + + // 先试 Scheme(更可能被宿主允许直接拉起 App) + if (scheme) { + [ivc.extensionContext openURL:scheme completionHandler:^(BOOL ok) { + if (ok) { finish(YES); return; } + if (ul) { + [ivc.extensionContext openURL:ul completionHandler:^(BOOL ok2) { + if (ok2) { finish(YES); return; } + // 兜底:在用户点击触发的场景下,尝试通过响应链调用 openURL: + BOOL bridged = NO; + @try { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunguarded-availability" + bridged = [KBURLOpenBridge openURLViaResponder:scheme from:self]; + if (!bridged && ul) { + bridged = [KBURLOpenBridge openURLViaResponder:ul from:self]; + } + #pragma clang diagnostic pop + } @catch (__unused NSException *e) { bridged = NO; } + finish(bridged); + }]; + } else { + // 没有 UL,则直接尝试桥接 Scheme + BOOL bridged = NO; + @try { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunguarded-availability" + bridged = [KBURLOpenBridge openURLViaResponder:scheme from:self]; + #pragma clang diagnostic pop + } @catch (__unused NSException *e) { bridged = NO; } + finish(bridged); + } + }]; + return; + } + // 无 scheme 时,直接尝试 UL if (ul) { [ivc.extensionContext openURL:ul completionHandler:^(BOOL ok) { - if (ok) { [self dismiss]; } - else { fallback(); } + if (ok) { finish(YES); return; } + BOOL bridged = NO; + @try { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunguarded-availability" + bridged = [KBURLOpenBridge openURLViaResponder:ul from:self]; + #pragma clang diagnostic pop + } @catch (__unused NSException *e) { bridged = NO; } + finish(bridged); }]; } else { - fallback(); + finish(NO); } } @end diff --git a/CustomKeyboard/View/KBFunctionView.m b/CustomKeyboard/View/KBFunctionView.m index 547b9db..5612ba7 100644 --- a/CustomKeyboard/View/KBFunctionView.m +++ b/CustomKeyboard/View/KBFunctionView.m @@ -15,6 +15,7 @@ #import "KBFullAccessGuideView.h" #import "KBFullAccessManager.h" #import "KBSkinManager.h" +#import "KBURLOpenBridge.h" // 兜底从扩展侧直接尝试 openURL: static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; @@ -49,6 +50,9 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; // 初始化剪贴板监控状态 _lastHandledPBCount = [UIPasteboard generalPasteboard].changeCount; + + // 监听“完全访问”状态变化,动态启停剪贴板监控,避免在未开完全访问时触发 TCC/XPC 错误日志 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kb_fullAccessChanged) name:KBFullAccessChangedNotification object:nil]; } return self; } @@ -64,6 +68,7 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; - (void)dealloc { [self stopPasteboardMonitor]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - UI @@ -92,10 +97,11 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; [self.rightButtonContainer addSubview:self.clearButtonInternal]; [self.rightButtonContainer addSubview:self.sendButtonInternal]; - // 竖向排布:粘贴、删除、清空为等高,发送略高 + // 竖向排布:粘贴、删除、清空为等高;发送优先更高,但允许在空间不足时压缩 CGFloat smallH = 44; CGFloat bigH = 56; - CGFloat vSpace = 10; + // 原 10 在键盘总高度 276 下容易超出容器,改为 8 以避免 AutoLayout 冲突 + CGFloat vSpace = 8; [self.pasteButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.rightButtonContainer.mas_top); make.left.right.equalTo(self.rightButtonContainer); @@ -114,8 +120,10 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; [self.sendButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) { make.top.equalTo(self.clearButtonInternal.mas_bottom).offset(vSpace); make.left.right.equalTo(self.rightButtonContainer); - make.height.mas_equalTo(bigH); - make.bottom.lessThanOrEqualTo(self.rightButtonContainer.mas_bottom); // 底部可伸缩 + // 允许在空间不足时缩短到 smallH,避免产生约束冲突 + make.height.greaterThanOrEqualTo(@(smallH)); + make.height.lessThanOrEqualTo(@(bigH)); + make.bottom.lessThanOrEqualTo(self.rightButtonContainer.mas_bottom); }]; // 2. 粘贴区(位于右侧按钮左侧) @@ -193,9 +201,22 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; [ivc.extensionContext openURL:ul completionHandler:^(BOOL ok) { if (ok) return; // Universal Link 成功 - NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"kbkeyboard://login?src=functionView&index=%ld&title=%@", (long)indexPath.item, encodedTitle]]; + // 统一使用主 App 注册的自定义 Scheme + NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//login?src=functionView&index=%ld&title=%@", KB_APP_SCHEME, (long)indexPath.item, encodedTitle]]; [ivc.extensionContext openURL:scheme completionHandler:^(BOOL ok2) { - if (!ok2) { + if (ok2) return; + + // 兜底:在用户点击触发的场景下,尝试通过响应链调用 openURL: + // 以提升在“备忘录”等宿主中的成功率。 + BOOL bridged = NO; + @try { + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wunguarded-availability" + bridged = [KBURLOpenBridge openURLViaResponder:scheme from:self]; + #pragma clang diagnostic pop + } @catch (__unused NSException *e) { bridged = NO; } + + if (!bridged) { // 两条路都失败:大概率未开完全访问或宿主拦截。统一交由 Manager 引导。 dispatch_async(dispatch_get_main_queue(), ^{ [[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self]; }); } @@ -235,6 +256,8 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; // - 无论允许/拒绝,都把本次 changeCount 记为已处理,避免一直重复询问。 - (void)startPasteboardMonitor { + // 未开启“完全访问”时不做自动读取,避免宿主/系统拒绝并刷错误日志 + if (![[KBFullAccessManager shared] hasFullAccess]) return; if (self.pasteboardTimer) return; __weak typeof(self) weakSelf = self; self.pasteboardTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) { @@ -259,24 +282,30 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId"; - (void)didMoveToWindow { [super didMoveToWindow]; - if (self.window && !self.isHidden) { - [self startPasteboardMonitor]; - } else { - [self stopPasteboardMonitor]; - } + [self kb_refreshPasteboardMonitor]; } - (void)setHidden:(BOOL)hidden { BOOL wasHidden = self.isHidden; [super setHidden:hidden]; if (wasHidden != hidden) { - if (!hidden && self.window) { - [self startPasteboardMonitor]; - } else { - [self stopPasteboardMonitor]; - } + [self kb_refreshPasteboardMonitor]; } } + +// 根据窗口可见性与完全访问状态,统一启停粘贴板监控 +- (void)kb_refreshPasteboardMonitor { + BOOL visible = (self.window && !self.isHidden); + if (visible && [[KBFullAccessManager shared] hasFullAccess]) { + [self startPasteboardMonitor]; + } else { + [self stopPasteboardMonitor]; + } +} + +- (void)kb_fullAccessChanged { + dispatch_async(dispatch_get_main_queue(), ^{ [self kb_refreshPasteboardMonitor]; }); +} - (void)onTapDelete { NSLog(@"点击:删除"); UIInputViewController *ivc = KBFindInputViewController(self); diff --git a/CustomKeyboard/View/KBKeyboardView.m b/CustomKeyboard/View/KBKeyboardView.m index 7fd0593..8c3fce9 100644 --- a/CustomKeyboard/View/KBKeyboardView.m +++ b/CustomKeyboard/View/KBKeyboardView.m @@ -264,6 +264,9 @@ if (firstChar) { for (KBKeyButton *b in row.subviews) { if (![b isKindOfClass:[KBKeyButton class]]) continue; + // 当本行没有字符键时,firstChar 可能是一个“特殊键”, + // 避免对基准按钮自身添加 self == self * k 的无效约束 + if (b == firstChar) continue; if (b.key.type == KBKeyTypeCharacter) continue; CGFloat multiplier = 1.5; if (b.key.type == KBKeyTypeSpace) multiplier = 4.0; diff --git a/Shared/KBConfig.h b/Shared/KBConfig.h index 8a79c94..d4e343d 100644 --- a/Shared/KBConfig.h +++ b/Shared/KBConfig.h @@ -15,7 +15,7 @@ // Universal Links 通用链接 #ifndef KB_UL_BASE -#define KB_UL_BASE @"https://your.domain/ul" +#define KB_UL_BASE @"https://app.tknb.net/ul" #endif #define KB_UL_LOGIN KB_UL_BASE @"/login" @@ -37,6 +37,14 @@ #define KB_KEYBOARD_EXTENSION_BUNDLE_ID @"com.loveKey.nyx.CustomKeyboard" #endif +// --- 应用自定义 Scheme --- +// 主 App 在 Info.plist 中注册的 URL Scheme,用于从键盘扩展唤起容器 App。 +// 注意:AppDelegate 中对 scheme 做了小写化比较(kbkeyboardappextension),iOS 对大小写不敏感; +// 这里统一通过宏引用,避免出现与 App 端不一致的字符串。 +#ifndef KB_APP_SCHEME +#define KB_APP_SCHEME @"kbkeyboardAppExtension" +#endif + // --- 常用宏 --- // 弱引用 self(在 block 中避免循环引用):使用处直接写 KBWeakSelf; #ifndef KBWeakSelf diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 3cd1f21..25d3975 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 0459D1B42EBA284C00F2D189 /* KBSkinCenterVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */; }; 0459D1B72EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; }; 0459D1B82EBA287900F2D189 /* KBSkinManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0459D1B62EBA287900F2D189 /* KBSkinManager.m */; }; + 0477BD952EBAFF4E0055D639 /* KBURLOpenBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 0477BD932EBAFF4E0055D639 /* KBURLOpenBridge.m */; }; 04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97082EB31B14007BD342 /* KBHUD.m */; }; 04A9FE132EB4D0D20020DB6D /* KBFullAccessManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */; }; 04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE152EB873C80020DB6D /* UIViewController+Extension.m */; }; @@ -94,6 +95,8 @@ 0459D1B32EBA284C00F2D189 /* KBSkinCenterVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinCenterVC.m; sourceTree = ""; }; 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 = ""; }; + 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 = ""; }; 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 = ""; }; @@ -217,6 +220,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0477BD942EBAFF4E0055D639 /* Utils */ = { + isa = PBXGroup; + children = ( + 0477BD922EBAFF4E0055D639 /* KBURLOpenBridge.h */, + 0477BD932EBAFF4E0055D639 /* KBURLOpenBridge.m */, + ); + path = Utils; + sourceTree = ""; + }; 04A9FE122EB4D0D20020DB6D /* Manager */ = { isa = PBXGroup; children = ( @@ -257,6 +269,7 @@ 04C6EAD72EAF870B0089C901 /* CustomKeyboard */ = { isa = PBXGroup; children = ( + 0477BD942EBAFF4E0055D639 /* Utils */, 04A9FE122EB4D0D20020DB6D /* Manager */, 04FC95662EB0546C007BD342 /* Model */, 04C6EADA2EAF8C7B0089C901 /* View */, @@ -842,6 +855,7 @@ A1B2C4002EB4A0A100000003 /* KBAuthManager.m in Sources */, 04A9FE132EB4D0D20020DB6D /* KBFullAccessManager.m in Sources */, A1B2C4202EB4B7A100000001 /* KBKeyboardPermissionManager.m in Sources */, + 0477BD952EBAFF4E0055D639 /* KBURLOpenBridge.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1005,7 +1019,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = keyBoard/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "YOLO输入法"; - INFOPLIST_KEY_CFBundleURLTypes = "{\n CFBundleURLName = \"com.loveKey.nyx.keyboard\";\n CFBundleURLSchemes = (\n kbkeyboard\n );\n}"; + INFOPLIST_KEY_CFBundleURLTypes = "{\n CFBundleURLName = \"com.loveKey.nyx.keyboard\";\n CFBundleURLSchemes = (\n kbkeyboardAppExtension\n );\n}"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; @@ -1044,7 +1058,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = keyBoard/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "YOLO输入法"; - INFOPLIST_KEY_CFBundleURLTypes = "{\n CFBundleURLName = \"com.loveKey.nyx.keyboard\";\n CFBundleURLSchemes = (\n kbkeyboard\n );\n}"; + INFOPLIST_KEY_CFBundleURLTypes = "{\n CFBundleURLName = \"com.loveKey.nyx.keyboard\";\n CFBundleURLSchemes = (\n kbkeyboardAppExtension\n );\n}"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UIMainStoryboardFile = Main; diff --git a/keyBoard.xcodeproj/xcshareddata/xcschemes/keyBoard.xcscheme b/keyBoard.xcodeproj/xcshareddata/xcschemes/keyBoard.xcscheme index 4b87720..5988250 100644 --- a/keyBoard.xcodeproj/xcshareddata/xcschemes/keyBoard.xcscheme +++ b/keyBoard.xcodeproj/xcshareddata/xcschemes/keyBoard.xcscheme @@ -1,7 +1,7 @@ + version = "1.3"> @@ -27,8 +27,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - + + @@ -87,4 +87,3 @@ revealArchiveInOrganizer = "YES"> - diff --git a/keyBoard/AppDelegate.m b/keyBoard/AppDelegate.m index 31b7514..1d9511b 100644 --- a/keyBoard/AppDelegate.m +++ b/keyBoard/AppDelegate.m @@ -61,10 +61,25 @@ static NSString * const kKBKeyboardExtensionBundleId = @"com.loveKey.nyx.CustomK #pragma mark - Deep Link +- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler { + if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { + NSURL *url = userActivity.webpageURL; + if (!url) return NO; + NSString *host = url.host.lowercaseString ?: @""; + if ([host hasSuffix:@"app.tknb.net"]) { + NSString *path = url.path.lowercaseString ?: @""; + if ([path hasPrefix:@"/ul/settings"]) { [self kb_openAppSettings]; return YES; } + if ([path hasPrefix:@"/ul/login"]) { [self kb_presentLoginSheetIfNeeded]; return YES; } + } + } + return NO; +} + // iOS 9+ - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { if (!url) return NO; - if ([[url.scheme lowercaseString] isEqualToString:@"kbkeyboard"]) { + // 注意:已对 scheme 做了小写化,比较值也应为小写 + if ([[url.scheme lowercaseString] isEqualToString:@"kbkeyboardappextension"]) { NSString *urlHost = url.host ?: @""; NSString *host = [urlHost lowercaseString]; if ([host isEqualToString:@"login"]) { // kbkeyboard://login diff --git a/keyBoard/Info.plist b/keyBoard/Info.plist index 0bbab3d..c798311 100644 --- a/keyBoard/Info.plist +++ b/keyBoard/Info.plist @@ -5,11 +5,13 @@ CFBundleURLTypes + CFBundleTypeRole + Editor CFBundleURLName com.loveKey.nyx.keyboard CFBundleURLSchemes - kbkeyboard + kbkeyboardAppExtension diff --git a/keyBoard/KeyBoardPrefixHeader.pch b/keyBoard/KeyBoardPrefixHeader.pch index b8caeb7..f229331 100644 --- a/keyBoard/KeyBoardPrefixHeader.pch +++ b/keyBoard/KeyBoardPrefixHeader.pch @@ -35,7 +35,7 @@ //-----------------------------------------------宏定义全局----------------------------------------------------------/ // 通用链接(Universal Links)统一配置 // 仅需修改这里的域名/前缀,工程内所有使用 UL 的地方都会同步。 -#define KB_UL_BASE @"https://your.domain/ul" // 替换为你的真实域名与前缀路径 +#define KB_UL_BASE @"https://app.tknb.net/ul" // 与 Associated Domains 一致 #define KB_UL_LOGIN KB_UL_BASE @"/login" #define KB_UL_SETTINGS KB_UL_BASE @"/settings"