This commit is contained in:
2026-01-30 21:24:17 +08:00
parent 2ff8a7a4af
commit cc82396195
5 changed files with 222 additions and 45 deletions

View File

@@ -364,6 +364,39 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
scrollToBottom:(BOOL)scrollToBottom {
CGFloat oldContentHeight = self.tableView.contentSize.height;
CGFloat oldOffsetY = self.tableView.contentOffset.y;
KBAiChatMessage *anchorMessage = nil;
CGFloat anchorOffset = 0;
if (keepOffset) {
NSArray<NSIndexPath *> *visibleRows = self.tableView.indexPathsForVisibleRows;
if (visibleRows.count > 0) {
NSArray<NSIndexPath *> *sortedRows = [visibleRows sortedArrayUsingComparator:^NSComparisonResult(NSIndexPath *obj1, NSIndexPath *obj2) {
if (obj1.row < obj2.row) {
return NSOrderedAscending;
} else if (obj1.row > obj2.row) {
return NSOrderedDescending;
}
return NSOrderedSame;
}];
NSIndexPath *anchorIndexPath = nil;
for (NSIndexPath *indexPath in sortedRows) {
if (indexPath.row < self.messages.count) {
KBAiChatMessage *message = self.messages[indexPath.row];
if (message.type != KBAiChatMessageTypeTime) {
anchorIndexPath = indexPath;
break;
}
}
}
if (!anchorIndexPath) {
anchorIndexPath = sortedRows.firstObject;
}
if (anchorIndexPath && anchorIndexPath.row < self.messages.count) {
anchorMessage = self.messages[anchorIndexPath.row];
CGRect anchorRect = [self.tableView rectForRowAtIndexPath:anchorIndexPath];
anchorOffset = oldOffsetY - anchorRect.origin.y;
}
}
}
[self.messages removeAllObjects];
if (messages.count > 0) {
@@ -399,10 +432,18 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
NSLog(@"[KBChatTableView] ========== reloadWithMessages 结束 ==========");
if (keepOffset) {
CGFloat newContentHeight = self.tableView.contentSize.height;
CGFloat delta = newContentHeight - oldContentHeight;
CGFloat offsetY = oldOffsetY + delta;
// 使 offset
CGFloat offsetY = oldOffsetY;
if (anchorMessage) {
NSInteger newIndex = [self.messages indexOfObjectIdenticalTo:anchorMessage];
if (newIndex != NSNotFound) {
CGRect newRect = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:newIndex inSection:0]];
offsetY = newRect.origin.y + anchorOffset;
}
} else {
CGFloat newContentHeight = self.tableView.contentSize.height;
CGFloat delta = newContentHeight - oldContentHeight;
offsetY = oldOffsetY + delta;
}
[self.tableView setContentOffset:CGPointMake(0, offsetY) animated:NO];
return;
}
@@ -439,6 +480,131 @@ static const NSTimeInterval kTimestampInterval = 5 * 60; // 5 分钟
[self.messages addObject:message];
}
- (NSInteger)firstVisibleNonTimeRowExcludingMessage:(KBAiChatMessage *)excludedMessage {
NSArray<NSIndexPath *> *visible = self.tableView.indexPathsForVisibleRows;
if (visible.count == 0) { return NSNotFound; }
NSArray<NSIndexPath *> *sorted = [visible sortedArrayUsingComparator:^NSComparisonResult(NSIndexPath * _Nonnull obj1, NSIndexPath * _Nonnull obj2) {
if (obj1.row < obj2.row) return NSOrderedAscending;
if (obj1.row > obj2.row) return NSOrderedDescending;
return NSOrderedSame;
}];
for (NSIndexPath *ip in sorted) {
if (ip.row < self.messages.count) {
KBAiChatMessage *msg = self.messages[ip.row];
if (msg.type != KBAiChatMessageTypeTime && msg != excludedMessage) {
return ip.row;
}
}
}
return sorted.firstObject.row;
}
- (CGFloat)offsetForRow:(NSInteger)row {
if (row == NSNotFound) { return self.tableView.contentOffset.y; }
CGRect rect = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:0]];
return self.tableView.contentOffset.y - rect.origin.y;
}
- (void)restoreOffsetWithMessage:(KBAiChatMessage *)message anchorOffset:(CGFloat)anchorOffset fallbackDelta:(CGFloat)fallbackDelta {
CGFloat offsetY = self.tableView.contentOffset.y + fallbackDelta;
if (message) {
NSInteger newIndex = [self.messages indexOfObjectIdenticalTo:message];
if (newIndex != NSNotFound) {
CGRect newRect = [self.tableView rectForRowAtIndexPath:[NSIndexPath indexPathForRow:newIndex inSection:0]];
offsetY = newRect.origin.y + anchorOffset;
}
}
[self.tableView setContentOffset:CGPointMake(0, offsetY) animated:NO];
}
- (BOOL)shouldInsertTimestampForMessage:(KBAiChatMessage *)message
inMessages:(NSArray<KBAiChatMessage *> *)messages {
if (messages.count == 0) {
return YES;
}
KBAiChatMessage *lastMessage = nil;
for (NSInteger i = messages.count - 1; i >= 0; i--) {
KBAiChatMessage *msg = messages[i];
if (msg.type != KBAiChatMessageTypeTime) {
lastMessage = msg;
break;
}
}
if (!lastMessage) {
return YES;
}
NSTimeInterval interval = [message.timestamp timeIntervalSinceDate:lastMessage.timestamp];
if (interval >= kTimestampInterval) {
return YES;
}
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *lastComponents = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear
fromDate:lastMessage.timestamp];
NSDateComponents *currentComponents = [calendar components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear
fromDate:message.timestamp];
return ![lastComponents isEqual:currentComponents];
}
- (NSArray<KBAiChatMessage *> *)messagesByInsertingTimestamps:(NSArray<KBAiChatMessage *> *)messages {
NSMutableArray<KBAiChatMessage *> *result = [NSMutableArray array];
for (KBAiChatMessage *msg in messages) {
if ([self shouldInsertTimestampForMessage:msg inMessages:result]) {
KBAiChatMessage *timeMessage = [KBAiChatMessage timeMessageWithTimestamp:msg.timestamp];
[result addObject:timeMessage];
}
[result addObject:msg];
}
return result;
}
- (void)prependHistoryMessages:(NSArray<KBAiChatMessage *> *)messages
openingMessage:(nullable KBAiChatMessage *)openingMessage {
CGFloat oldContentHeight = self.tableView.contentSize.height;
NSInteger anchorRow = [self firstVisibleNonTimeRowExcludingMessage:openingMessage];
KBAiChatMessage *anchorMsg = nil;
CGFloat anchorOffset = 0;
if (anchorRow != NSNotFound && anchorRow < self.messages.count) {
anchorMsg = self.messages[anchorRow];
anchorOffset = [self offsetForRow:anchorRow];
}
NSArray<KBAiChatMessage *> *toInsert = [self messagesByInsertingTimestamps:messages];
[UIView performWithoutAnimation:^{
if (toInsert.count > 0) {
NSInteger insertIndex = 0;
if (openingMessage) {
NSInteger openingIndex = [self.messages indexOfObjectIdenticalTo:openingMessage];
if (openingIndex != NSNotFound) {
insertIndex = openingIndex + 1;
}
}
NSRange range = NSMakeRange(insertIndex, toInsert.count);
NSIndexSet *set = [NSIndexSet indexSetWithIndexesInRange:range];
[self.messages insertObjects:toInsert atIndexes:set];
NSMutableArray<NSIndexPath *> *indexPaths = [NSMutableArray array];
for (NSInteger i = 0; i < toInsert.count; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:insertIndex + i inSection:0]];
}
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
[self.tableView layoutIfNeeded];
CGFloat newContentHeight = self.tableView.contentSize.height;
CGFloat delta = newContentHeight - oldContentHeight;
[self restoreOffsetWithMessage:anchorMsg anchorOffset:anchorOffset fallbackDelta:delta];
}];
}
///
- (BOOL)shouldInsertTimestampForMessage:(KBAiChatMessage *)message {
//