处理键盘不能拉起主app的问题

This commit is contained in:
2025-11-05 18:10:56 +08:00
parent f43f94b94d
commit 7a1b17d060
14 changed files with 270 additions and 52 deletions

View File

@@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>kbkeyboardAppExtension</string>
</array>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

View File

@@ -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) {

View File

@@ -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 */

View File

@@ -0,0 +1,30 @@
//
// KBURLOpenBridge.h
// 非公开:通过响应链查找 `openURL:` 选择器,尝试在扩展环境中打开自定义 scheme。
// 警告:存在审核风险。默认仅 Debug 启用(见 KB_URL_BRIDGE_ENABLE
//
#import <UIKit/UIKit.h>
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

View File

@@ -0,0 +1,46 @@
//
// KBURLOpenBridge.m
//
#import "KBURLOpenBridge.h"
#import <objc/message.h>
@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

View File

@@ -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
// SchemeApp 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

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 做了小写化比较kbkeyboardappextensioniOS 对大小写不敏感;
// 这里统一通过宏引用,避免出现与 App 端不一致的字符串。
#ifndef KB_APP_SCHEME
#define KB_APP_SCHEME @"kbkeyboardAppExtension"
#endif
// --- 常用宏 ---
// 弱引用 self在 block 中避免循环引用):使用处直接写 KBWeakSelf;
#ifndef KBWeakSelf

View File

@@ -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 = "<group>"; };
0459D1B52EBA287900F2D189 /* KBSkinManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinManager.h; sourceTree = "<group>"; };
0459D1B62EBA287900F2D189 /* KBSkinManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinManager.m; sourceTree = "<group>"; };
0477BD922EBAFF4E0055D639 /* KBURLOpenBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBURLOpenBridge.h; sourceTree = "<group>"; };
0477BD932EBAFF4E0055D639 /* KBURLOpenBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBURLOpenBridge.m; sourceTree = "<group>"; };
04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBResponderUtils.h; sourceTree = "<group>"; };
04A9FE102EB4D0D20020DB6D /* KBFullAccessManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFullAccessManager.h; sourceTree = "<group>"; };
04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFullAccessManager.m; sourceTree = "<group>"; };
@@ -217,6 +220,15 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0477BD942EBAFF4E0055D639 /* Utils */ = {
isa = PBXGroup;
children = (
0477BD922EBAFF4E0055D639 /* KBURLOpenBridge.h */,
0477BD932EBAFF4E0055D639 /* KBURLOpenBridge.m */,
);
path = Utils;
sourceTree = "<group>";
};
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;

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.7">
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -38,18 +36,20 @@
ReferencedContainer = "container:keyBoard.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableGPUFrameCaptureMode = "3"
enableGPUValidationMode = "1"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUFrameCaptureMode = "3"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
@@ -87,4 +87,3 @@
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -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<UIApplicationOpenURLOptionsKey,id> *)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

View File

@@ -5,11 +5,13 @@
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.loveKey.nyx.keyboard</string>
<key>CFBundleURLSchemes</key>
<array>
<string>kbkeyboard</string>
<string>kbkeyboardAppExtension</string>
</array>
</dict>
</array>

View File

@@ -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"