diff --git a/_spm/checkouts/swift-collections b/_spm/checkouts/swift-collections new file mode 160000 index 0000000..7b847a3 --- /dev/null +++ b/_spm/checkouts/swift-collections @@ -0,0 +1 @@ +Subproject commit 7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e diff --git a/keyBoard/Class/AiTalk/V/Comment/KBAICommentInputView.m b/keyBoard/Class/AiTalk/V/Comment/KBAICommentInputView.m index bef6e48..9a65361 100644 --- a/keyBoard/Class/AiTalk/V/Comment/KBAICommentInputView.m +++ b/keyBoard/Class/AiTalk/V/Comment/KBAICommentInputView.m @@ -92,7 +92,9 @@ } - (void)showKeyboard { - [self.textField becomeFirstResponder]; + BOOL before = self.textField.isFirstResponder; + BOOL become = [self.textField becomeFirstResponder]; + NSLog(@"[KBAICommentInputView] showKeyboard before=%d become=%d after=%d", before, become, self.textField.isFirstResponder); } #pragma mark - Actions @@ -129,11 +131,17 @@ } - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { + NSLog(@"[KBAICommentInputView] textFieldShouldBeginEditing"); [self updatePlaceholderVisibility]; return YES; } +- (void)textFieldDidBeginEditing:(UITextField *)textField { + NSLog(@"[KBAICommentInputView] textFieldDidBeginEditing firstResponder=%d", textField.isFirstResponder); +} + - (void)textFieldDidEndEditing:(UITextField *)textField { + NSLog(@"[KBAICommentInputView] textFieldDidEndEditing"); [self updatePlaceholderVisibility]; } diff --git a/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m b/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m index a8f8da3..b685fbd 100644 --- a/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m +++ b/keyBoard/Class/AiTalk/VC/KBAIHomeVC.m @@ -167,6 +167,8 @@ static NSString * const KBAISelectedPersonaIdKey = @"KBAISelectedPersonaId"; - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; + [self kb_syncTextInputStateIfNeeded]; + [self kb_logInputLayoutWithTag:@"viewDidAppear"]; KBPersonaChatCell *cell = [self currentPersonaCell]; if (cell) { [cell onBecameCurrentPersonaCell]; @@ -175,6 +177,17 @@ static NSString * const KBAISelectedPersonaIdKey = @"KBAISelectedPersonaId"; - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; + // 离开页面时结束编辑并重置底部输入状态,避免返回时出现 hidden/firstResponder 错位 + [self.view endEditing:YES]; + self.voiceInputKeyboardActive = NO; + self.currentKeyboardHeight = 0.0; + self.isTextInputMode = NO; + self.commentInputView.hidden = YES; + [self.commentInputBottomConstraint setOffset:100]; + self.voiceInputBar.hidden = NO; + [self.voiceInputBarBottomConstraint setOffset:-self.baseInputBarBottomSpacing]; + [self.view layoutIfNeeded]; + [self kb_logInputLayoutWithTag:@"viewWillDisappear"]; for (NSIndexPath *indexPath in self.collectionView.indexPathsForVisibleItems) { KBPersonaChatCell *cell = (KBPersonaChatCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; if (cell) { @@ -263,16 +276,26 @@ static NSString * const KBAISelectedPersonaIdKey = @"KBAISelectedPersonaId"; self.isTextInputMode = YES; self.voiceInputBar.hidden = YES; self.commentInputView.hidden = NO; + [self kb_logInputLayoutWithTag:@"showTextInputView-beforeAdjust"]; + // 键盘未弹起时先停在底部栏位置,随后由键盘通知动画抬起到键盘上方 + // 键盘已弹起时(如返回页面)直接使用当前键盘高度对齐 + CGFloat targetOffset = self.currentKeyboardHeight > 0.0 ? -self.currentKeyboardHeight : -self.baseInputBarBottomSpacing; + [self.commentInputBottomConstraint setOffset:targetOffset]; + [self.view layoutIfNeeded]; + [self kb_logInputLayoutWithTag:@"showTextInputView-afterAdjust"]; [self.commentInputView showKeyboard]; } /// 隐藏文本输入视图 - (void)hideTextInputView { + [self kb_logInputLayoutWithTag:@"hideTextInputView-before"]; self.isTextInputMode = NO; [self.view endEditing:YES]; [self.commentInputView clearText]; self.commentInputView.hidden = YES; + [self.commentInputBottomConstraint setOffset:100]; self.voiceInputBar.hidden = NO; + [self kb_logInputLayoutWithTag:@"hideTextInputView-after"]; } #pragma mark - 2:数据加载 @@ -570,12 +593,32 @@ static NSString * const KBAISelectedPersonaIdKey = @"KBAISelectedPersonaId"; - (void)setupKeyboardNotifications { [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(handleKeyboardWillChangeFrame:) + selector:@selector(handleKeyboardNotification:) name:UIKeyboardWillChangeFrameNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleKeyboardNotification:) + name:UIKeyboardDidChangeFrameNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleKeyboardNotification:) + name:UIKeyboardWillShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleKeyboardNotification:) + name:UIKeyboardDidShowNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleKeyboardNotification:) + name:UIKeyboardWillHideNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(handleKeyboardNotification:) + name:UIKeyboardDidHideNotification + object:nil]; } -- (void)handleKeyboardWillChangeFrame:(NSNotification *)notification { +- (void)handleKeyboardNotification:(NSNotification *)notification { NSDictionary *userInfo = notification.userInfo; CGRect endFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; @@ -583,17 +626,42 @@ static NSString * const KBAISelectedPersonaIdKey = @"KBAISelectedPersonaId"; CGRect convertedFrame = [self.view convertRect:endFrame fromView:nil]; CGFloat keyboardHeight = MAX(0.0, CGRectGetMaxY(self.view.bounds) - CGRectGetMinY(convertedFrame)); + BOOL shouldHandle = YES; + BOOL fromAllowedInput = [self kb_isKeyboardFromVoiceInputBar]; if (keyboardHeight > 0.0) { - if (![self kb_isKeyboardFromVoiceInputBar]) { - return; + // 文本输入模式优先跟随键盘,避免返回页面后 firstResponder 瞬时不稳定导致被误过滤 + if (!self.isTextInputMode && !fromAllowedInput) { + shouldHandle = NO; } self.voiceInputKeyboardActive = YES; } else { - if (!self.voiceInputKeyboardActive) { - return; + if (!self.voiceInputKeyboardActive && !self.isTextInputMode) { + shouldHandle = NO; } self.voiceInputKeyboardActive = NO; + } + + [self kb_logKeyboardNotification:notification + keyboardHeight:keyboardHeight + convertedFrame:convertedFrame + fromAllowedInput:fromAllowedInput + shouldHandle:shouldHandle]; + if (!shouldHandle) { + return; + } + + UIView *firstResponder = [self kb_findFirstResponderInView:self.view]; + BOOL firstInComment = firstResponder ? [firstResponder isDescendantOfView:self.commentInputView] : NO; + if (keyboardHeight > 0.0 && firstInComment && !self.isTextInputMode) { + // 防止出现「firstResponder 在 commentInput,但 textMode 仍为 NO」导致定位到底部 + self.isTextInputMode = YES; + self.voiceInputBar.hidden = YES; + self.commentInputView.hidden = NO; + [self kb_logInputLayoutWithTag:@"keyboardSync-forceTextMode"]; + } + + if (keyboardHeight <= 0.0) { // 键盘隐藏时,如果是文本输入模式,隐藏文本输入框并显示 VoiceInputBar if (self.isTextInputMode) { [self hideTextInputView]; @@ -625,7 +693,9 @@ static NSString * const KBAISelectedPersonaIdKey = @"KBAISelectedPersonaId"; animations:^{ [self.view layoutIfNeeded]; } - completion:nil]; + completion:^(BOOL finished) { + [self kb_logInputLayoutWithTag:@"handleKeyboardNotification-animComplete"]; + }]; } #pragma mark - 7:键盘收起 @@ -843,6 +913,7 @@ static NSString * const KBAISelectedPersonaIdKey = @"KBAISelectedPersonaId"; } - (void)chatLimitPopViewDidTapRecharge:(KBChatLimitPopView *)view { + NSLog(@"[KBAIHomeVC][Pay] chatLimitPopViewDidTapRecharge"); [self.chatLimitPopView dismiss]; if (![KBUserSessionManager shared].isLoggedIn) { [[KBUserSessionManager shared] goLoginVC]; @@ -961,6 +1032,74 @@ static NSString * const KBAISelectedPersonaIdKey = @"KBAISelectedPersonaId"; [self showPersonaSidebar]; } +#pragma mark - Debug Log + +- (void)kb_syncTextInputStateIfNeeded { + UIView *firstResponder = [self kb_findFirstResponderInView:self.view]; + BOOL firstInComment = firstResponder ? [firstResponder isDescendantOfView:self.commentInputView] : NO; + if (!firstInComment) { + return; + } + self.isTextInputMode = YES; + self.voiceInputBar.hidden = YES; + self.commentInputView.hidden = NO; + if (self.currentKeyboardHeight > 0.0) { + [self.commentInputBottomConstraint setOffset:-self.currentKeyboardHeight]; + [self.voiceInputBarBottomConstraint setOffset:-MAX(self.currentKeyboardHeight - 5.0, self.baseInputBarBottomSpacing)]; + } else { + [self.commentInputBottomConstraint setOffset:-self.baseInputBarBottomSpacing]; + } + [self.view layoutIfNeeded]; + [self kb_logInputLayoutWithTag:@"kb_syncTextInputStateIfNeeded"]; +} + +- (void)kb_logInputLayoutWithTag:(NSString *)tag { + [self.view layoutIfNeeded]; + UIView *firstResponder = [self kb_findFirstResponderInView:self.view]; + NSString *firstResponderInfo = firstResponder ? NSStringFromClass(firstResponder.class) : @"nil"; + NSLog(@"[KBAIHomeVC][Layout][%@] textMode=%d voiceHidden=%d commentHidden=%d currentKeyboardHeight=%.2f viewH=%.2f safeBottom=%.2f commentFrame=%@ voiceFrame=%@ firstResponder=%@", + tag ?: @"-", + self.isTextInputMode, + self.voiceInputBar.hidden, + self.commentInputView.hidden, + self.currentKeyboardHeight, + CGRectGetHeight(self.view.bounds), + self.view.safeAreaInsets.bottom, + NSStringFromCGRect(self.commentInputView.frame), + NSStringFromCGRect(self.voiceInputBar.frame), + firstResponderInfo); +} + +- (void)kb_logKeyboardNotification:(NSNotification *)notification + keyboardHeight:(CGFloat)keyboardHeight + convertedFrame:(CGRect)convertedFrame + fromAllowedInput:(BOOL)fromAllowedInput + shouldHandle:(BOOL)shouldHandle { + NSDictionary *userInfo = notification.userInfo; + CGRect endFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; + NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]; + NSInteger curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; + UIView *firstResponder = [self kb_findFirstResponderInView:self.view]; + NSString *firstResponderInfo = firstResponder ? NSStringFromClass(firstResponder.class) : @"nil"; + BOOL firstInComment = firstResponder ? [firstResponder isDescendantOfView:self.commentInputView] : NO; + BOOL firstInVoice = firstResponder ? [firstResponder isDescendantOfView:self.voiceInputBar] : NO; + NSLog(@"[KBAIHomeVC][Keyboard][%@] shouldHandle=%d textMode=%d voiceActive=%d fromAllowed=%d firstInComment=%d firstInVoice=%d firstResponder=%@ endFrame=%@ converted=%@ keyboardHeight=%.2f duration=%.2f curve=%ld viewWindow=%@", + notification.name, + shouldHandle, + self.isTextInputMode, + self.voiceInputKeyboardActive, + fromAllowedInput, + firstInComment, + firstInVoice, + firstResponderInfo, + NSStringFromCGRect(endFrame), + NSStringFromCGRect(convertedFrame), + keyboardHeight, + duration, + (long)curve, + self.view.window ? @"YES" : @"NO"); +} + - (void)showPersonaSidebar { if (!self.sidebarView) { CGFloat width = KB_SCREEN_WIDTH * 0.7;