Ios 在UICollectionView或UITableView中实现粘性单元格

Ios 在UICollectionView或UITableView中实现粘性单元格,ios,swift,uitableview,uicollectionview,sticky,Ios,Swift,Uitableview,Uicollectionview,Sticky,我将实现一个表,其中包含一个项目列表,该项目应该始终在屏幕上。例如: 列表中有50项 您的“粘性”列表项是25 您有10个项目可以一次显示在屏幕上 尽管您在列表中的位置不同,“粘性”列表应始终可见 如果您的项目低于您在列表中的位置,它将显示在列表的底部 如果您的项目介于之前的项目之间,则应显示在列表顶部 一旦你到达你的项目在列表中的真实位置,它应该随着列表的滚动一起移动 以下是更好地理解实施要求的插图: 对于如何实施该计划,我们将乐于听取任何可能的想法、建议或建议。不幸的是,我没有找到任何

我将实现一个表,其中包含一个项目列表,该项目应该始终在屏幕上。例如:

  • 列表中有50项
  • 您的“粘性”列表项是25
  • 您有10个项目可以一次显示在屏幕上
  • 尽管您在列表中的位置不同,“粘性”列表应始终可见
  • 如果您的项目低于您在列表中的位置,它将显示在列表的底部
  • 如果您的项目介于之前的项目之间,则应显示在列表顶部
  • 一旦你到达你的项目在列表中的真实位置,它应该随着列表的滚动一起移动
以下是更好地理解实施要求的插图:

对于如何实施该计划,我们将乐于听取任何可能的想法、建议或建议。不幸的是,我没有找到任何有用的库或解决方案来解决这个问题。对于这种情况,UICollectionView和UITableView都是可以接受的

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


提前感谢您的评论和回答

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

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

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

为了运行示例代码,如果您创建了一个新的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)
        }
    }
}

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

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

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

为了运行示例代码,如果您创建了一个新的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)
        }
    }
}

谢谢你的解决方案。我不得不稍微调整一下,但总的来说你的想法奏效了!要解决“粘贴单元格在捕捉到顶部或底部位置之前大部分离开屏幕”的难题,您可以在上面的单元格离开屏幕后显示粘贴视图,因为此时选定的视图到达边缘。谢谢您提供的解决方案。我不得不稍微调整一下,但总的来说你的想法奏效了!要解决“粘贴单元格在捕捉到顶部或底部位置之前大部分离开屏幕”的难题,您可以在上面的单元格离开屏幕后显示粘贴视图,因为此时选定的视图到达边缘。