@@ -19,15 +19,12 @@ 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 ) UI TableView * tableView ;
@ property ( nonatomic , strong ) Base TableView * tableView ;
@ property ( nonatomic , strong ) KBAICommentInputView * inputView ;
@ property ( nonatomic , strong ) NSMutableArray < KBAICommentModel * > * comments ;
@@ -184,8 +181,21 @@ static NSInteger const kRepliesLoadCount = 3;
NSArray * commentsArray = json [ @ "comments" ] ;
[ self . comments removeAllObjects ] ;
// 获 取 tableView 宽 度 用 于 计 算 高 度
CGFloat tableWidth = self . tableView . bounds . size . width ;
if ( tableWidth <= 0 ) {
tableWidth = [ UIScreen mainScreen ] . bounds . size . width ;
}
for ( NSDictionary * dict in commentsArray ) {
KBAICommentModel * comment = [ KBAICommentModel mj_objectWithKeyValues : dict ] ;
// 预 先 计 算 并 缓 存 Header 高 度
comment . cachedHeaderHeight = [ comment calculateHeaderHeightWithMaxWidth : tableWidth ] ;
// 预 先 计 算 并 缓 存 所 有 Reply 高 度
for ( KBAIReplyModel * reply in comment . replies ) {
reply . cachedCellHeight = [ reply calculateCellHeightWithMaxWidth : tableWidth ] ;
}
[ self . comments addObject : comment ] ;
}
@@ -268,7 +278,6 @@ static NSInteger const kRepliesLoadCount = 3;
// 无 二 级 评 论 时 返 回 空 视 图
if ( state = = KBAIReplyFooterStateHidden ) {
NSLog ( @ "[KBAICommentView] footer hidden section=%ld" , ( long ) section ) ;
return nil ;
}
@@ -286,7 +295,15 @@ static NSInteger const kRepliesLoadCount = 3;
- ( CGFloat ) tableView : ( UITableView * ) tableView
heightForHeaderInSection : ( NSInteger ) section {
return UITableViewAutomaticDimension ;
KBAICommentModel * comment = self . comments [ section ] ;
return comment . cachedHeaderHeight ;
}
- ( CGFloat ) tableView : ( UITableView * ) tableView
heightForRowAtIndexPath : ( NSIndexPath * ) indexPath {
KBAICommentModel * comment = self . comments [ indexPath . section ] ;
KBAIReplyModel * reply = comment . displayedReplies [ indexPath . row ] ;
return reply . cachedCellHeight ;
}
- ( CGFloat ) tableView : ( UITableView * ) tableView
@@ -300,16 +317,6 @@ static NSInteger const kRepliesLoadCount = 3;
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 {
@@ -317,9 +324,8 @@ static NSInteger const kRepliesLoadCount = 3;
KBAIReplyFooterState state = [ comment footerState ] ;
switch ( state ) {
case KBAIReplyFooterStateExpand :
case KBAIReplyFooterStateLoadMore : {
[ self loadMoreRepliesForSection : section ] ;
case KBAIReplyFooterStateExpand : {
[ self expandRepliesForSection : section ] ;
break ;
}
case KBAIReplyFooterStateCollapse : {
@@ -331,115 +337,30 @@ static NSInteger const kRepliesLoadCount = 3;
}
}
- ( void ) loadMore RepliesForSection: ( NSInteger ) section {
- ( void ) expand RepliesForSection: ( 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 . t ableView. 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 ] ] ;
}
// 更 新 ( 带 轻 微 动 画 )
[ self . tableView beginUpdates ] ;
if ( insertIndexPaths . count > 0 ) {
[ self . tableView insertRowsAtIndexPaths : insertIndexPaths
withRowAnimation : UITableViewRowAnimationFade ] ;
}
[ 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 ] ;
}
// 一 次 性 展 开 全 部 回 复
[ comment expandAllReplies ] ;
// 直 接 刷 新 该 section
// [ UIView performWithoutAnimation : ^ {
[ self . tableView reloadSections : [ NSIndexSet indexSetWithIndex : section ]
withRowAnimation : UIT ableViewRowAnimationAutomatic ] ;
// } ] ;
}
- ( 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 ] ;
// 删 除 行 ( 带 轻 微 动 画 )
[ self . tableView beginUpdates ] ;
if ( deleteIndexPaths . count > 0 ) {
[ self . t ableView deleteRowsAtIndexPaths : deleteIndexPaths
withRowAnimation : UITableViewRowAnimationFade ] ;
}
[ 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 ] ;
}
// 直 接 刷 新 该 section
// [ UIView performWithoutAnimation : ^ {
[ self . tableView reloadSections : [ NSIndexSet indexSetWithIndex : section ]
withRowAnimation : UIT ableViewRowAnimationAutomatic ] ;
// } ] ;
}
# pragma mark - Actions
@@ -484,20 +405,14 @@ static NSInteger const kRepliesLoadCount = 3;
return _closeButton ;
}
- ( UI TableView * ) tableView {
- ( Base TableView * ) tableView {
if ( ! _tableView ) {
_tableView = [ [ UI TableView alloc ] initWithFrame : CGRectZero
_tableView = [ [ Base TableView 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
@@ -531,81 +446,4 @@ static NSInteger const kRepliesLoadCount = 3;
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