swift 单元格不足时滚动tableView

pgpifvop  于 2022-12-17  发布在  Swift
关注(0)|答案(1)|浏览(220)

我有一个UITableView,由于某些原因,我在ViewDidLoad中设置了contentInset.topcontentOffset.y

tableView.contentInset.top = 150
tableView.contentOffset.y = -150

这个概念是,当它被打开时,行从顶部开始-150点,当滚动开始时,行首先回到顶部,然后实际滚动开始(新单元格从底部出现,旧单元格在顶部消失)。
唯一的问题是,当UITableView上没有足够的单元格时,它不会滚动到顶部。我实际上不关心实际的滚动开始(新单元格从底部出现,旧单元格在顶部消失),我希望在任何情况下,对于任何数量的单元格,表格视图都像这样滚动到顶部:

tableView.contentInset.top = 0
tableView.contentOffset.y = 0

然后当没有足够的单元格时,它就不会进行实际的滚动。有什么方法可以做到这一点吗?
顺便说一句,我使用scrollViewDidScroll来平滑地上下移动它,当没有足够的单元格时想这样做

非常感谢

k7fdbhmy

k7fdbhmy1#

您要做的是,如果生成的行高小于表视图框架的高度,则设置表视图的.contentInset.bottom
我们将从一个简单的动态高度单元格(多行标签)开始:

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),
        ])
    }
}

以及具有表格视图的基本控制器,从每侧插入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
    }
    
}

运行时如下所示:

不过,到目前为止,它处于您当前的状态--我们无法向上滚动到顶部。
我们需要做的是找到一种方法来设置表视图的.contentInset.bottom

因此,我们将实现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
            }
            
        }
        
    }
    
}

现在,当我们运行它时,我们可以“滚动到顶部”:

将控制器顶部的testRowCount从5更改为6、7、8、20、30等。一旦有足够的行(或行更高),表格视图就可以滚动到顶部,* 无需 * .contentInset.bottom,我们就可以在保持150点顶部插图的同时进行“正常”滚动。
值得注意的是:上面的scrollViewDidScroll代码将在每次滚动表格时运行。理想情况下,我们只会让它运行,直到我们确定了底部偏移量(如果需要的话)。
为此,我们需要一些新的var属性和一些if测试。
下面是另一个版本的控制器,一旦我们知道需要什么,它就会停止测试:

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
    }
    
}

相关问题