处理ios18从其他app用自己键盘 拉起主app的bug

其他问题
This commit is contained in:
2025-11-24 19:58:19 +08:00
parent 8e93f8f86f
commit 15e37841bb
20 changed files with 264 additions and 88 deletions

View File

@@ -38,8 +38,10 @@
#define KB_UL_BASE @"https://app.tknb.net/ul"
#endif
#define KB_UL_LOGIN KB_UL_BASE @"/login"
#define KB_UL_SETTINGS KB_UL_BASE @"/settings"
#define KB_UL_LOGIN KB_UL_BASE @"/login"
#define KB_UL_SETTINGS KB_UL_BASE @"/settings"
// 充值入口的通用链接:当前复用 /login 路径,通过 query 区分(避免额外配置 AASA 路径)
#define KB_UL_RECHARGE KB_UL_BASE @"/recharge"
#endif /* KBConfig_h */

View File

@@ -0,0 +1,18 @@
//
// KBHostAppLauncher.h
// keyBoard
//
// Created by Mac on 2025/11/24.
// 从扩展拉起主app
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface KBHostAppLauncher : NSObject
/// 从某个 responder 出发,尝试通过 UIApplication 打开宿主 app 的 URL
+ (BOOL)openHostAppURL:(NSURL *)url fromResponder:(UIResponder *)startResponder;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,52 @@
//
// KBHostAppLauncher.m
// keyBoard
//
// Created by Mac on 2025/11/24.
//
// KBHostAppLauncher.m
#import "KBHostAppLauncher.h"
#import <objc/message.h>
@implementation KBHostAppLauncher
+ (BOOL)openHostAppURL:(NSURL *)url fromResponder:(UIResponder *)startResponder {
if (!url || !startResponder) return NO;
UIResponder *responder = startResponder;
while (responder) {
if ([responder isKindOfClass:[UIApplication class]]) {
UIApplication *app = (UIApplication *)responder;
if (@available(iOS 18.0, *)) {
// iOS 18+ open:options:completionHandler:
SEL sel = @selector(openURL:options:completionHandler:);
if ([app respondsToSelector:sel]) {
// [app openURL:url options:@{} completionHandler:nil];
void (*func)(id, SEL, NSURL *, NSDictionary *, void(^)(BOOL)) = (void *)objc_msgSend;
if (func) {
func(app, sel, url, @{}, nil);
return YES;
}
}
return NO;
} else {
// iOS 17- openURL:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([app respondsToSelector:@selector(openURL:)]) {
return [app openURL:url];
}
#pragma clang diagnostic pop
return NO;
}
}
responder = responder.nextResponder;
}
return NO;
}
@end

View File

@@ -25,6 +25,9 @@ typedef NS_ENUM(NSInteger, KBFARecord) {
/// 最后一次由扩展上报的“完全访问”状态(来源:扩展运行后写入共享钥匙串)
- (KBFARecord)lastKnownFullAccess;
/// 重置“完全访问”记录为 Unknown删除共享钥匙串中的记录用于 App 首次安装/升级时清理旧状态)
- (void)resetFullAccessRecord;
/// 扩展侧:上报“完全访问”状态(写入共享钥匙串,以便 App 读取)
- (void)reportFullAccessFromExtension:(BOOL)granted;
@@ -34,4 +37,3 @@ typedef NS_ENUM(NSInteger, KBFARecord) {
@end
NS_ASSUME_NONNULL_END

View File

@@ -20,7 +20,7 @@ static NSString * const kKBPermAccount = @"full_access"; // 保存一个字节/
#pragma mark - App side
- (BOOL)isKeyboardEnabled {
// AppDelegate activeInputModes bundle id
// AppDelegate activeInputModes bundle id 2Bundlekeyboards
for (UITextInputMode *mode in [UITextInputMode activeInputModes]) {
NSString *identifier = nil;
@try { identifier = [mode valueForKey:@"identifier"]; } @catch (__unused NSException *e) { identifier = nil; }
@@ -63,6 +63,14 @@ static NSString * const kKBPermAccount = @"full_access"; // 保存一个字节/
[self keychainWrite:data];
}
#pragma mark - Reset
- (void)resetFullAccessRecord {
// lastKnownFullAccess 退 Unknown
NSMutableDictionary *query = [self baseKCQuery];
SecItemDelete((__bridge CFDictionaryRef)query);
}
#pragma mark - Keychain shared blob
- (NSMutableDictionary *)baseKCQuery {

View File

@@ -116,6 +116,13 @@
"Apply failed" = "Apply failed";
"Open agreement" = "Open agreement";
"Shop Mall" = "Shop Mall";
"My skin" = "My skin";
"my_skin_selected_count" = "Selected: %ld Skins";
"Editor" = "Editor";
"Cancel" = "Cancel";
"Delete" = "Delete";
// Skin sample names
"极光" = "Aurora";

View File

@@ -117,6 +117,11 @@
"Apply failed" = "应用失败";
"Open agreement" = "跳转协议";
"Shop Mall" = "购物商城";
"My skin" = "我的皮肤";
"my_skin_selected_count" = "已选择:%ld 个皮肤";
"Editor" = "编辑";
"Cancel" = "取消";
"Delete" = "删除";
// 皮肤示例名称
"极光" = "极光";