Files
keyboard/CustomKeyboard/KeyboardViewController.m
2026-03-02 09:19:06 +08:00

285 lines
10 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// KeyboardViewController.m
// CustomKeyboard
//
// Created by Mac on 2025/10/27.
//
#import "KeyboardViewController+Private.h"
#import "KBBackspaceUndoManager.h"
#import "KBChatLimitPopView.h"
#import "KBChatPanelView.h"
#import "KBFullAccessManager.h"
#import "KBFunctionView.h"
#import "KBInputBufferManager.h"
#import "KBKeyBoardMainView.h"
#import "KBKeyboardSubscriptionView.h"
#import "KBLocalizationManager.h"
#import "KBSkinManager.h"
#import "KBSuggestionEngine.h"
#import "KBKeyboardLayoutResolver.h"
#import <SDWebImage/SDWebImage.h>
#if DEBUG
#import <mach/mach.h>
#endif
#if DEBUG
static NSInteger sKBKeyboardVCAliveCount = 0;
static uint64_t KBPhysFootprintBytes(void) {
task_vm_info_data_t vmInfo;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t kr = task_info(mach_task_self(), TASK_VM_INFO,
(task_info_t)&vmInfo, &count);
if (kr != KERN_SUCCESS) {
return 0;
}
return (uint64_t)vmInfo.phys_footprint;
}
static NSString *KBFormatMB(uint64_t bytes) {
double mb = (double)bytes / 1024.0 / 1024.0;
return [NSString stringWithFormat:@"%.1fMB", mb];
}
#endif
@implementation KeyboardViewController
{
BOOL _kb_didTriggerLoginDeepLinkOnce;
NSString *_kb_lastLoadedProfileId; // 记录上次加载的 profileId
#if DEBUG
BOOL _kb_debugDidCountAlive;
#endif
}
- (void)viewDidLoad {
[super viewDidLoad];
#if DEBUG
if (!_kb_debugDidCountAlive) {
_kb_debugDidCountAlive = YES;
sKBKeyboardVCAliveCount += 1;
}
NSLog(@"[Keyboard] KeyboardViewController viewDidLoad alive=%ld self=%p mem=%@",
(long)sKBKeyboardVCAliveCount, self, KBFormatMB(KBPhysFootprintBytes()));
#endif
// 撤销删除是“上一段删除操作”的临时状态;键盘被系统回收/重建或跨页面回来时应当清空,避免误显示。
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self setupUI];
self.suggestionEngine = [KBSuggestionEngine shared];
self.currentWord = @"";
// 指定 HUD 的承载视图(扩展里无法取到 App 的 KeyWindow
[KBHUD setContainerView:self.view];
// 绑定完全访问管理器,便于统一感知和联动网络开关
[[KBFullAccessManager shared] bindInputController:self];
self.kb_fullAccessObserverToken = [[NSNotificationCenter defaultCenter]
addObserverForName:KBFullAccessChangedNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(__unused NSNotification *_Nonnull note){
// 如需,可在此刷新与完全访问相关的 UI
}];
// 皮肤变化时,立即应用
__weak typeof(self) weakSelf = self;
self.kb_skinObserverToken = [[NSNotificationCenter defaultCenter]
addObserverForName:KBSkinDidChangeNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(__unused NSNotification *_Nonnull note) {
__strong typeof(weakSelf) self = weakSelf;
if (!self) {
return;
}
[self kb_applyTheme];
}];
[self kb_applyTheme];
[self kb_registerDarwinSkinInstallObserver];
[self kb_consumePendingShopSkin];
[self kb_applyDefaultSkinIfNeeded];
// 监听 App Group 配置变化,动态切换键盘布局
[self kb_checkAndApplyLayoutIfNeeded];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// 扩展进程内存上限较小:在系统发出内存警告时主动清理可重建的缓存,降低被系统杀死概率。
self.kb_cachedGradientImage = nil;
[self.kb_defaultGradientLayer removeFromSuperlayer];
self.kb_defaultGradientLayer = nil;
[[KBSkinManager shared] clearRuntimeImageCaches];
[[SDImageCache sharedImageCache] clearMemory];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// FIX: iOS 26 键盘闪烁问题 —— 恢复键盘正确高度
// setupUI 中高度初始为 0防止系统预渲染快照闪烁此处恢复为实际键盘高度。
// 此时系统已准备好键盘滑入动画,恢复高度后键盘将正常从底部滑入。
CGFloat portraitWidth = [self kb_portraitWidth];
CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth];
if (self.kb_heightConstraint) {
self.kb_heightConstraint.constant = keyboardHeight;
}
// 进入/重新进入输入界面时,清理上一次会话残留的撤销状态与缓存,避免显示“撤销删除”但实际上已不可撤销。
[[KBBackspaceUndoManager shared] registerNonClearAction];
[[KBInputBufferManager shared] resetWithText:@""];
[[KBLocalizationManager shared] reloadFromSharedStorageIfNeeded];
// 键盘再次出现时,恢复 HUD 容器与主题viewDidDisappear 里可能已清理图片/缓存)。
[KBHUD setContainerView:self.view];
[self kb_ensureKeyBoardMainViewIfNeeded];
[self kb_applyTheme];
#if DEBUG
NSLog(@"[Keyboard] viewWillAppear self=%p mem=%@",
self, KBFormatMB(KBPhysFootprintBytes()));
#endif
// 注意:微信/QQ 等宿主的 documentContext 可能是“截断窗口”,这里只更新
// liveText不要把它当作全文 manualSnapshot。
[[KBInputBufferManager shared]
updateFromExternalContextBefore:self.textDocumentProxy
.documentContextBeforeInput
after:self.textDocumentProxy
.documentContextAfterInput];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[KBBackspaceUndoManager shared] registerNonClearAction];
[self kb_releaseMemoryWhenKeyboardHidden];
#if DEBUG
NSLog(@"[Keyboard] viewWillDisappear self=%p mem=%@",
self, KBFormatMB(KBPhysFootprintBytes()));
#endif
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// 再兜底一次,防止某些宿主只触发 willDisappear 而未触发 didDisappear。
[self kb_releaseMemoryWhenKeyboardHidden];
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
if (previousTraitCollection.userInterfaceStyle !=
self.traitCollection.userInterfaceStyle) {
self.kb_cachedGradientImage = nil;
[self kb_applyDefaultSkinIfNeeded];
}
}
}
- (void)textDidChange:(id<UITextInput>)textInput {
[super textDidChange:textInput];
[[KBInputBufferManager shared]
updateFromExternalContextBefore:self.textDocumentProxy
.documentContextBeforeInput
after:self.textDocumentProxy
.documentContextAfterInput];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// if (!_kb_didTriggerLoginDeepLinkOnce) {
// _kb_didTriggerLoginDeepLinkOnce = YES;
// // 仅在未登录时尝试拉起主App登录
// if (!KBAuthManager.shared.isLoggedIn) {
// [self kb_tryOpenContainerForLoginIfNeeded];
// }
// }
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// [self kb_updateKeyboardLayoutIfNeeded];
// 首次布局完成后显示,避免闪烁
if (self.contentView.hidden) {
self.contentView.hidden = NO;
}
if (self.kb_defaultGradientLayer) {
self.kb_defaultGradientLayer.frame = self.bgImageView.bounds;
}
// 每次布局时检查是否需要切换键盘布局
[self kb_checkAndApplyLayoutIfNeeded];
}
- (void)viewWillTransitionToSize:(CGSize)size
withTransitionCoordinator:
(id<UIViewControllerTransitionCoordinator>)coordinator {
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
__weak typeof(self) weakSelf = self;
[coordinator
animateAlongsideTransition:^(
id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[weakSelf kb_updateKeyboardLayoutIfNeeded];
}
completion:^(
__unused id<
UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
[weakSelf kb_updateKeyboardLayoutIfNeeded];
}];
}
- (void)dealloc {
if (self.kb_fullAccessObserverToken) {
[[NSNotificationCenter defaultCenter]
removeObserver:self.kb_fullAccessObserverToken];
self.kb_fullAccessObserverToken = nil;
}
if (self.kb_skinObserverToken) {
[[NSNotificationCenter defaultCenter]
removeObserver:self.kb_skinObserverToken];
self.kb_skinObserverToken = nil;
}
[self kb_unregisterDarwinSkinInstallObserver];
#if DEBUG
if (_kb_debugDidCountAlive) {
sKBKeyboardVCAliveCount -= 1;
}
NSLog(@"[Keyboard] KeyboardViewController dealloc alive=%ld self=%p mem=%@",
(long)sKBKeyboardVCAliveCount, self, KBFormatMB(KBPhysFootprintBytes()));
#endif
}
#pragma mark - Layout Switching
- (void)kb_checkAndApplyLayoutIfNeeded {
NSString *currentProfileId = [[KBKeyboardLayoutResolver sharedResolver] currentProfileId];
if (currentProfileId.length == 0) {
currentProfileId = @"en_US_qwerty"; // 默认回退
}
// 如果 profileId 没有变化,不需要重新加载
if ([currentProfileId isEqualToString:_kb_lastLoadedProfileId]) {
return;
}
NSLog(@"[KeyboardViewController] Detected profileId change: %@ -> %@", _kb_lastLoadedProfileId, currentProfileId);
_kb_lastLoadedProfileId = currentProfileId;
// 通知 KBKeyBoardMainView 切换布局
if (self.keyBoardMainView && [self.keyBoardMainView respondsToSelector:@selector(reloadLayoutWithProfileId:)]) {
[self.keyBoardMainView performSelector:@selector(reloadLayoutWithProfileId:) withObject:currentProfileId];
}
// 更新联想引擎类型
NSString *suggestionEngine = [[KBKeyboardLayoutResolver sharedResolver] suggestionEngineForProfileId:currentProfileId];
if (suggestionEngine.length > 0) {
[self kb_updateSuggestionEngineType:suggestionEngine];
}
}
- (void)kb_updateSuggestionEngineType:(NSString *)engineType {
// 根据 engineType 切换不同的联想引擎
// 例如latin, pinyin_traditional, pinyin_simplified, bopomofo
NSLog(@"[KeyboardViewController] Switching suggestion engine to: %@", engineType);
[[KBSuggestionEngine shared] setEngineTypeFromString:engineType];
}
@end