简体   繁体   English

如何使 tableFooterView 始终位于 UITableView 的底部

[英]How to keep tableFooterView always on bottom of UITableView

I have a UITableView with a variable amount of sections.我有一个UITableView具有可变数量的部分。 Each section has a variable amount of cells and every section has a header and a footer.每个部分都有可变数量的单元格,每个部分都有一个页眉和一个页脚。 My UITableView also has a tableFooterView which I want to keep on the bottom of the screen at all times, except when the table is too large to fit on the screen, then the tableFooterView should be shown below the last section.我的UITableView也有一个tableFooterView ,我想一直保留在屏幕底部,除非表格太大而无法显示在屏幕上,那么tableFooterView应该显示在最后一部分的下方。 What I want to accomplish is illustrated here:我想要完成的事情如下所示:

Example of what I want, scenario 1我想要的例子,场景 1

Example of what I want, scenario 2我想要的例子,场景 2

However, currently the tableFooterView is always located right beneath the last section, so when there are for example only two sections, it looks like this:然而,目前tableFooterView总是位于最后一部分的正下方,所以当只有两个部分时,它看起来像这样:

Example of what I currently have我目前拥有的示例

I am looking for a way to keep it always at the bottom, in every possible scenario.我正在寻找一种方法,可以在任何可能的情况下始终将其保持在底部。 I have been looking around and because Apple doesn't support AutoLayout for the tableFooterView , I haven't found a solution yet.我一直在环顾四周,因为 Apple 不支持 tableFooterView 的tableFooterView ,我还没有找到解决方案。 Similar cases replace the tableFooterView with a sectionFooter on the last section, but I can't do that as I already have sectionFooters .类似的案例更换tableFooterViewsectionFooter在最后一节,但我不能这样做,因为我已经有sectionFooters

Is there anybody who can help me out or point me towards the right direction?有没有人可以帮助我或为我指明正确的方向? A couple of things to consider:需要考虑以下几点:

  • It has to be a tableFooterView ;它必须是一个tableFooterView
  • Users can add sections to the UITableView and rows to the sections, so the tableFooterView should then update its location用户可以向UITableView添加部分并向这些部分添加行,因此 tableFooterView 应该更新其位置

How I set up the tableFooterView at the moment:我目前如何设置tableFooterView

class CustomView: UITableViewDelegate, UITableViewDataSource {

    var myTableFooter: UIView = {

        let myTableFooter = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
        myTableFooter.backgroundColor = .red
        myTableFooter.isUserInteractionEnabled = true
        return myTableFooter

    }()

    override init(frame: CGRect) {

        super.init(frame: frame)
        setupViews()

        MyTableView.tableFooterView = myTableFooter

    }

}

EDIT: Tried the scrollViewDidScroll method as suggested, but didn't work:编辑:按照建议尝试了scrollViewDidScroll方法,但没有奏效:

func scrollViewDidScroll(_ scrollView: UIScrollView) {

    if(scrollView == myTableView) {

        let neededHeight = myTableView.frame.height - 50 - view.safeAreaInsets.bottom
        let currentHeight = myTableView.contentSize.height - 50

        let heightDifference = neededHeight - currentHeight

        if(heightDifference > 0) {

            myTableView.tableFooterView?.transform = CGAffineTransform(translationX: 0, y: heightDifference)

        }

    }

}

One approach would be:一种方法是:

  • use an extension to define a "self-sizing non-scrolling" table view使用扩展来定义“自调整大小非滚动”表格视图
  • embed the table view and a normal UIView for the "footer" view in a "container" view在“容器”视图中嵌入表视图和“页脚”视图的普通UIView
  • embed the container view in a scroll view, with a height equal to the scroll view but with a low priority将容器视图嵌入到滚动视图中,高度等于滚动视图但优先级较低
  • constrain the footer view to the bottom of the container view, and >= to the bottom of the table view将页脚视图约束到容器视图的底部,并将>=约束到表视图的底部

So, the "auto-height" of the tableView + the height of the footer view determines the height of the container view, which determines the .contentSize of the scroll view.所以,tableView的“auto-height”+footer view的高度决定了container view的高度,它决定了scroll view的.contentSize The footer view will "stick" to the bottom of the container view.页脚视图将“粘”到容器视图的底部。 When the scroll view has enough content, it will "push down" the footer view.当滚动视图有足够的内容时,它会“下推”页脚视图。

Example:例子:

在此处输入图片说明

在此处输入图片说明

Here is the code to create that.这是创建它的代码。 Everything is done via code... no IBOutlets needed, so just create a new view controller and assign its class to PennyWiseViewController :一切都是通过代码完成的……不需要 IBOutlets,所以只需创建一个新的视图控制器并将其类分配给PennyWiseViewController

//
//  PennyWiseViewController.swift
//
//  Created by Don Mag on 5/14/19.
//

import UIKit

final class ContentSizedTableView: UITableView {

    override var contentSize:CGSize {
        didSet {
            invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        layoutIfNeeded()
        return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
    }

}

class MyOneLabelCell: UITableViewCell {

    // very simple one-label tableView cell

    let theLabel: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.numberOfLines = 0
        return v
    }()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        contentView.addSubview(theLabel)

        NSLayoutConstraint.activate([
            theLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8.0),
            theLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8.0),
            theLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 8.0),
            theLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8.0),
            ])

    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

class PennyWiseViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    let theContainerView: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    let theScrollView: UIScrollView = {
        let v = UIScrollView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    let theTableView: ContentSizedTableView = {
        let v = ContentSizedTableView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.isScrollEnabled = false
        return v
    }()

    let theFooterView: UILabel = {
        let v = UILabel()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .red
        v.textColor = .white
        v.text = "The Footer View"
        v.textAlignment = .center
        return v
    }()

    // start with 3 sections
    // selecting the row in the first section allows adding sections
    // selecting the row in the second section allows deleting sections
    var numSections = 3

    let reuseID = "MyOneLabelCell"

    override func viewDidLoad() {
        super.viewDidLoad()

        theTableView.dataSource = self
        theTableView.delegate = self

        theTableView.register(MyOneLabelCell.self, forCellReuseIdentifier: reuseID)

        // add the views
        view.addSubview(theScrollView)
        theScrollView.addSubview(theContainerView)
        theContainerView.addSubview(theTableView)
        theContainerView.addSubview(theFooterView)

        // this will allow the container height to be at least the height of the scroll view
        // when enough content is added to the container, it will grow
        let containerHeightConstraint = theContainerView.heightAnchor.constraint(equalTo: theScrollView.heightAnchor, multiplier: 1.0)
        containerHeightConstraint.priority = .defaultLow

        NSLayoutConstraint.activate([

            // constrain scrollView to all 4 sides (safe-area)
            theScrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            theScrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
            theScrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            theScrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),

            // constrain containerView to all 4 sides of scrollView
            theContainerView.topAnchor.constraint(equalTo: theScrollView.topAnchor),
            theContainerView.bottomAnchor.constraint(equalTo: theScrollView.bottomAnchor),
            theContainerView.leadingAnchor.constraint(equalTo: theScrollView.leadingAnchor),
            theContainerView.trailingAnchor.constraint(equalTo: theScrollView.trailingAnchor),

            theContainerView.widthAnchor.constraint(equalTo: theScrollView.widthAnchor),

            // constrain tableView to top/leading/trailing of constainerView
            theTableView.topAnchor.constraint(equalTo: theContainerView.topAnchor),
            theTableView.leadingAnchor.constraint(equalTo: theContainerView.leadingAnchor),
            theTableView.trailingAnchor.constraint(equalTo: theContainerView.trailingAnchor),

            // constrain footerView >= 20 from bottom of tableView
            theFooterView.topAnchor.constraint(greaterThanOrEqualTo: theTableView.bottomAnchor, constant: 20.0),

            theFooterView.leadingAnchor.constraint(equalTo: theContainerView.leadingAnchor, constant: 0.0),
            theFooterView.trailingAnchor.constraint(equalTo: theContainerView.trailingAnchor, constant: 0.0),
            theFooterView.bottomAnchor.constraint(equalTo: theContainerView.bottomAnchor, constant: 0.0),

            theFooterView.heightAnchor.constraint(equalToConstant: 150.0),

            containerHeightConstraint,

            ])

    }


    // MARK: - Table view data source

    func numberOfSections(in tableView: UITableView) -> Int {
        return numSections
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return section < 2 ? 1 : 2
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) as! MyOneLabelCell

        switch indexPath.section {
        case 0:
            cell.theLabel.text = "Add a section"
        case 1:
            cell.theLabel.text = "Delete a section"
        default:
            cell.theLabel.text = "\(indexPath)"
        }

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        switch indexPath.section {
        case 0:
            numSections += 1
            tableView.reloadData()
        case 1:
            if numSections > 2 {
                numSections -= 1
                tableView.reloadData()
            }
        default:
            print("\(indexPath) was selected")
        }

    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "Section \(section) Header"
    }

    func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
        return "Section \(section) Footer"
    }

}

You might be able to do this by manually translating the frame of the footer view when you scroll the table.您可以通过在滚动表格时手动平移页脚视图的框架来完成此操作。 You will need to do the following:您需要执行以下操作:

  1. Set the view as a tableFooterView .将视图设置为tableFooterView
  2. Respond to the scrollViewDidScroll method of UIScrollViewDelegate.响应 UIScrollViewDelegate 的scrollViewDidScroll方法。
  3. Calculate the amount to offset the footer view and set that as a transform: tableView.tableFooterView?.transform = CGAffineTransform(translationX: 0, y: <some value>)计算偏移页脚视图的量并将其设置为转换: tableView.tableFooterView?.transform = CGAffineTransform(translationX: 0, y: <some value>)

I made a demo in Github: StickTableFooterView我在 Github 做了一个演示: StickTableFooterView

截屏

How to keep tableFooterView always on bottom of UITableView?如何使 tableFooterView 始终位于 UITableView 的底部?

  1. Creating an view as tableFooterView.创建一个视图作为 tableFooterView。
  2. Creating an view and put it into tableFooterView as a subview(Pretended tableFooterView).创建一个视图并将其作为子视图放入 tableFooterView(假装的 tableFooterView)。
  3. Setting proper layout constraints.设置适当的布局约束。
  4. Enjoy your tableFooterView ticking on bottom of UITableView.享受在 UITableView 底部打勾的 tableFooterView。
  • Becuase the pretended tableFooterView is out of the bound of its superview, need to deal with the touch event.因为伪装的tableFooterView超出了它的superview的边界,需要处理touch事件。 See ViewWithOutboundsButtons.m.请参阅 ViewWithOutb​​oundsButtons.m。

About demo关于演示

  • Red area refers the tableFooterView.红色区域指的是 tableFooterView。
  • Yellow area refers the pretended tableFooterView.黄色区域指的是假装的 tableFooterView。

-----Updated------ - - -更新 - - -

Setting proper layout constraints:设置适当的布局约束:

innerView.translatesAutoresizingMaskIntoConstraints = false;
[NSLayoutConstraint constraintWithItem:innerView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:innerView.superview attribute:NSLayoutAttributeRight multiplier:1 constant:0].active = YES;
[NSLayoutConstraint constraintWithItem:innerView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:innerView.superview attribute:NSLayoutAttributeLeft multiplier:1 constant:0].active = YES;
[NSLayoutConstraint constraintWithItem:innerView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:innerView.superview attribute:NSLayoutAttributeBottom multiplier:1 constant:0].active = YES;
[NSLayoutConstraint constraintWithItem:innerView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self.tableView.superview attribute:NSLayoutAttributeBottom multiplier:1 constant:0].active = YES;
[NSLayoutConstraint constraintWithItem:innerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:innerViewHeight].active = YES;

The key point is set layout constraint with tableView.superview :关键点是使用tableView.superview设置布局约束:

[NSLayoutConstraint constraintWithItem:innerView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self.tableView.superview attribute:NSLayoutAttributeBottom multiplier:1 constant:0].active = YES;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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