[英]Scroll tableView when there is no enough cells
I have a UITableView
that for some reasons, I set a contentInset.top
and contentOffset.y
on it in the ViewDidLoad
我有一个
UITableView
,由于某些原因,我在ViewDidLoad
中设置了contentInset.top
和contentOffset.y
tableView.contentInset.top = 150
tableView.contentOffset.y = -150
The concept is, when the it gets opened, the rows start -150 point from the top, and when scroll begins, the rows come back to the top first, and then the actual scrolling starts (new cells appears from bottom and old cell disappear in the top).这个概念是,当它打开时,行从顶部开始 -150 点,当滚动开始时,行首先回到顶部,然后实际滚动开始(新单元格从底部出现,旧单元格消失前几名)。
The only issue is, when there isn't enough cell on the UITableView
, it won't scroll to back to the top.唯一的问题是,当
UITableView
上没有足够的单元格时,它不会滚动回到顶部。 I actually don't care about the actual scrolling starts (new cells appears from bottom and old cell disappear in the top), I want that in any case with any number of cells, the table view scroll to the top like that:我实际上并不关心实际的滚动开始(新单元格从底部出现,旧单元格从顶部消失),我希望在任何情况下使用任意数量的单元格,表格视图像这样滚动到顶部:
tableView.contentInset.top = 0
tableView.contentOffset.y = 0
and then when there is no enough cell, it won't go for the actual scrolling.然后当没有足够的单元格时,它不会进行实际的滚动。 Is there any way to do that?
有什么办法吗?
BTW, I use scrollViewDidScroll to smoothly move it up and down with user finger, want to do that when there is no enough cell顺便说一句,我使用 scrollViewDidScroll 用用户手指平滑地上下移动它,想在没有足够的单元格时这样做
Thank you so much太感谢了
What you want to do is set the table view's .contentInset.bottom
if the resulting height of the rows is less than the height of the table view's frame.如果行的结果高度小于表格视图框架的高度,您要做的是设置表格视图的
.contentInset.bottom
。
We'll start with a simple dynamic height cell (a multiline label):我们将从一个简单的动态高度单元格(多行标签)开始:
class DynamicHeightCell: UITableViewCell {
let theLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
return v
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
theLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(theLabel)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
theLabel.topAnchor.constraint(equalTo: g.topAnchor),
theLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor),
theLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor),
theLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor),
])
}
}
and a basic controller with a table view, inset by 40-points from each side:和一个带有表格视图的基本控制器,从每一侧插入 40 个点:
class TableInsetVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
// set the number of rows to use
// once we get past 7 (or so), the rows will be
// taller than the tableView frame
let testRowCount = 5
let tableView: UITableView = {
let v = UITableView()
return v
}()
// we'll cycle through colors for the cell backgrounds
// to make it easier to see the cell frames
let bkgColors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue, .systemYellow, .systemCyan, .systemBrown,
]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
[tableView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
])
tableView.register(DynamicHeightCell.self, forCellReuseIdentifier: "c")
tableView.dataSource = self
tableView.delegate = self
// so we can see the tableView frame
tableView.backgroundColor = .lightGray
tableView.contentInset.top = 150
tableView.contentOffset.y = -150
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return testRowCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! DynamicHeightCell
// we want dynamic height cells, so
var s = "Row: \(indexPath.row + 1)"
// to make it easy to see it's the last row
if indexPath.row == tableView.numberOfRows(inSection: 0) - 1 {
s += " --- Last Row"
}
// fill cells with 1 to 4 rows of text
for i in 0..<(indexPath.row % 4) {
s += "\nThis is Line \(i + 2)"
}
c.theLabel.text = s
// cycle background color to make it easy to see the cell frames
c.contentView.backgroundColor = bkgColors[indexPath.row % bkgColors.count]
return c
}
}
It looks like this when run:运行时看起来像这样:
So far, though, it's in your current condition -- we can't scroll up to the top.不过,到目前为止,它处于您当前的状态——我们无法向上滚动到顶部。
What we need to do is find a way to set the table view's .contentInset.bottom
:我们需要做的是找到一种方法来设置表视图的
.contentInset.bottom
:
So, we'll implement scrollViewDidScroll(...)
:所以,我们将实现
scrollViewDidScroll(...)
:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// unwrap optional
if let rows = tableView.indexPathsForVisibleRows {
// get indexPath of final cell
let n = tableView.numberOfRows(inSection: 0)
let lastRowIndexPath = IndexPath(row: n - 1, section: 0)
// if final cell is visible
if rows.contains(lastRowIndexPath) {
// we now know the tableView's contentSize, so
// if .contentSize.height is less than tableView.frame.height
if tableView.contentSize.height < tableView.frame.height {
// calculate and set bottom inset
tableView.contentInset.bottom = tableView.frame.height - tableView.contentSize.height
}
}
}
}
Now when we run that, we can "scroll to the top":现在当我们运行它时,我们可以“滚动到顶部”:
Change the testRowCount
at the top of the controller from 5 to 6, 7, 8, 20, 30, etc. Once there are enough rows (or the rows are taller) so the table view can scroll to the top without the .contentInset.bottom
we get "normal" scrolling while maintaining the 150-point top inset.将控制器顶部的 testRowCount 从 5 更改为
testRowCount
等。一旦有足够的行(或行更高),表视图就可以滚动到顶部而无需.contentInset.bottom
我们得到“正常”滚动,同时保持 150 点顶部插图。
Worth noting: the above scrollViewDidScroll
code will end up running every time the table is scrolled.值得注意的是:上面的
scrollViewDidScroll
代码将在每次滚动表格时结束运行。 Ideally, we would only let it run until we've determined the bottom offset (if one is needed).理想情况下,我们只会让它运行,直到我们确定了底部偏移量(如果需要的话)。
To do that, we need a couple new var properties and some if
testing.为此,我们需要一些新的 var 属性和一些
if
测试。
Here's another version of that controller that stops testing once we know what's needed:这是该控制器的另一个版本,一旦我们知道需要什么,它就会停止测试:
class TableInsetVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
// we'll use this for both the .contentInset.bottom
// AND to stop testing the height when we've determined whether it's needed or not
var bottomInset: CGFloat = -1
// we don't want to start testing the height until AFTER initial layout has finished
// so we'll use this as a flag
var hasAppeared: Bool = false
// set the number of rows to use
// once we get past 7 (or so), the rows will be
// taller than the tableView frame
let testRowCount = 5
let tableView: UITableView = {
let v = UITableView()
return v
}()
// we'll cycle through colors for the cell backgrounds
// to make it easier to see the cell frames
let bkgColors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue, .systemYellow, .systemCyan, .systemBrown,
]
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
[tableView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
])
tableView.register(DynamicHeightCell.self, forCellReuseIdentifier: "c")
tableView.dataSource = self
tableView.delegate = self
// so we can see the tableView frame
tableView.backgroundColor = .lightGray
tableView.contentInset.top = 150
tableView.contentOffset.y = -150
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
hasAppeared = true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// initial layout has finished, AND we have not yet changed bottomInset
if hasAppeared, bottomInset == -1 {
// unwrap optional
if let rows = tableView.indexPathsForVisibleRows {
// get indexPath of final cell
let n = tableView.numberOfRows(inSection: 0)
let lastRowIndexPath = IndexPath(row: n - 1, section: 0)
// if final cell is visible
if rows.contains(lastRowIndexPath) {
// we now know the tableView's contentSize, so
// if .contentSize.height is less than tableView.frame.height
if tableView.contentSize.height < tableView.frame.height {
// calculate and set bottom inset
bottomInset = tableView.frame.height - tableView.contentSize.height
tableView.contentInset.bottom = bottomInset
} else {
// .contentSize.height is greater than tableView.frame.height
// so we don't set .contentInset.bottom
// and we set bottomInset to -2 so we stop testing
bottomInset = -2
}
} else {
// final cell is not visible, so
// if we have scrolled up past the top,
// we know the full table is taller than the tableView
// and we set bottomInset to -2 so we stop testing
if tableView.contentOffset.y >= 0 {
bottomInset = -2
}
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return testRowCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "c", for: indexPath) as! DynamicHeightCell
// we want dynamic height cells, so
var s = "Row: \(indexPath.row + 1)"
// to make it easy to see it's the last row
if indexPath.row == tableView.numberOfRows(inSection: 0) - 1 {
s += " --- Last Row"
}
// fill cells with 1 to 4 rows of text
for i in 0..<(indexPath.row % 4) {
s += "\nThis is Line \(i + 2)"
}
c.theLabel.text = s
// cycle background color to make it easy to see the cell frames
c.contentView.backgroundColor = bkgColors[indexPath.row % bkgColors.count]
return c
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.