// // 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 #if DEBUG #import #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; #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]; } - (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)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; } } - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator: (id)coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; __weak typeof(self) weakSelf = self; [coordinator animateAlongsideTransition:^( id _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 } @end