如何在屏幕底部定位UITableViewCell?

UITableViewCell有一到三个UITableViewView 。 是否有一种方法可以在reloadData之后始终将单元放置在屏幕的底部?

 +----------------+ +----------------+ +----------------+ | | | | | | | | | | | | | | | | | | | | | | | +------------+ | | | | | | | cell 1 | | | | | | | +------------+ | | | | +------------+ | | +------------+ | | | | | cell 1 | | | | cell 2 | | | | | +------------+ | | +------------+ | | +------------+ | | +------------+ | | +------------+ | | | cell 1 | | | | cell 2 | | | | cell 3 | | | +------------+ | | +------------+ | | +------------+ | +----------------+ +----------------+ +----------------+ 

我已经创build了一个新的样本解决scheme,因为以前的答案不是过时的现代使用。

最新的技术使用自动布局和自我大小的细胞,所以以前的答案将不再工作。 我重新修改了解决scheme以使用现代function,并创build了一个示例项目,将其放在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) { }]; } 

每当添加一行时调用此方法:

 - (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); } 

你可以在你的表格视图中设置一个标题,使其高到足以推下第一个单元格。 然后相应地设置你的tableView的contentOffset。 我不认为有一个快速build立的方式来做到这一点,虽然。

我不喜欢空单元格, contentInset或基于transform的解决scheme,而是我想出了其他解决scheme:

例

UITableView的布局是私人的,如果苹果想要更改,最好是完全控制,从而使您的代码具有前瞻性和灵活性。 我切换到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; } 

它对我的情况非常好,显然,可以通过caching内容大小的高度进行优化。 另外,我不确定如果没有对大数据集进行优化,该如何执行,我没有testing过。 我已经用demo演示了一个示例项目: 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; } 

这可以通过使用下面的函数来完成

 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 } } 

用这个。 这当然会有所帮助。

 - (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]; } } 

这里是@Brennan接受的解决scheme的Swift 3版本,testing和认可:)

 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; }) } 

我会调整和重新定位UITableView在其父视图取决于单元格的数量。 我想这是涉及最低解决方法的解决scheme。 另外,你真的需要使用UITableView吗?

 if(indexPath.row!=CategoriesArray.count-1) { cell.hidden = YES; } return cell; 

在一个新的部分添加一个空白的单元格,并使其成为索引为零的部分。

 -(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]; 

它为我工作。 希望这可以帮助。

没有一行代码的优雅和快速的解决scheme。

使用容器视图并将UITableViewController放入容器(embeddedsegue)。

你可以设置任何你想要这个容器的高度。

我发现这样做的最好方法是观察tableview的内容大小,并在必要时调整插入。 例如:

 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]; } } 

所有的答案有dynamicrowHeight和/或animation的一些怪癖。 对我来说,最好的解决scheme是对表格(flipY)的转换:

 tableView.transform = CGAffineTransform (scaleX: 1,y: -1) 

cellForRowAt里面:

 cell.contentView.transform = CGAffineTransform (scaleX: 1,y: -1) cell.accessoryView?.transform = CGAffineTransform (scaleX: 1,y: -1) 

您也可以反转您的数据arrays,也可能翻转部分页眉/页脚。 此外,您的页脚成为您的新标题 – 但是,嘿,它的作品。