如何让 tableFooterView 始终位于 UITableView 的底部
Posted
技术标签:
【中文标题】如何让 tableFooterView 始终位于 UITableView 的底部【英文标题】:How to keep tableFooterView always on bottom of UITableView 【发布时间】:2019-05-14 14:20:38 【问题描述】:我有一个UITableView
,其中包含可变数量的部分。每个部分都有可变数量的单元格,每个部分都有页眉和页脚。我的UITableView
也有一个tableFooterView
,我想一直保持在屏幕底部,除非表格太大而无法放在屏幕上,那么tableFooterView
应该显示在最后一部分下方.这里说明了我想要完成的事情:
Example of what I want, scenario 1
Example of what I want, scenario 2
但是,目前tableFooterView
总是位于最后一个部分的正下方,因此例如只有两个部分时,它看起来像这样:
Example of what I currently have
我正在寻找一种方法,使其在所有可能的情况下始终保持在底部。我一直在环顾四周,因为 Apple 不支持 tableFooterView
的 AutoLayout,所以我还没有找到解决方案。类似的情况在最后一部分用sectionFooter
替换tableFooterView
,但我不能这样做,因为我已经有sectionFooters
。
有没有人可以帮助我或为我指明正确的方向?有几点需要考虑:
必须是tableFooterView
;
用户可以将部分添加到UITableView
并将行添加到部分,因此 tableFooterView 应随后更新其位置
我现在如何设置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
编辑:按照建议尝试了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)
【问题讨论】:
您是否考虑过切换到 UICollectionView 并进行自定义布局? 这是一个选择。但是我已经有一个 UICollectionView 作为我的主视图,UICollectionView 中有两个单元格,每个单元格都包含一个 UITableView。我相信应该有一种方法可以用 UITableView 完成我想要的。 First...“Apple 不支持tableFooterView
的 AutoLayout” --- 是和否。它需要额外的一两个步骤,但您当然可以在 tableFooterView
中使用 AutoLayout ... 但是,这可能无法解决您的问题(例如,您不能在其他地方限制页脚视图)。一种选择是使用嵌入在滚动视图中的“自动调整大小”tableView,同时在滚动视图中嵌入标准的“footerView”UIView
。
在tableview下面添加一个视图
我很乐意,@RajeshKumarR。但我不知道当 UITableView 太大而无法在屏幕上显示时它会如何跟随它的内容。有示例代码吗?
【参考方案1】:
一种方法是:
使用扩展来定义“自动调整大小的非滚动”表格视图 在“容器”视图中嵌入表格视图和“页脚”视图的普通UIView
将容器视图嵌入到滚动视图中,高度与滚动视图相同,但优先级较低
将页脚视图限制在容器视图的底部,将>=
限制在表格视图的底部
所以,tableView的“auto-height”+footer view的高度决定了container view的高度,也就决定了scroll view的.contentSize
。页脚视图将“粘”到容器视图的底部。当滚动视图有足够的内容时,它会“下推”页脚视图。
例子:
这是创建它的代码。一切都通过代码完成...不需要 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"
【讨论】:
哇!这看起来很有希望。晚饭后我会研究这个,非常感谢你到目前为止所做的努力。我会告诉你进展如何。 嗨伙计,我目前正在我现有的项目中实现这个,它看起来很有希望。我仍然需要做一些改变,但我相信这应该会奏效。但是,当我插入新行或删除行时,我使用持续时间为 0.25 秒的动画。由于内容大小无效,动画看起来不太好。知道如何解决这个问题吗? 所以我目前正在玩这个解决方案。到目前为止,我还没有找到正确动画行/节插入或删除的方法。在 TableView 本身上插入似乎很好,而删除行/部分会使内容“跳跃”。看起来 contentSize 是在动画完成之前设置的,所以动画有点消失了。 TableFooterView 也没有动画,只是移动(落)到它应该“结束”的地方。知道这是否可以解决? @PennyWise - tableView 可能不是要走的路......因为您希望表格视图垂直扩展以适应行数,所以您没有重用单元格,所以您可以简单地使用自定义UIView
(而不是UITableViewCell
)并操纵约束——或者将它们放在堆栈视图中。
我绝对需要一个 UITableView。现在,我在添加或删除单元格或部分(方法 insertSections、deleteSections、insertRows、deleteRows)时删除了所有动画,它可以根据需要工作。我唯一不知道的是如何在更新intrinsicContentSize 时执行动画。我可能暂时不使用动画。也尝试过使用 heightAnchor 约束但没有成功。【参考方案2】:
您可以通过在滚动表格时手动平移页脚视图的框架来做到这一点。您需要执行以下操作:
-
将视图设置为
tableFooterView
。
响应UIScrollViewDelegate的scrollViewDidScroll
方法。
计算偏移页脚视图的量并将其设置为转换:tableView.tableFooterView?.transform = CGAffineTransform(translationX: 0, y: <some value>)
【讨论】:
试过了,但无法正常工作。框架没有更新,但所有检查都通过了(做了一些打印来测试是否存在某种错误)。我将在 OP 中添加我的代码以供参考。【参考方案3】:我在 Github 做了一个演示:StickTableFooterView
如何让tableFooterView 始终在UITableView 的底部?
-
将视图创建为 tableFooterView。
创建一个视图并将其作为子视图(Pretended tableFooterView)放入 tableFooterView。
设置适当的布局约束。
享受您的 tableFooterView 在 UITableView 底部的滴答声。
关于演示
红色区域指的是 tableFooterView。 黄色区域指的是伪装的 tableFooterView。-----更新------
设置适当的布局约束:
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;
关键是用tableView.superview
设置布局约束:
[NSLayoutConstraint constraintWithItem:innerView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:self.tableView.superview attribute:NSLayoutAttributeBottom multiplier:1 constant:0].active = YES;
【讨论】:
不是链接到存储库,您能否将相关代码粘贴到您的答案中并解释它是如何解决问题的? @C.Peck,当然,添加一些解释。以上是关于如何让 tableFooterView 始终位于 UITableView 的底部的主要内容,如果未能解决你的问题,请参考以下文章
网页CSS定位问题 ,如何让块bottom始终位于页面最下方了?
在 iOS 中,如何创建一个始终位于所有其他视图控制器之上的按钮?