使用 UITableViewAutomaticDimension 行高自动滚动到表格底部? - 斯威夫特,iOS 8+
Posted
技术标签:
【中文标题】使用 UITableViewAutomaticDimension 行高自动滚动到表格底部? - 斯威夫特,iOS 8+【英文标题】:Automatically scrolling to the bottom of a table with UITableViewAutomaticDimension row height? - Swift, iOS 8+ 【发布时间】:2015-10-24 12:10:31 【问题描述】:通常,在表格中,可以使用以下代码自动滚动到底部:
let indexPath = NSIndexPath(forRow: rowCount - 1, inSection: 0)
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
但是,当我将其设置为在 viewDidLoad() 中使用自动行高时,如下所示,上面的代码仅向下滚动大约一半(取决于行高和行数 - 它只发生在多行中远大于估计的RowHeight)。
tableView.estimatedRowHeight = 50
tableView.rowHeight = UITableViewAutomaticDimension
如何在使用 UITableViewAutomaticDimension 时以编程方式向下滚动到表格底部?
【问题讨论】:
我知道它是旧的,但你尝试过 contentSize 属性吗? 【参考方案1】:在使用自动调整单元格大小的表格视图底部插入行时,我遇到了非常相似的问题,这是我能找到的唯一解决方法。它并不漂亮,但可以完成工作(在 ios 8 和 9 上测试)。 在插入时:
// 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)
如您所见,我们滚动到最后一行只是为了触发行插入 然后滚动回之前的行(全部没有动画)以避免任何可见的滚动。 然后在插入单元格时:
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
【讨论】:
【参考方案2】:我遇到了同样的问题,添加这样的延迟帮助我解决了这个问题:
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)
)
【讨论】:
我认为这是单元格尚未呈现的问题?我正在使用 50 行,即使有 10 秒的延迟,它仍然没有完全滚动到底部。 是的,似乎是渲染时间问题。它对你有用吗? 查看我编辑的评论 - 我在输入完之前不小心按了 Enter 我明白了。您能否尝试实现tableView:estimatedHeightForRowAtIndexPath:
并为您确定的单元格返回尽可能接近的高度。您可以在覆盖其余单元格的else
部分中返回UITableViewAutomaticDimension
。这可能是错误的单元格高度计算 - 只是一个想法。
我有一个表格视图,每个单元格都有一个标签,大约 50 高,大约 200+ 高。如果我使用estimatedHeightForRowAtIndexPath 并计算该函数中的行数,我可以获得更准确的数字,它确实滚动到底部,但代码不是很整洁 - 这是我唯一的选择(计算行数,乘以字体大小并添加填充)?【参考方案3】:
实际上,UITableView 的这种“错误行为”的原因是行高估计。当使用动态单元格高度时,tableView.contentSize 和例如 [tableView rectForHeaderInSection: lastSection] 的结果对于尚未显示的项目(例如仅具有估计布局矩形的项目。例如,矩形部分可能位于 contentSize 之外或内部某处,即使内部没有单元格也是如此)。
此外,scollRectToVisible: 似乎只适用于有效的矩形。因此,以当前对表格视图项的错误估计来调用它可能只会导致任何结果......
我对这个问题的解决方案是一个 UITableView 类别,它只是迭代几次以滚动到所需的位置。所以 UITableView 有机会计算真正的项目布局矩形(页眉、单元格、页脚)。我的测试表明,在大多数情况下,只需要一个循环。
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
【讨论】:
以上是关于使用 UITableViewAutomaticDimension 行高自动滚动到表格底部? - 斯威夫特,iOS 8+的主要内容,如果未能解决你的问题,请参考以下文章
带有嵌入 UICollectionView 的 UITableView 不起作用