在 UICollectionView 或 UITableView 中实现 Sticky Cell
Implementing Sticky Cell in UICollectionView or UITableView
我要实现一个 table 项目列表,其中包括一个应该始终显示在屏幕上的项目。所以,例如:
- 列表中有 50 项
- 您的“置顶”列表项是第 25 个
- 您有 10 个项目可以一次显示在屏幕上
- 无论您在列表中的位置如何,“置顶”列表都应始终可见
- 如果您的项目低于您在列表中的位置,它将显示在列表底部
- 如果您的项目介于之前的项目之间,它应该显示在列表的顶部
- 一旦到达项目在列表中的实际位置,它应该会随着列表的滚动一起移动
为了更好地理解实施要求,以下是插图:
对于任何可能的想法、建议或关于如何实施的建议,我们将很高兴。不幸的是,我没有找到任何有用的库或解决方案来解决这个问题。对于这种情况,UICollectionView 和 UITableView 都是 acceptable。
根据我的理解,粘性页眉或页脚在这种情况下不起作用,因为它们只涵盖了我需要的一半功能。
提前感谢您的评论和回答!!!
我很确定你实际上不能让相同的实际细胞像那样粘。不过,您可以通过自动布局技巧来创造粘性的错觉。基本上,我的建议是您可以拥有与您想要“粘性”的单元格相同的视图,并在您的粘性单元格可见时将它们约束在您的粘性单元格之上。如果你慢慢滚动,我能做到的最好的看起来并不完美。 (在捕捉到顶部或底部位置之前,粘性单元格大部分离开屏幕。在我看来,在相当正常的滚动速度下它并不明显。您的里程可能会有所不同。)
关键是设置一个 table 视图委托,这样您就可以收到有关单元格何时出现或不出现在屏幕上的通知。
我提供了一个示例视图控制器。我确信我的示例代码在某些地方不起作用。 (例如,我没有处理堆叠多个“粘性”单元格或动态行高。另外,我将我的粘性单元格设为蓝色以便更容易看到粘性。)
为了 运行 示例代码,如果您创建新的 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)
}
}
}
我要实现一个 table 项目列表,其中包括一个应该始终显示在屏幕上的项目。所以,例如:
- 列表中有 50 项
- 您的“置顶”列表项是第 25 个
- 您有 10 个项目可以一次显示在屏幕上
- 无论您在列表中的位置如何,“置顶”列表都应始终可见
- 如果您的项目低于您在列表中的位置,它将显示在列表底部
- 如果您的项目介于之前的项目之间,它应该显示在列表的顶部
- 一旦到达项目在列表中的实际位置,它应该会随着列表的滚动一起移动
为了更好地理解实施要求,以下是插图:
对于任何可能的想法、建议或关于如何实施的建议,我们将很高兴。不幸的是,我没有找到任何有用的库或解决方案来解决这个问题。对于这种情况,UICollectionView 和 UITableView 都是 acceptable。
根据我的理解,粘性页眉或页脚在这种情况下不起作用,因为它们只涵盖了我需要的一半功能。
提前感谢您的评论和回答!!!
我很确定你实际上不能让相同的实际细胞像那样粘。不过,您可以通过自动布局技巧来创造粘性的错觉。基本上,我的建议是您可以拥有与您想要“粘性”的单元格相同的视图,并在您的粘性单元格可见时将它们约束在您的粘性单元格之上。如果你慢慢滚动,我能做到的最好的看起来并不完美。 (在捕捉到顶部或底部位置之前,粘性单元格大部分离开屏幕。在我看来,在相当正常的滚动速度下它并不明显。您的里程可能会有所不同。)
关键是设置一个 table 视图委托,这样您就可以收到有关单元格何时出现或不出现在屏幕上的通知。
我提供了一个示例视图控制器。我确信我的示例代码在某些地方不起作用。 (例如,我没有处理堆叠多个“粘性”单元格或动态行高。另外,我将我的粘性单元格设为蓝色以便更容易看到粘性。)
为了 运行 示例代码,如果您创建新的 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)
}
}
}