如何在屏幕底部定位UITableViewCell?

时间:2013-03-08 02:28:57

标签: ios cocoa-touch uitableview

UITableViewCell中有一到三个UITableViewView。有没有办法在reloadData之后始终将单元格放在屏幕底部?

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

14 个答案:

答案 0 :(得分:13)

我已经创建了一个新的示例解决方案,因为之前的答案并不过时,不适合现代使用。

最新技术使用autolayout和self-sizing单元格,因此之前的答案将不再有效。我重新设计了解决方案以使用现代功能,并创建了一个示例项目来放置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) {
    }];
}

答案 1 :(得分:10)

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

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

答案 2 :(得分:5)

您可以在表格视图中设置标题,并使其足够高以推动第一个单元格。然后相应地设置tableView的contentOffset。我不认为有一种快速的内置方式可以做到这一点。

答案 3 :(得分:2)

这可以通过快速使用以下功能来完成

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

答案 4 :(得分:2)

我不喜欢空单元格,基于contentInsettransform的解决方案,而是提出了其他解决方案:

Example

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

它适用于我的案例,显然,可以通过某种方式缓存内容大小高度来优化。另外,我不确定如果没有对大数据集进行优化会如何执行,我没有测试。我已经将示例项目与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;
}

答案 5 :(得分:2)

以下是@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 :(得分:1)

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

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

答案 7 :(得分:0)

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

return cell;

答案 8 :(得分:0)

我会根据单元格的数量调整UITableView并将其重新定位在其父视图中。我想这是解决方案涉及最小的解决方法。 另外,你真的需要使用UITableView吗?

答案 9 :(得分:0)

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

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

它对我有用。希望这会有所帮助。

答案 10 :(得分:0)

没有一行代码的优雅而快速的解决方案。

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

您可以为此容器设置所需的任何高度。

答案 11 :(得分:0)

我发现这样做的最佳方法是观察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];
    }
}

答案 12 :(得分:0)

所有答案都有动态rowHeight和/或动画的怪癖。 对我来说,最好的解决方案是转换表格(flipY):

cellForRowAt

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

  public AudioTrack create() {
    if (mTrack != null) {
        if (mTrack.getState() == AudioTrack.STATE_INITIALIZED) {
            return mTrack;
        } else {
            mTrack.release();
            mTrack = null;
        }
    }

    mBuffSize = AudioTrack.getMinBufferSize(Settings.SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT);
    mTrack = new AudioTrack(AudioManager.STREAM_MUSIC, Settings.SAMPLE_RATE,
            AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, mBuffSize, AudioTrack.MODE_STREAM);
    if (mTrack.getState() == AudioTrack.STATE_INITIALIZED) {
        mTrack.play();
        return mTrack;
    }
    return null;
}

您也可以反转数据数组,也可以翻转section-header / footer。 你的页脚也会成为你的新标题 - 但是,嘿,它有效。

答案 13 :(得分:0)

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)