【问题标题】:Implementing Sticky Cell in UICollectionView or UITableView在 UICollectionView 或 UITableView 中实现 Sticky Cell
【发布时间】:2021-06-02 14:19:03
【问题描述】:

我要实现一个包含项目列表的表格,其中包括一个应该始终显示在屏幕上的项目。所以,例如:

  • 列表中有 50 个项目
  • 您的“粘性”列表项是第 25 个
  • 您有 10 个项目可以同时显示在屏幕上
  • 尽管您在列表中的位置,“粘性”列表应始终保持可见
  • 如果您的项目低于您在列表中的位置,它将显示在列表底部
  • 如果您的项目介于之前的项目之间,它应该显示在列表顶部
  • 一旦您到达您的项目在列表中的实际位置,它应该与列表的滚动一起移动

为了更好地理解实施要求,以下是插图:

对于如何实现这一点的任何可能的想法、建议或建议,我们将很高兴。不幸的是,我没有找到任何有用的库或解决方案来解决这个问题。对于这种情况,UICollectionView 和 UITableView 都是可以接受的。

据我了解,粘性页眉或页脚在这种情况下不起作用,因为它们只涵盖了我需要的一半功能。

提前感谢您的cmets和答案!!!

【问题讨论】:

    标签: ios swift uitableview uicollectionview sticky


    【解决方案1】:

    我很确定您实际上不能让相同的实际单元格像那样粘。不过,您可以通过自动布局技巧来制造粘性错觉。基本上,我的建议是,您可以拥有与您想要“粘性”的单元格相同的视图,并将它们限制在粘性单元格的顶部,同时您的粘性单元格可见。如果你慢慢滚动,我能做到的最好的看起来并不完美。 (在捕捉到顶部或底部位置之前,粘性单元格大部分会离开屏幕。在我看来,在相当正常的滚动速度下它并不明显。您的里程可能会有所不同。)

    关键是设置一个表格视图委托,这样您就可以收到有关单元格何时会或不会出现在屏幕上的通知。

    我已经包含了一个示例视图控制器。我确定在某些地方我的示例代码不起作用。 (例如,我没有处理堆叠多个“粘性”单元格或动态行高。另外,我将粘性单元格设为蓝色,以便更容易看到粘性。)

    为了运行示例代码,如果您创建一个新的 UIKit 应用程序,您应该能够将其粘贴到 Xcode 生成的默认项目中。只需用这个替换他们给你的视图控制器,看看它的实际效果。

    import UIKit
    
    struct StickyView {
        let view: UIView
        let constraint: NSLayoutConstraint
    }
    
    class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
        lazy var stickyViewConstraints = [Int: StickyView]()
        
        lazy var tableView: UITableView = {
            let table = UITableView()
            table.translatesAutoresizingMaskIntoConstraints = false
            table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
            table.rowHeight = 40
            table.dataSource = self
            table.delegate = self
            return table
        }()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            addTable()
            setupStickyViews()
        }
        
        private func addTable() {
            view.addSubview(tableView)
            tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        }
        
        private func setupStickyViews() {
            let cell25 = UITableViewCell()
            cell25.translatesAutoresizingMaskIntoConstraints = false
            cell25.backgroundColor = .blue
            cell25.textLabel?.text = "25"
            view.addSubview(cell25)
            cell25.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
            cell25.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
            cell25.heightAnchor.constraint(equalToConstant: tableView.rowHeight).isActive = true
            
            let bottom = cell25.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
            bottom.isActive = true
            stickyViewConstraints[25] = StickyView(view: cell25, constraint: bottom)
        }
    
        // MARK: - Data Source
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return section == 0 ? 50 : 0
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
            cell.textLabel?.text = "\(indexPath.row)"
            return cell
        }
        
        // MARK: - Delegate
        func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            guard let stickyView = stickyViewConstraints[indexPath.row] else { return }
            stickyView.constraint.isActive = false
            var verticalConstraint: NSLayoutConstraint
            if shouldPlaceStickyViewAtTop(stickyRow: indexPath.row) {
                verticalConstraint = stickyView.view.topAnchor.constraint(equalTo: view.topAnchor)
            } else {
                verticalConstraint = stickyView.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            }
            verticalConstraint.isActive = true
            stickyViewConstraints[indexPath.row] = StickyView(view: stickyView.view, constraint: verticalConstraint)
        }
        
        private func shouldPlaceStickyViewAtTop(stickyRow: Int) -> Bool {
            let visibleRows = tableView.indexPathsForVisibleRows?.map(\.row)
            guard let min = visibleRows?.min() else { return false }
            return min > stickyRow
        }
        
        func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
            if let stickyView = stickyViewConstraints[indexPath.row] {
                stickyView.constraint.isActive = false
                let bottom = stickyView.view.bottomAnchor.constraint(equalTo: cell.bottomAnchor)
                bottom.isActive = true
                stickyViewConstraints[indexPath.row] = StickyView(view: stickyView.view, constraint: bottom)
            }
        }
    }
    

    【讨论】:

    • 感谢您的解决方案。我不得不稍微调整一下,但总的来说你的想法奏效了!
    • 要解决"sticky cell going mostly off the screen before snapping to the top or bottom position" 的困境,您可以在上面的单元格离开屏幕时显示粘滞视图,因为那是所选视图到达边缘的时候。
    猜你喜欢
    • 2020-09-17
    • 1970-01-01
    • 2017-11-14
    • 1970-01-01
    • 2015-03-09
    • 1970-01-01
    • 2012-12-25
    • 2014-11-04
    • 1970-01-01
    相关资源
    最近更新 更多