简体   繁体   English

当没有足够的单元格时滚动 tableView

[英]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.topcontentOffset.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.

相关问题 使用行动画添加单元格时,将tableview保持在相同的滚动位置 - Keep tableview in same scroll position when adding cells with row animation 当我在 swift iOS 中垂直滚动时,Tableview 单元格数据正在发生变化 - Tableview cells data is changing when I scroll vertically in swift iOS 当我在其上进行滚动并在其底部完成时,将单元格添加到我的tableView中 - Add cells to my tableView when I make the scroll on it and finish at the bottom of it 当我滚动查看tableview时,活动的tableView单元格不断被禁用 - Active tableView cells keep getting disabled when I scroll through my tableview 限制tableView中显示的单元格数量,滚动到最后一个单元格时加载更多单元格 - Limit the amount of cells shown in tableView, load more cells when scroll to last cell iOS:带有自定义单元格的Tableview不会滚动 - iOS: Tableview with custom cells does not scroll 当我滚动表格视图时,单元格的某些文本会更改颜色 - some text of my cells changes color when I scroll my tableview 如果我为tableview单元格设置动画,则表格视图滚动不会停止 - Tableview scroll is not stopping on tap if i animate tableview cells tableView单元格内的多个collectionView单元格在滚动上更改设计 - multiple collectionView cells inside tableView cells changing design on scroll 以编程方式将视图添加到tableView单元格中,从而使滚动变得混乱而缓慢 - Adding views programmatically into tableView cells making scroll jerky and slow
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM