// // KeyboardViewController+UI.m // CustomKeyboard // // Created by Codex on 2026/02/22. // #import "KeyboardViewController+Private.h" #import "KBChatMessage.h" #import "KBChatPanelView.h" #import "KBFunctionView.h" #import "KBKeyBoardMainView.h" #import "KBKeyboardSubscriptionView.h" #import "KBSettingView.h" #import "Masonry.h" @implementation KeyboardViewController (UI) - (void)setupUI { self.view.translatesAutoresizingMaskIntoConstraints = NO; // 按“短边”宽度等比缩放,横屏保持竖屏布局比例 CGFloat portraitWidth = [self kb_portraitWidth]; CGFloat keyboardHeight = [self kb_keyboardHeightForWidth:portraitWidth]; CGFloat keyboardBaseHeight = [self kb_keyboardBaseHeightForWidth:portraitWidth]; CGFloat screenWidth = CGRectGetWidth([UIScreen mainScreen].bounds); // FIX: iOS 26 键盘闪烁问题 // iOS 26 在键盘滑入动画开始前,会对 self.view 做一次离屏预渲染快照(非实时 view), // 该快照会短暂显示在屏幕中间。如果此时 view 已有完整高度和内容,用户就会看到 // 键盘 UI 在屏幕中间闪现一帧,然后键盘才从底部正常滑入。 // 解决方案:初始高度设为 0,让系统快照时无内容可渲染; // 在 viewWillAppear: 中恢复正确高度,此时系统已准备好滑入动画。 // (iOS 18 及更早版本无此预渲染机制,不受影响) NSLayoutConstraint *h = [self.view.heightAnchor constraintEqualToConstant:0]; NSLayoutConstraint *w = [self.view.widthAnchor constraintEqualToConstant:screenWidth]; self.kb_heightConstraint = h; self.kb_widthConstraint = w; h.priority = UILayoutPriorityRequired; w.priority = UILayoutPriorityRequired; [NSLayoutConstraint activateConstraints:@[ h, w ]]; // 关闭 UIInputView 自适应(某些系统版本会尝试放大为全屏高度导致冲突) if ([self.view isKindOfClass:[UIInputView class]]) { UIInputView *iv = (UIInputView *)self.view; if ([iv respondsToSelector:@selector(setAllowsSelfSizing:)]) { iv.allowsSelfSizing = NO; } } // 内容容器:横屏时保持竖屏宽度,居中显示 [self.view addSubview:self.contentView]; [self.contentView mas_makeConstraints:^(MASConstraintMaker *make) { make.centerX.equalTo(self.view); make.bottom.equalTo(self.view); self.contentWidthConstraint = make.width.mas_equalTo(portraitWidth); self.contentHeightConstraint = make.height.mas_equalTo(keyboardHeight); }]; // 背景图铺底(仅在内容容器内) [self.contentView addSubview:self.bgImageView]; [self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.contentView); }]; [self.contentView addSubview:self.keyBoardMainView]; [self.keyBoardMainView mas_makeConstraints:^(MASConstraintMaker *make) { make.left.right.equalTo(self.contentView); make.bottom.equalTo(self.contentView); self.keyBoardMainHeightConstraint = make.height.mas_equalTo(keyboardBaseHeight); }]; // 初始隐藏,避免布局完成前闪烁 self.contentView.hidden = YES; } #pragma mark - Lazy - (nullable KBFunctionView *)kb_functionViewIfCreated { return _functionView; } - (UIView *)contentView { if (!_contentView) { _contentView = [[UIView alloc] init]; _contentView.backgroundColor = [UIColor clearColor]; } return _contentView; } - (UIImageView *)bgImageView { if (!_bgImageView) { _bgImageView = [[UIImageView alloc] init]; _bgImageView.contentMode = UIViewContentModeScaleAspectFill; _bgImageView.clipsToBounds = YES; } return _bgImageView; } - (KBKeyBoardMainView *)keyBoardMainView { if (!_keyBoardMainView) { _keyBoardMainView = [[KBKeyBoardMainView alloc] init]; _keyBoardMainView.delegate = self; } return _keyBoardMainView; } - (KBFunctionView *)functionView { if (!_functionView) { _functionView = [[KBFunctionView alloc] init]; _functionView.delegate = self; // 监听功能面板顶部Bar点击 } return _functionView; } - (KBSettingView *)settingView { if (!_settingView) { _settingView = [[KBSettingView alloc] init]; } return _settingView; } - (KBChatPanelView *)chatPanelView { if (!_chatPanelView) { NSLog(@"[Keyboard] ⚠️ chatPanelView 被创建!"); _chatPanelView = [[KBChatPanelView alloc] init]; _chatPanelView.delegate = self; } return _chatPanelView; } - (NSMutableArray *)chatMessages { if (!_chatMessages) { _chatMessages = [NSMutableArray array]; } return _chatMessages; } - (KBKeyboardSubscriptionView *)subscriptionView { if (!_subscriptionView) { _subscriptionView = [[KBKeyboardSubscriptionView alloc] init]; _subscriptionView.delegate = self; _subscriptionView.hidden = YES; _subscriptionView.alpha = 0.0; } return _subscriptionView; } @end