简体   繁体   中英

How to align UITableViewCell to the bottom of the UITableView?

When you insert your first UITableViewCell with insertRowsAtIndexPaths:withRowAnimation: , it usually appears at the top of the UITableView . In the Periscope app , the opposite happens - the first inserted cell is bottom aligned. As new cells are pushed in, the old cells move up in the table. How is this achieved?

在此处输入图片说明

In case you're interested in how I did it in the Periscope iOS app, it's actually pretty simple...

TL;DR; Add a transparent table header header view with a height equal to your table view frame's height. Then, as you add cells to your table, simply animate the table view's content offset.

Give your table view a header view:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    UIView *headerView = [[UIView alloc] initWithFrame:CGRectZero];
    headerView.userInteractionEnabled = NO; // all touches within this space must go through to the video layer

    return headerView;   // empty header above chat, so messages flow in from bottom
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return self.tableView.frame.size.height;            // empty header above chat, so messages flow in from bottom
}

Add data to your table (in my case, messages get added to an array called _messages. I then call reloadData on the UITableViewController). Then call this method to animate the cells in:

- (void)scrollTableToBottom
{
    if (!self.isViewLoaded || _messages.count == 0)
        return;

    CGFloat offsetY = self.tableView.contentSize.height - self.tableView.frame.size.height + self.tableView.contentInset.bottom;

    [UIView animateWithDuration:0.33
            delay:0
            options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionAllowUserInteraction
            animations:^{
                [self.tableView setContentOffset:CGPointMake(0, offsetY) animated:NO];
            }
            completion:nil];
}

Hope that helps. I found this to be a pretty cheap/simple way of simulating cells anchored to the bottom. I know some people have mentioned flipping the table upside down, but that just seems crazy to me. :-)

Swift 4 version of Aaron Wasserman answer.

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let headerView = UIView(frame: .zero)
    headerView.isUserInteractionEnabled = false
    return headerView
}

// Added logic to avoid blank space at the top
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return tableView.frame.size.height - CGFloat(messages.count * cellHeight)
}

 func scrollToBottom(animated: Bool) {
    if isViewLoaded && messages.count > 0 {

        let offset = tableView.contentSize.height - tableView.frame.size.height + tableView.contentInset.bottom

        UIView.animate(withDuration: 0.33, delay: 0, options: [.curveEaseOut, .allowUserInteraction], animations: {
            self.tableView.setContentOffset(CGPoint(x: 0, y: offset), animated: animated)
        }, completion: nil)
    }
}

scrollToBottom method needs to be called after tableView.reloadData()

My modification of the mr.Wasserman answer

extension MyViewController : UITableViewDelegate {

  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
      return UITableView.automaticDimension
  }

  func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
      let headerView = UIView(frame: .zero)
      headerView.isUserInteractionEnabled = false
      return headerView
  }

  func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
      let diff = tableView.contentSize.height - tableView.bounds.height
      return diff > 0 ? 0 : -diff
  }

}

You can use the insertRowsAtIndexPath method, and then get the tableview to scroll to bottom.

[self.tableView insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.posts.count-1 inSection:0]] withRowAnimation:UITableViewRowAnimationBottom];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

If you wish to have that giant gap at the top of your tableview you can set the scroll offset, and adjust this as new items come in.

Another way, would be to flip the tableview upside down, and then flip each row upside down.

Bit late to the party, but here's another approach. It can be done without having to add arbitrary header views. Adjust the content offset whenever the content size changes.

class BottomEndianTableView: UITableView {

    private var observer: Any?

    override init(frame: CGRect, style: UITableView.Style) {
        super.init(frame: frame, style: style)
        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    private func commonInit() {
        observer = observe(\.contentSize) { _, _ in
            DispatchQueue.main.async { [weak self] in
                self?.scrollToEnd(animated: false)
            }
        }
    }

    func scrollToEnd(animated: Bool) {
        let scrollDistance = contentSize.height - frame.height
        setContentOffset(CGPoint(x: 0, y: scrollDistance), animated: animated)
    }

}

You can also call scrollToEnd(animated: true) in response to the keyboard displaying

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM