使用UITableViewAutomaticDimension行高自动滚动到表的底部? – Swift,iOS 8+

通常,在表格中,可以使用以下代码自动滚动到底部:

let indexPath = NSIndexPath(forRow: rowCount - 1, inSection: 0) self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true) 

但是,当我将它设置为在viewDidLoad()中使用自动行高时(如下所示),上面的代码只能大致向下滚动一半(根据行高和行数而变化 – 只会发生多行比estimatedRowHeight)。

 tableView.estimatedRowHeight = 50 tableView.rowHeight = UITableViewAutomaticDimension 

如何在使用UITableViewAutomaticDimension时编程式地向下滚动到表格底部?

将行插入到具有自动单元格大小的表视图底部时,我遇到了非常类似的问题,这是我能find的唯一解决方法。 这不是很漂亮,但做的工作(在iOS 8和9testing)。 在插入时:

 // compute the insertion index as the last one let insertionIndex = NSIndexPath(forRow: elements.count - 1, inSection: 0) // delete from the top, insert at the bottom tableView.beginUpdates() tableView.deleteRowsAtIndexPaths(deletionsIndexes, withRowAnimation: .Top) tableView.insertRowsAtIndexPaths([insertionIndex], withRowAnimation: .Fade) tableView.endUpdates() // scroll to the last row, position will be wrong tableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: .Bottom, animated: false) // scroll back to the one before, the actual scrolling will be done after inserting the cell if elements.count > 1 { let prevIndex = NSIndexPath(forRow: lastIndex.row - 1, inSection: 0) self.tableView.scrollToRowAtIndexPath(prevIndex, atScrollPosition: .Bottom, animated: false) } 

正如你所看到的,我们滚动到最后一行来触发行插入,然后回滚到之前的行(全部没有animation),以避免任何可见的滚动。 然后插入单元格时:

 func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { [...] let cell = try configuredCell(forElement: element) dispatch_async(dispatch_get_main_queue()) { let lastIndex = NSIndexPath(forRow: self.elements.count - 1, inSection: 0) // when inserting the last element we do the actual animated scrolling if indexPath.row == self.elements.count - 1 { tableView.scrollToRowAtIndexPath(lastIndex, atScrollPosition: .Bottom, animated: true) } } return cell } 

我有同样的问题,并添加像这样的延迟帮助我解决这个问题:

 let delay = 0.1 * Double(NSEC_PER_SEC) let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay)) dispatch_after(time, dispatch_get_main_queue(), { let indexPath = NSIndexPath(forRow: rowCount - 1, inSection: 0) self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true) }) 

事实上, UITableView的这种“不良行为”的原因是行高的估计。 当使用dynamic单元格高度时,tableView的结果。 contentSize和例如[tableView rectForHeaderInSection: lastSection]对于尚未显示的项目是不同步的(例如,仅具有估计的布局rects的项目。例如,section rect可能在contentSize之外,或者在内部某处,即使没有单元内)。

另外,似乎scollRectToVisible:只能用于有效的rects。 因此,用目前对表格视图项目的错误估计来调用它可能只会导致一无所获。

我对这个问题的解决scheme是一个UITableView类 ,只是迭代几次滚动到所需的位置。 所以UITableView有机会计算真正的项目布局rects(页眉,单元格,页脚)。 我的testing显示,在大多数情况下只需要一个循环。

的UITableView + LEAScrollToVisible.h

 // // UITableView+LEAScrollToVisible.h // // Copyright © 2016 LaborEtArs. All rights reserved. // #import <UIKit/UIKit.h> /** UITableView (LEAScrollToVisible) */ @interface UITableView (LEAScrollToVisible) /* scrollSectionHeaderToVisible:withCompletion_LEA: */ - (void)scrollSectionHeaderToVisible:(NSInteger)pSectionHeaderIndex withCompletion_LEA:(void (^)(void))pCompletion; /* scrollRowToVisible:withCompletion_LEA: */ - (void)scrollRowToVisible:(NSIndexPath *)pRowIndexPath withCompletion_LEA:(void (^)(void))pCompletion; /* scrollSectionFooterToVisible:withCompletion_LEA: */ - (void)scrollSectionFooterToVisible:(NSInteger)pSectionFooterIndex withCompletion_LEA:(void (^)(void))pCompletion; @end 

的UITableView + LEAScrollToVisible.m

 // // UITableView+LEAScrollToVisible.m // // Copyright © 2016 LaborEtArs. All rights reserved. // #import "UITableView+LEAScrollToVisible.h" /** UITableView (LEAScrollToVisible) */ @implementation UITableView (LEAScrollToVisible) /* scrollSectionHeaderToVisible:withCompletion_LEA: */ - (void)scrollSectionHeaderToVisible:(NSInteger)pSectionHeaderIndex withCompletion_LEA:(void (^)(void))pCompletion { //FLog; NSAssert((pSectionHeaderIndex < self.numberOfSections), @"Invalid section header index: %li", (long int)pSectionHeaderIndex); // visible part of the scroll view CGRect visibleRect = CGRectMake((self.contentInset.left + self.contentOffset.x), (self.contentInset.top + self.contentOffset.y), (CGRectGetWidth(self.frame) - (self.contentInset.left + self.contentInset.right)), (CGRectGetHeight(self.frame) - (self.contentInset.top + self.contentInset.bottom))); // maximum content offset (to avoid to scroll too far) CGFloat maxPossibleContentOffset = MAX((-self.contentInset.top), (self.contentSize.height - CGRectGetHeight(visibleRect) - self.contentInset.top)); // the rect for the header view (maybe just estimated) CGRect sectionHeaderRect = [self rectForHeaderInSection:pSectionHeaderIndex]; // the theoretical offset to show the header rect topmost in the table view visible area CGFloat contentOffsetToBeVisibleAtTop = (CGRectGetMinY(sectionHeaderRect) - self.contentInset.top); // the real target offset CGFloat targetContentOffsetY = MIN(contentOffsetToBeVisibleAtTop, maxPossibleContentOffset); if (0.5 <= fabs(self.contentOffset.y - targetContentOffsetY)) { // current offset is different -> move // disable user interaction to avoid disturbances self.userInteractionEnabled = NO; // Scroll to target offset [self setContentOffset:CGPointMake(self.contentOffset.x, targetContentOffsetY) animated:YES]; // Reiterate after waiting for the animation to finalize dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // Reenable user interaction self.userInteractionEnabled = YES; [self scrollSectionHeaderToVisible:pSectionHeaderIndex withCompletion_LEA:pCompletion]; }); } else { // current offset fits -> execute completion block if (pCompletion) { pCompletion(); } } } /* scrollRowToVisible:withCompletion_LEA: */ - (void)scrollRowToVisible:(NSIndexPath *)pRowIndexPath withCompletion_LEA:(void (^)(void))pCompletion { //FLog; NSAssert((pRowIndexPath.section < self.numberOfSections), @"Invalid index path: %@", pRowIndexPath); NSAssert((pRowIndexPath.row < [self numberOfRowsInSection:pRowIndexPath.section]), @"Invalid index path: %@", pRowIndexPath); // visible part of the scroll view CGRect visibleRect = CGRectMake((self.contentInset.left + self.contentOffset.x), (self.contentInset.top + self.contentOffset.y), (CGRectGetWidth(self.frame) - (self.contentInset.left + self.contentInset.right)), (CGRectGetHeight(self.frame) - (self.contentInset.top + self.contentInset.bottom))); // maximum content offset (to avoid to scroll too far) CGFloat maxPossibleContentOffset = MAX((-self.contentInset.top), (self.contentSize.height - CGRectGetHeight(visibleRect) - self.contentInset.top)); // the rect for the cell view (maybe just estimated) CGRect cellRect = [self rectForRowAtIndexPath:pRowIndexPath]; // the theoretical offset to show the row rect topmost in the table view visible area CGFloat contentOffsetToBeVisibleAtTop = (CGRectGetMinY(cellRect) - self.contentInset.top); // the real target offset CGFloat targetContentOffsetY = MIN(contentOffsetToBeVisibleAtTop, maxPossibleContentOffset); if (0.5 <= fabs(self.contentOffset.y - targetContentOffsetY)) { // current offset is different -> move // disable user interaction to avoid disturbances self.userInteractionEnabled = NO; // Scroll to target offset [self setContentOffset:CGPointMake(self.contentOffset.x, targetContentOffsetY) animated:YES]; // Reiterate after waiting for the animation to finalize dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // Reenable user interaction self.userInteractionEnabled = YES; [self scrollRowToVisible:pRowIndexPath withCompletion_LEA:pCompletion]; }); } else { // current offset fits -> execute completion block if (pCompletion) { pCompletion(); } } } /* scrollSectionFooterToVisible:withCompletion_LEA: */ - (void)scrollSectionFooterToVisible:(NSInteger)pSectionFooterIndex withCompletion_LEA:(void (^)(void))pCompletion { //FLog; NSAssert((pSectionFooterIndex < self.numberOfSections), @"Invalid section footer index: %li", (long int)pSectionFooterIndex); // visible part of the scroll view CGRect visibleRect = CGRectMake((self.contentInset.left + self.contentOffset.x), (self.contentInset.top + self.contentOffset.y), (CGRectGetWidth(self.frame) - (self.contentInset.left + self.contentInset.right)), (CGRectGetHeight(self.frame) - (self.contentInset.top + self.contentInset.bottom))); // maximum content offset (to avoid to scroll too far) CGFloat maxPossibleContentOffset = MAX((-self.contentInset.top), (self.contentSize.height - CGRectGetHeight(visibleRect) - self.contentInset.top)); // the rect for the footer view (maybe just estimated) CGRect sectionFooterRect = [self rectForFooterInSection:pSectionFooterIndex]; // the theoretical offset to show the footer rect topmost in the table view visible area CGFloat contentOffsetToBeVisibleAtTop = (CGRectGetMinY(sectionFooterRect) - self.contentInset.top); // the real target offset CGFloat targetContentOffsetY = MIN(contentOffsetToBeVisibleAtTop, maxPossibleContentOffset); if (0.5 <= fabs(self.contentOffset.y - targetContentOffsetY)) { // current offset is different -> move // disable user interaction to avoid disturbances self.userInteractionEnabled = NO; // Scroll to target offset [self setContentOffset:CGPointMake(self.contentOffset.x, targetContentOffsetY) animated:YES]; // Reiterate after waiting for the animation to finalize dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // Reenable user interaction self.userInteractionEnabled = YES; [self scrollSectionFooterToVisible:pSectionFooterIndex withCompletion_LEA:pCompletion]; }); } else { // current offset fits -> execute completion block if (pCompletion) { pCompletion(); } } } @end