616 lines
20 KiB
Objective-C
616 lines
20 KiB
Objective-C
//
|
||
// KBAICommentView.m
|
||
// keyBoard
|
||
//
|
||
// Created by Mac on 2026/1/16.
|
||
//
|
||
|
||
#import "KBAICommentView.h"
|
||
#import "KBAICommentFooterView.h"
|
||
#import "KBAICommentHeaderView.h"
|
||
#import "KBAICommentInputView.h"
|
||
#import "KBAICommentModel.h"
|
||
#import "KBAIReplyCell.h"
|
||
#import "KBAIReplyModel.h"
|
||
#import <MJExtension/MJExtension.h>
|
||
#import <Masonry/Masonry.h>
|
||
|
||
static NSString *const kCommentHeaderIdentifier = @"CommentHeader";
|
||
static NSString *const kReplyCellIdentifier = @"ReplyCell";
|
||
static NSString *const kCommentFooterIdentifier = @"CommentFooter";
|
||
|
||
/// 每次展开的回复数量
|
||
static NSInteger const kRepliesLoadCount = 3;
|
||
|
||
@interface KBAICommentView () <UITableViewDataSource, UITableViewDelegate>
|
||
|
||
@property(nonatomic, strong) UIView *headerView;
|
||
@property(nonatomic, strong) UILabel *titleLabel;
|
||
@property(nonatomic, strong) UIButton *closeButton;
|
||
@property(nonatomic, strong) UITableView *tableView;
|
||
@property(nonatomic, strong) KBAICommentInputView *inputView;
|
||
|
||
@property(nonatomic, strong) NSMutableArray<KBAICommentModel *> *comments;
|
||
@property(nonatomic, assign) NSInteger totalCommentCount;
|
||
|
||
/// 键盘高度
|
||
@property(nonatomic, assign) CGFloat keyboardHeight;
|
||
/// 输入框底部约束
|
||
@property(nonatomic, strong) MASConstraint *inputBottomConstraint;
|
||
|
||
@end
|
||
|
||
@implementation KBAICommentView
|
||
|
||
#pragma mark - Lifecycle
|
||
|
||
- (instancetype)initWithFrame:(CGRect)frame {
|
||
self = [super initWithFrame:frame];
|
||
if (self) {
|
||
self.comments = [NSMutableArray array];
|
||
[self setupUI];
|
||
[self setupKeyboardObservers];
|
||
[self loadComments];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (void)dealloc {
|
||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||
}
|
||
|
||
#pragma mark - UI Setup
|
||
|
||
- (void)setupUI {
|
||
self.backgroundColor = [UIColor whiteColor];
|
||
self.layer.cornerRadius = 12;
|
||
self.layer.maskedCorners = kCALayerMinXMinYCorner | kCALayerMaxXMinYCorner;
|
||
self.clipsToBounds = YES;
|
||
|
||
[self addSubview:self.headerView];
|
||
[self.headerView addSubview:self.titleLabel];
|
||
[self.headerView addSubview:self.closeButton];
|
||
[self addSubview:self.tableView];
|
||
[self addSubview:self.inputView];
|
||
|
||
[self.headerView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.top.left.right.equalTo(self);
|
||
make.height.mas_equalTo(50);
|
||
}];
|
||
|
||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.center.equalTo(self.headerView);
|
||
}];
|
||
|
||
[self.closeButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.right.equalTo(self.headerView).offset(-16);
|
||
make.centerY.equalTo(self.headerView);
|
||
make.width.height.mas_equalTo(30);
|
||
}];
|
||
|
||
[self.inputView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.left.right.equalTo(self);
|
||
make.height.mas_equalTo(50);
|
||
self.inputBottomConstraint = make.bottom.equalTo(self);
|
||
}];
|
||
|
||
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||
make.top.equalTo(self.headerView.mas_bottom);
|
||
make.left.right.equalTo(self);
|
||
make.bottom.equalTo(self.inputView.mas_top);
|
||
}];
|
||
}
|
||
|
||
#pragma mark - Keyboard Observers
|
||
|
||
- (void)setupKeyboardObservers {
|
||
[[NSNotificationCenter defaultCenter]
|
||
addObserver:self
|
||
selector:@selector(keyboardWillShow:)
|
||
name:UIKeyboardWillShowNotification
|
||
object:nil];
|
||
[[NSNotificationCenter defaultCenter]
|
||
addObserver:self
|
||
selector:@selector(keyboardWillHide:)
|
||
name:UIKeyboardWillHideNotification
|
||
object:nil];
|
||
}
|
||
|
||
- (void)keyboardWillShow:(NSNotification *)notification {
|
||
CGRect keyboardFrame =
|
||
[notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||
NSTimeInterval duration =
|
||
[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]
|
||
doubleValue];
|
||
|
||
self.keyboardHeight = keyboardFrame.size.height;
|
||
|
||
[self.inputBottomConstraint uninstall];
|
||
[self.inputView mas_updateConstraints:^(MASConstraintMaker *make) {
|
||
self.inputBottomConstraint =
|
||
make.bottom.equalTo(self).offset(-self.keyboardHeight);
|
||
}];
|
||
|
||
[UIView animateWithDuration:duration
|
||
animations:^{
|
||
[self layoutIfNeeded];
|
||
}];
|
||
}
|
||
|
||
- (void)keyboardWillHide:(NSNotification *)notification {
|
||
NSTimeInterval duration =
|
||
[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey]
|
||
doubleValue];
|
||
|
||
self.keyboardHeight = 0;
|
||
|
||
[self.inputBottomConstraint uninstall];
|
||
[self.inputView mas_updateConstraints:^(MASConstraintMaker *make) {
|
||
self.inputBottomConstraint = make.bottom.equalTo(self);
|
||
}];
|
||
|
||
[UIView animateWithDuration:duration
|
||
animations:^{
|
||
[self layoutIfNeeded];
|
||
}];
|
||
}
|
||
|
||
#pragma mark - Data Loading
|
||
|
||
- (void)loadComments {
|
||
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"comments_mock"
|
||
ofType:@"json"];
|
||
if (!filePath) {
|
||
NSLog(@"[KBAICommentView] comments_mock.json not found");
|
||
return;
|
||
}
|
||
|
||
NSData *data = [NSData dataWithContentsOfFile:filePath];
|
||
if (!data) {
|
||
NSLog(@"[KBAICommentView] Failed to read comments_mock.json");
|
||
return;
|
||
}
|
||
|
||
NSError *error;
|
||
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
|
||
options:0
|
||
error:&error];
|
||
if (error) {
|
||
NSLog(@"[KBAICommentView] JSON parse error: %@", error);
|
||
return;
|
||
}
|
||
|
||
self.totalCommentCount = [json[@"totalCount"] integerValue];
|
||
NSArray *commentsArray = json[@"comments"];
|
||
|
||
[self.comments removeAllObjects];
|
||
for (NSDictionary *dict in commentsArray) {
|
||
KBAICommentModel *comment = [KBAICommentModel mj_objectWithKeyValues:dict];
|
||
[self.comments addObject:comment];
|
||
}
|
||
|
||
[self updateTitle];
|
||
[self.tableView reloadData];
|
||
}
|
||
|
||
- (void)updateTitle {
|
||
NSString *countText;
|
||
if (self.totalCommentCount >= 10000) {
|
||
countText = [NSString
|
||
stringWithFormat:@"%.1fw条评论", self.totalCommentCount / 10000.0];
|
||
} else {
|
||
countText =
|
||
[NSString stringWithFormat:@"%ld条评论", (long)self.totalCommentCount];
|
||
}
|
||
self.titleLabel.text = countText;
|
||
}
|
||
|
||
#pragma mark - UITableViewDataSource
|
||
|
||
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
|
||
return self.comments.count;
|
||
}
|
||
|
||
- (NSInteger)tableView:(UITableView *)tableView
|
||
numberOfRowsInSection:(NSInteger)section {
|
||
KBAICommentModel *comment = self.comments[section];
|
||
return comment.displayedReplies.count;
|
||
}
|
||
|
||
- (UITableViewCell *)tableView:(UITableView *)tableView
|
||
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||
KBAIReplyCell *cell =
|
||
[tableView dequeueReusableCellWithIdentifier:kReplyCellIdentifier
|
||
forIndexPath:indexPath];
|
||
|
||
KBAICommentModel *comment = self.comments[indexPath.section];
|
||
KBAIReplyModel *reply = comment.displayedReplies[indexPath.row];
|
||
[cell configureWithReply:reply];
|
||
|
||
__weak typeof(self) weakSelf = self;
|
||
cell.onLikeAction = ^{
|
||
// TODO: 处理点赞逻辑
|
||
reply.isLiked = !reply.isLiked;
|
||
reply.likeCount += reply.isLiked ? 1 : -1;
|
||
[weakSelf.tableView reloadRowsAtIndexPaths:@[ indexPath ]
|
||
withRowAnimation:UITableViewRowAnimationNone];
|
||
};
|
||
|
||
return cell;
|
||
}
|
||
|
||
#pragma mark - UITableViewDelegate
|
||
|
||
- (UIView *)tableView:(UITableView *)tableView
|
||
viewForHeaderInSection:(NSInteger)section {
|
||
KBAICommentHeaderView *header = [tableView
|
||
dequeueReusableHeaderFooterViewWithIdentifier:kCommentHeaderIdentifier];
|
||
|
||
KBAICommentModel *comment = self.comments[section];
|
||
[header configureWithComment:comment];
|
||
|
||
__weak typeof(self) weakSelf = self;
|
||
header.onLikeAction = ^{
|
||
// TODO: 处理点赞逻辑
|
||
comment.isLiked = !comment.isLiked;
|
||
comment.likeCount += comment.isLiked ? 1 : -1;
|
||
[weakSelf.tableView reloadSections:[NSIndexSet indexSetWithIndex:section]
|
||
withRowAnimation:UITableViewRowAnimationNone];
|
||
};
|
||
|
||
return header;
|
||
}
|
||
|
||
- (UIView *)tableView:(UITableView *)tableView
|
||
viewForFooterInSection:(NSInteger)section {
|
||
KBAICommentModel *comment = self.comments[section];
|
||
KBAIReplyFooterState state = [comment footerState];
|
||
|
||
// 无二级评论时返回空视图
|
||
if (state == KBAIReplyFooterStateHidden) {
|
||
NSLog(@"[KBAICommentView] footer hidden section=%ld", (long)section);
|
||
return nil;
|
||
}
|
||
|
||
KBAICommentFooterView *footer = [tableView
|
||
dequeueReusableHeaderFooterViewWithIdentifier:kCommentFooterIdentifier];
|
||
[footer configureWithComment:comment];
|
||
|
||
__weak typeof(self) weakSelf = self;
|
||
footer.onAction = ^{
|
||
[weakSelf handleFooterActionForSection:section];
|
||
};
|
||
|
||
return footer;
|
||
}
|
||
|
||
- (CGFloat)tableView:(UITableView *)tableView
|
||
heightForHeaderInSection:(NSInteger)section {
|
||
return UITableViewAutomaticDimension;
|
||
}
|
||
|
||
- (CGFloat)tableView:(UITableView *)tableView
|
||
heightForFooterInSection:(NSInteger)section {
|
||
KBAICommentModel *comment = self.comments[section];
|
||
KBAIReplyFooterState state = [comment footerState];
|
||
|
||
if (state == KBAIReplyFooterStateHidden) {
|
||
return CGFLOAT_MIN;
|
||
}
|
||
return 30;
|
||
}
|
||
|
||
- (CGFloat)tableView:(UITableView *)tableView
|
||
estimatedHeightForHeaderInSection:(NSInteger)section {
|
||
return 100;
|
||
}
|
||
|
||
- (CGFloat)tableView:(UITableView *)tableView
|
||
estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||
return 60;
|
||
}
|
||
|
||
#pragma mark - Footer Actions
|
||
|
||
- (void)handleFooterActionForSection:(NSInteger)section {
|
||
KBAICommentModel *comment = self.comments[section];
|
||
KBAIReplyFooterState state = [comment footerState];
|
||
|
||
switch (state) {
|
||
case KBAIReplyFooterStateExpand:
|
||
case KBAIReplyFooterStateLoadMore: {
|
||
[self loadMoreRepliesForSection:section];
|
||
break;
|
||
}
|
||
case KBAIReplyFooterStateCollapse: {
|
||
[self collapseRepliesForSection:section];
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
- (void)loadMoreRepliesForSection:(NSInteger)section {
|
||
KBAICommentModel *comment = self.comments[section];
|
||
NSInteger currentCount = comment.displayedReplies.count;
|
||
NSDictionary *anchor = [self captureHeaderAnchorForSection:section];
|
||
|
||
NSLog(@"[KBAICommentView] loadMore(before) section=%ld offsetY=%.2f contentSizeH=%.2f boundsH=%.2f rows=%ld",
|
||
(long)section,
|
||
self.tableView.contentOffset.y,
|
||
self.tableView.contentSize.height,
|
||
self.tableView.bounds.size.height,
|
||
(long)currentCount);
|
||
|
||
// 加载更多
|
||
[comment loadMoreReplies:kRepliesLoadCount];
|
||
|
||
// 计算新增的行
|
||
NSInteger newCount = comment.displayedReplies.count;
|
||
NSMutableArray *insertIndexPaths = [NSMutableArray array];
|
||
for (NSInteger i = currentCount; i < newCount; i++) {
|
||
[insertIndexPaths addObject:[NSIndexPath indexPathForRow:i
|
||
inSection:section]];
|
||
}
|
||
|
||
// 更新(避免动画导致跳动)
|
||
[UIView performWithoutAnimation:^{
|
||
[self.tableView beginUpdates];
|
||
if (insertIndexPaths.count > 0) {
|
||
[self.tableView insertRowsAtIndexPaths:insertIndexPaths
|
||
withRowAnimation:UITableViewRowAnimationNone];
|
||
}
|
||
[self.tableView endUpdates];
|
||
[self.tableView layoutIfNeeded];
|
||
[self restoreHeaderAnchor:anchor];
|
||
}];
|
||
|
||
NSLog(@"[KBAICommentView] loadMore(after) section=%ld offsetY=%.2f contentSizeH=%.2f boundsH=%.2f rows=%ld",
|
||
(long)section,
|
||
self.tableView.contentOffset.y,
|
||
self.tableView.contentSize.height,
|
||
self.tableView.bounds.size.height,
|
||
(long)comment.displayedReplies.count);
|
||
dispatch_async(dispatch_get_main_queue(), ^{
|
||
NSLog(@"[KBAICommentView] loadMore(next) section=%ld offsetY=%.2f contentSizeH=%.2f boundsH=%.2f viewBoundsH=%.2f",
|
||
(long)section,
|
||
self.tableView.contentOffset.y,
|
||
self.tableView.contentSize.height,
|
||
self.tableView.bounds.size.height,
|
||
self.bounds.size.height);
|
||
});
|
||
|
||
// 手动刷新 Footer(避免使用 reloadSections 导致滚动)
|
||
KBAICommentFooterView *footerView =
|
||
(KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
||
if (footerView) {
|
||
[footerView configureWithComment:comment];
|
||
}
|
||
}
|
||
|
||
- (void)collapseRepliesForSection:(NSInteger)section {
|
||
KBAICommentModel *comment = self.comments[section];
|
||
NSInteger rowCount = comment.displayedReplies.count;
|
||
NSDictionary *anchor = [self captureHeaderAnchorForSection:section];
|
||
|
||
NSLog(@"[KBAICommentView] collapse(before) section=%ld offsetY=%.2f contentSizeH=%.2f boundsH=%.2f rows=%ld",
|
||
(long)section,
|
||
self.tableView.contentOffset.y,
|
||
self.tableView.contentSize.height,
|
||
self.tableView.bounds.size.height,
|
||
(long)rowCount);
|
||
|
||
// 计算要删除的行
|
||
NSMutableArray *deleteIndexPaths = [NSMutableArray array];
|
||
for (NSInteger i = 0; i < rowCount; i++) {
|
||
[deleteIndexPaths addObject:[NSIndexPath indexPathForRow:i
|
||
inSection:section]];
|
||
}
|
||
|
||
// 收起回复
|
||
[comment collapseReplies];
|
||
|
||
// 删除行(避免动画导致跳动)
|
||
[UIView performWithoutAnimation:^{
|
||
[self.tableView beginUpdates];
|
||
if (deleteIndexPaths.count > 0) {
|
||
[self.tableView deleteRowsAtIndexPaths:deleteIndexPaths
|
||
withRowAnimation:UITableViewRowAnimationNone];
|
||
}
|
||
[self.tableView endUpdates];
|
||
[self.tableView layoutIfNeeded];
|
||
[self restoreHeaderAnchor:anchor];
|
||
}];
|
||
|
||
NSLog(@"[KBAICommentView] collapse(after) section=%ld offsetY=%.2f contentSizeH=%.2f boundsH=%.2f rows=%ld",
|
||
(long)section,
|
||
self.tableView.contentOffset.y,
|
||
self.tableView.contentSize.height,
|
||
self.tableView.bounds.size.height,
|
||
(long)comment.displayedReplies.count);
|
||
dispatch_async(dispatch_get_main_queue(), ^{
|
||
NSLog(@"[KBAICommentView] collapse(next) section=%ld offsetY=%.2f contentSizeH=%.2f boundsH=%.2f viewBoundsH=%.2f",
|
||
(long)section,
|
||
self.tableView.contentOffset.y,
|
||
self.tableView.contentSize.height,
|
||
self.tableView.bounds.size.height,
|
||
self.bounds.size.height);
|
||
});
|
||
|
||
// 手动刷新 Footer(避免使用 reloadSections 导致滚动)
|
||
KBAICommentFooterView *footerView =
|
||
(KBAICommentFooterView *)[self.tableView footerViewForSection:section];
|
||
if (footerView) {
|
||
[footerView configureWithComment:comment];
|
||
}
|
||
}
|
||
|
||
#pragma mark - Actions
|
||
|
||
- (void)closeButtonTapped {
|
||
// 关闭评论视图(由外部处理)
|
||
[[NSNotificationCenter defaultCenter]
|
||
postNotificationName:@"KBAICommentViewCloseNotification"
|
||
object:nil];
|
||
}
|
||
|
||
#pragma mark - Lazy Loading
|
||
|
||
- (UIView *)headerView {
|
||
if (!_headerView) {
|
||
_headerView = [[UIView alloc] init];
|
||
_headerView.backgroundColor = [UIColor whiteColor];
|
||
}
|
||
return _headerView;
|
||
}
|
||
|
||
- (UILabel *)titleLabel {
|
||
if (!_titleLabel) {
|
||
_titleLabel = [[UILabel alloc] init];
|
||
_titleLabel.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium];
|
||
_titleLabel.textColor = [UIColor labelColor];
|
||
_titleLabel.text = @"0条评论";
|
||
}
|
||
return _titleLabel;
|
||
}
|
||
|
||
- (UIButton *)closeButton {
|
||
if (!_closeButton) {
|
||
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
|
||
[_closeButton setImage:[UIImage systemImageNamed:@"xmark"]
|
||
forState:UIControlStateNormal];
|
||
_closeButton.tintColor = [UIColor labelColor];
|
||
[_closeButton addTarget:self
|
||
action:@selector(closeButtonTapped)
|
||
forControlEvents:UIControlEventTouchUpInside];
|
||
}
|
||
return _closeButton;
|
||
}
|
||
|
||
- (UITableView *)tableView {
|
||
if (!_tableView) {
|
||
_tableView = [[UITableView alloc] initWithFrame:CGRectZero
|
||
style:UITableViewStyleGrouped];
|
||
_tableView.dataSource = self;
|
||
_tableView.delegate = self;
|
||
_tableView.backgroundColor = [UIColor whiteColor];
|
||
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||
_tableView.estimatedRowHeight = 0;
|
||
_tableView.rowHeight = UITableViewAutomaticDimension;
|
||
_tableView.estimatedSectionHeaderHeight = 0;
|
||
_tableView.estimatedSectionFooterHeight = 0;
|
||
_tableView.sectionHeaderHeight = UITableViewAutomaticDimension;
|
||
_tableView.sectionFooterHeight = UITableViewAutomaticDimension;
|
||
_tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
|
||
|
||
// 注册 Header/Cell/Footer
|
||
[_tableView registerClass:[KBAICommentHeaderView class]
|
||
forHeaderFooterViewReuseIdentifier:kCommentHeaderIdentifier];
|
||
[_tableView registerClass:[KBAIReplyCell class]
|
||
forCellReuseIdentifier:kReplyCellIdentifier];
|
||
[_tableView registerClass:[KBAICommentFooterView class]
|
||
forHeaderFooterViewReuseIdentifier:kCommentFooterIdentifier];
|
||
|
||
// 去掉顶部间距
|
||
if (@available(iOS 15.0, *)) {
|
||
_tableView.sectionHeaderTopPadding = 0;
|
||
}
|
||
}
|
||
return _tableView;
|
||
}
|
||
|
||
- (KBAICommentInputView *)inputView {
|
||
if (!_inputView) {
|
||
_inputView = [[KBAICommentInputView alloc] init];
|
||
_inputView.placeholder = @"说点什么...";
|
||
|
||
__weak typeof(self) weakSelf = self;
|
||
_inputView.onSend = ^(NSString *text) {
|
||
// TODO: 发送评论
|
||
NSLog(@"[KBAICommentView] Send comment: %@", text);
|
||
[weakSelf.inputView clearText];
|
||
};
|
||
}
|
||
return _inputView;
|
||
}
|
||
|
||
#pragma mark - Header Anchor
|
||
|
||
- (NSDictionary *)captureHeaderAnchorForSection:(NSInteger)section {
|
||
CGRect rect = [self.tableView rectForHeaderInSection:section];
|
||
NSLog(@"[KBAICommentView] anchor capture(header) section=%ld rect=%@ offsetY=%.2f contentSizeH=%.2f boundsH=%.2f",
|
||
(long)section,
|
||
NSStringFromCGRect(rect),
|
||
self.tableView.contentOffset.y,
|
||
self.tableView.contentSize.height,
|
||
self.tableView.bounds.size.height);
|
||
if (CGRectIsEmpty(rect) || CGRectIsNull(rect)) {
|
||
NSLog(@"[KBAICommentView] anchor capture(header) empty section=%ld",
|
||
(long)section);
|
||
return nil;
|
||
}
|
||
|
||
CGFloat offset = self.tableView.contentOffset.y - rect.origin.y;
|
||
return @{
|
||
@"section" : @(section),
|
||
@"offset" : @(offset),
|
||
@"fallbackOffset" : @(self.tableView.contentOffset.y)
|
||
};
|
||
}
|
||
|
||
- (void)restoreHeaderAnchor:(NSDictionary *)anchor {
|
||
if (!anchor) {
|
||
NSLog(@"[KBAICommentView] anchor restore(header) skipped (nil)");
|
||
return;
|
||
}
|
||
|
||
NSInteger section = [anchor[@"section"] integerValue];
|
||
if (section < 0 || section >= self.comments.count) {
|
||
NSLog(@"[KBAICommentView] anchor restore(header) invalid section=%ld",
|
||
(long)section);
|
||
return;
|
||
}
|
||
|
||
CGRect rect = [self.tableView rectForHeaderInSection:section];
|
||
NSLog(@"[KBAICommentView] anchor restore(header) section=%ld rect=%@",
|
||
(long)section,
|
||
NSStringFromCGRect(rect));
|
||
if (CGRectIsEmpty(rect) || CGRectIsNull(rect)) {
|
||
NSNumber *fallbackOffset = anchor[@"fallbackOffset"];
|
||
if (!fallbackOffset) {
|
||
NSLog(@"[KBAICommentView] anchor restore(header) no fallback section=%ld",
|
||
(long)section);
|
||
return;
|
||
}
|
||
NSLog(@"[KBAICommentView] anchor restore(header) fallback section=%ld offsetY=%.2f",
|
||
(long)section,
|
||
[fallbackOffset doubleValue]);
|
||
[self setTableViewOffset:[fallbackOffset doubleValue]];
|
||
return;
|
||
}
|
||
|
||
CGFloat targetOffsetY = rect.origin.y + [anchor[@"offset"] doubleValue];
|
||
NSLog(@"[KBAICommentView] anchor restore(header) target section=%ld targetY=%.2f",
|
||
(long)section,
|
||
targetOffsetY);
|
||
[self setTableViewOffset:targetOffsetY];
|
||
}
|
||
|
||
- (void)setTableViewOffset:(CGFloat)offsetY {
|
||
UIEdgeInsets inset = self.tableView.adjustedContentInset;
|
||
CGFloat minOffsetY = -inset.top;
|
||
CGFloat maxOffsetY =
|
||
MAX(minOffsetY, self.tableView.contentSize.height + inset.bottom -
|
||
self.tableView.bounds.size.height);
|
||
CGFloat targetOffsetY = MIN(MAX(offsetY, minOffsetY), maxOffsetY);
|
||
NSLog(@"[KBAICommentView] set offset target=%.2f min=%.2f max=%.2f",
|
||
targetOffsetY,
|
||
minOffsetY,
|
||
maxOffsetY);
|
||
[self.tableView setContentOffset:CGPointMake(0, targetOffsetY)
|
||
animated:NO];
|
||
}
|
||
|
||
@end
|