如何将 UITableViewCell 定位在屏幕底部?
Posted
技术标签:
【中文标题】如何将 UITableViewCell 定位在屏幕底部?【英文标题】:How to position UITableViewCell at bottom of screen? 【发布时间】:2013-03-08 02:28:56 【问题描述】:UITableViewView
中有一到三个 UITableViewCell
s。有没有办法在reloadData
之后始终将单元格定位在屏幕底部?
+----------------+ +----------------+ +----------------+
| | | | | |
| | | | | |
| | | | | |
| | | | | +------------+ |
| | | | | | cell 1 | |
| | | | | +------------+ |
| | | +------------+ | | +------------+ |
| | | | cell 1 | | | | cell 2 | |
| | | +------------+ | | +------------+ |
| +------------+ | | +------------+ | | +------------+ |
| | cell 1 | | | | cell 2 | | | | cell 3 | |
| +------------+ | | +------------+ | | +------------+ |
+----------------+ +----------------+ +----------------+
【问题讨论】:
会启用滚动吗? @Espresso 滚动无关紧要,只要最后一个单元格位于底部 单元格的高度是静态的吗? @NAZIK 你可能会认为是的。 您可以随时将表格倒置,然后旋转单元格以应对这种情况。 【参考方案1】:我已经创建了一个新的示例解决方案,因为以前的答案对于现代用途来说并不过时。
最新技术使用自动布局和自动调整单元格大小,因此之前的答案将不再有效。我重新设计了解决方案以使用现代功能,并创建了一个示例项目以放在 GitHub 上。
此代码不是计算每行的高度,这会导致额外的工作,而是获取最后一行的框架,以便可以计算从顶部插入的内容。它利用了表格视图已经在做的事情,因此不需要额外的工作。
此代码也仅设置顶部插图,以防为键盘或其他覆盖设置底部插图。
请在 GitHub 上报告任何错误或提交改进,我将更新此示例。
GitHub:https://github.com/brennanMKE/BottomTable
- (void)updateContentInsetForTableView:(UITableView *)tableView animated:(BOOL)animated
NSUInteger lastRow = [self tableView:tableView numberOfRowsInSection:0];
NSUInteger lastIndex = lastRow > 0 ? lastRow - 1 : 0;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:lastIndex inSection:0];
CGRect lastCellFrame = [self.tableView rectForRowAtIndexPath:lastIndexPath];
// top inset = table view height - top position of last cell - last cell height
CGFloat topInset = MAX(CGRectGetHeight(self.tableView.frame) - lastCellFrame.origin.y - CGRectGetHeight(lastCellFrame), 0);
UIEdgeInsets contentInset = tableView.contentInset;
contentInset.top = topInset;
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:animated ? 0.25 : 0.0 delay:0.0 options:options animations:^
tableView.contentInset = contentInset;
completion:^(BOOL finished)
];
【讨论】:
为什么要传tableView不使用?tableView.contentInset = contentInset;
【参考方案2】:
每当添加一行时调用此方法:
- (void)updateContentInset
NSInteger numRows=[self tableView:_tableView numberOfRowsInSection:0];
CGFloat contentInsetTop=_tableView.bounds.size.height;
for (int i=0;i<numRows;i++)
contentInsetTop-=[self tableView:_tableView heightForRowAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
if (contentInsetTop<=0)
contentInsetTop=0;
break;
_tableView.contentInset = UIEdgeInsetsMake(contentInsetTop, 0, 0, 0);
【讨论】:
比用旋转拧紧所有东西要合适得多。 我要做的一个改变是取而代之的是采用当前的内容插入值并仅修改顶部值。如果要显示键盘并设置偏移量以允许显示底部的内容,则可能需要将底部值设置为 0 以外的其他值。 不那么冗长的方法:self.tableView.contentInset = UIEdgeInsetsMake(MAX(0.0, self.tableView.bounds.size.height - self.tableView.contentSize.height), 0, 0, 0);
。表格视图的内容大小是所有单元格的大小。【参考方案3】:
您可以在表格视图中设置标题并使其足够高以将第一个单元格向下推。然后相应地设置 tableView 的 contentOffset 。不过,我认为没有一种快速的内置方法可以做到这一点。
【讨论】:
@ohho 另外,您可以将表格分组。 我已经发布了一个新的答案,其中包含一个现代解决方案,该解决方案适用于自动布局和自动调整单元格。请查看并投票。【参考方案4】:我不喜欢基于空单元格、contentInset
或 transform
的解决方案,而是想出了其他解决方案:
UITableView
的布局是私有的,如果 Apple 愿意,可能会更改,最好完全控制,从而使您的代码面向未来且更加灵活。我切换到UICollectionView
并为此实现了基于UICollectionViewFlowLayout
的特殊布局(Swift 3):
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
// Do we need to stick cells to the bottom or not
var shiftDownNeeded = false
// Size of all cells without modifications
let allContentSize = super.collectionViewContentSize()
// If there are not enough cells to fill collection view vertically we shift them down
let diff = self.collectionView!.bounds.size.height - allContentSize.height
if Double(diff) > DBL_EPSILON
shiftDownNeeded = true
// Ask for common attributes
let attributes = super.layoutAttributesForElements(in: rect)
if let attributes = attributes
if shiftDownNeeded
for element in attributes
let frame = element.frame;
// shift all the cells down by the difference of heights
element.frame = frame.offsetBy(dx: 0, dy: diff);
return attributes;
它在我的情况下工作得很好,显然,可以通过某种方式缓存内容大小高度来优化。另外,我不确定如果不对大数据集进行优化,它会如何执行,我没有测试过。我已经将示例项目与演示放在一起:MDBottomSnappingCells。
这里是 Objective-C 版本:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;
// Do we need to stick cells to the bottom or not
BOOL shiftDownNeeded = NO;
// Size of all cells without modifications
CGSize allContentSize = [super collectionViewContentSize];
// If there are not enough cells to fill collection view vertically we shift them down
CGFloat diff = self.collectionView.bounds.size.height - allContentSize.height;
if(diff > DBL_EPSILON)
shiftDownNeeded = YES;
// Ask for common attributes
NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
if(shiftDownNeeded)
for(UICollectionViewLayoutAttributes *element in attributes)
CGRect frame = element.frame;
// shift all the cells down by the difference of heights
element.frame = CGRectOffset(frame, 0, diff);
return attributes;
【讨论】:
我喜欢你的方法。但是,我有一个问题。我们如何在集合视图中使用自动布局获得动态单元格高度?我们可以使用 UITableViewAutomaticDimension 在 tableview 中做到这一点。 我建议不要在 UITableView/UICollectionView 单元格中使用自动布局,但如果您仍然需要此功能,您可以通过多种方式执行此操作,对于集合视图,我找到了答案:***.com/q/25895311/1032151 通常,对于 UITableView,您有一个静态的“调整单元格”,您可以使用数据配置并计算高度,而集合视图的工作方式不同,但对于我建议的布局,您可以使用相同的概念。【参考方案5】:这是@Brennan 接受的解决方案的 Swift 3 版本,经过测试和批准:)
func updateContentInsetForTableView( tableView:UITableView,animated:Bool)
let lastRow = tableView.numberOfRows(inSection: 0)
let lastIndex = lastRow > 0 ? lastRow - 1 : 0;
let lastIndexPath = IndexPath(row: lastIndex, section: 9)
let lastCellFrame = tableView.rectForRow(at: lastIndexPath)
let topInset = max(tableView.frame.height - lastCellFrame.origin.y - lastCellFrame.height, 0)
var contentInset = tableView.contentInset;
contentInset.top = topInset;
_ = UIViewAnimationOptions.beginFromCurrentState;
UIView.animate(withDuration: 0.1, animations: () -> Void in
tableView.contentInset = contentInset;
)
【讨论】:
【参考方案6】:这可以通过以下函数快速完成
func updateContentInsetForTableView(tblView: UITableView, animated: Bool)
let lastRow: NSInteger = self.tableView(tblView, numberOfRowsInSection: 0)
let lastIndex: NSInteger = lastRow > 0 ? lastRow - 1 : 0
let lastIndexPath: NSIndexPath = NSIndexPath(forRow: lastIndex, inSection: 0)
let lastCellFrame: CGRect = tblView.rectForRowAtIndexPath(lastIndexPath)
let topInset: CGFloat = max(CGRectGetHeight(tblView.frame) - lastCellFrame.origin.y - CGRectGetHeight(lastCellFrame), 0)
var contentInset: UIEdgeInsets = tblView.contentInset
contentInset.top = topInset
let option: UIViewAnimationOptions = UIViewAnimationOptions.BeginFromCurrentState
UIView.animateWithDuration(animated ? 0.25 : 0.0, delay: 0.0, options: option, animations: () -> Void in
tblView.contentInset = contentInset
) (_) -> Void in
【讨论】:
【参考方案7】:使用这个。这肯定会有所帮助。
- (void)reloadData
[super reloadData];
[self recalculateContentInset];
[self recalculateScrollIndicator];
- (void)recalculateContentInset
CGFloat contentInsetHeight = MAX(self.frame.size.height - self.contentSize.height, 0);
CGFloat duration = 0.0;
[UIView animateWithDuration:duration
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^
[self setContentInset:UIEdgeInsetsMake(contentInsetHeight, 0, 0, 0)];
completion:nil];
- (void)recalculateScrollIndicator
if(self.contentSize.height >= self.frame.size.height)
[self setShowsVerticalScrollIndicator:YES];
else
[self setShowsVerticalScrollIndicator:NO];
【讨论】:
你能在你的答案中解释你的代码或至少一些 cmets 吗?【参考方案8】:我会根据单元格的数量在其父视图中调整 UITableView 的大小并重新定位。我想这是涉及最少变通办法的解决方案。 另外,你真的需要使用 UITableView 吗?
【讨论】:
【参考方案9】:if(indexPath.row!=CategoriesArray.count-1)
cell.hidden = YES;
return cell;
【讨论】:
【参考方案10】:在新节中添加一个空白单元格并将其设为索引为零的节。
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
return 2;
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
if (section==0)
return 1;
return [self.yourArray count];
现在
tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath //method add following in beginnig//
if (indexPath.section == 0 )
UITableViewCell * cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
// cell.backgroundColor = [UIColor clearColor];
return cell;
现在
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
if (indexPath.section == 0 )
if (self.yourArray.count>0)
CGFloat totalCellHeight = self.messages.count * yourCellHeight;
if (totalCellHeight>= self.table.bounds.size.height)
return 0;
return self.table.bounds.size.height - totalCellHeight;
else
return self.table.bounds.size.height;
return yourCellHeight;
现在把这个粘贴到你要重新加载 tableView 的地方
[self.table reloadData];
[self.table scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.yourArray count]-1 inSection:1] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
它对我有用。希望这会有所帮助。
【讨论】:
在问题 cmets 中,Ohho 已指定单元格高度是静态的。所以我想到了这个。感谢艾伦展示它。【参考方案11】:优雅快捷的解决方案,无需一行代码。
使用容器视图并将 UITableViewController 放入容器中(embed segue)。
您可以为此容器设置任何高度。
【讨论】:
【参考方案12】:我发现这样做的最佳方法是观察 tableview 的内容大小并在必要时调整 inset。例如:
static char kvoTableContentSizeContext = 0;
- (void) viewWillAppear:(BOOL)animated
[_tableView addObserver:self forKeyPath:@"contentSize" options:0 context:&kvoTableContentSizeContext];
- (void) viewWillDisappear:(BOOL)animated
[_tableView removeObserver:self forKeyPath:@"contentSize"];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
if (context == &kvoTableContentSizeContext)
CGFloat contentHeight = _tableView.contentSize.height;
CGFloat tableHeight = _tableView.frame.size.height;
if (contentHeight < tableHeight)
UIEdgeInsets insets = _tableView.contentInset;
insets.top = tableHeight - contentHeight;
_tableView.contentInset = insets;
else
_tableView.contentInset = UIEdgeInsetsZero;
else
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
【讨论】:
【参考方案13】:所有答案都有一些与动态 rowHeight 和/或动画有关的怪癖。 对我来说,最好的解决方案是转换表格(翻转):
tableView.transform = CGAffineTransform (scaleX: 1,y: -1)
在cellForRowAt
内:
cell.contentView.transform = CGAffineTransform (scaleX: 1,y: -1)
cell.accessoryView?.transform = CGAffineTransform (scaleX: 1,y: -1)
您还可以反转数据数组,也可以翻转节页眉/页脚。 您的页脚也将成为您的新页眉 - 但是,它有效。
【讨论】:
【参考方案14】:func updateContentInsetForTableView( tableView:UITableView,animated:Bool)
let lastRow = tableView.numberOfRows(inSection: 0)
let lastIndex = lastRow > 0 ? lastRow - 1 : 0;
let lastIndexPath = IndexPath(row: lastIndex, section: 9)
let lastCellFrame = tableView.rectForRow(at: lastIndexPath)
let topInset = max(tableView.frame.height - lastCellFrame.origin.y - lastCellFrame.height, 0)
var contentInset = tableView.contentInset;
contentInset.top = topInset;
_ = UIViewAnimationOptions.beginFromCurrentState;
UIView.animate(withDuration: 0.1, animations: () -> Void in
tableView.contentInset = contentInset;
)
if self.commesnts.count > 0
tableView.scrollToRow(at: IndexPath(item:self.commesnts.count-1, section: 0), at: .bottom, animated: true)
我使用了@bkokot 解决方案,几乎没有添加。它做了两件事
1. Start showing cells from bottom of UITableView
2. Scroll cells to bottom so that last inserted row become visible
(just like chat)
【讨论】:
以上是关于如何将 UITableViewCell 定位在屏幕底部?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 UITableViewCell 中获取 UIImageview 的屏幕位置
在屏幕上滚动和返回时如何保留 UITableViewCell 的选定状态