将 UICollectionView 与 UITableview 同步
Synchronize UICollectionView with UITableview
我正在开发一个 UI,它有一个 UICollectionView,它可以水平滚动,下面有一个 UITableView:
我的目标是,每当我在 TableView 中滚动时,CollectionView 将以与 Tableview 完全相同的速度和相同的 amount/direction 移动,反之亦然。
所以我需要以某种方式连接它们。我尝试获取 myTableView.contentOffset.y
并将此值放入 CollectionView myCollectionView.setContentOffset(CGPoint(x: offset, y: 0), animated: true)
.
然而,这个函数需要以尽可能短的间隔一直被调用,而且它不是真正的同步,也不流畅和快速。
有没有其他的可能实现这个?
感谢任何意见!
“这个函数需要在最短的可能间隔内一直被调用” 不是真的。您只需要在 tableView(或 collectionView)scrolled.
时同步滚动
为此,实施 scrollViewDidScroll
。
但是,您将有几个问题需要处理...
首先,除非您的行的高度与单元格的宽度相同,否则您不能直接使用 .contentOffset.y
。
例如,如果您的行高 50 磅,单元格宽 100 磅,当您向下滚动 10 行时,.contentOffset.y
将为 500。如果您设置 .contentOffset.x
在你的 collectionView 上设置为 500,它只会滚动 5 个单元格而不是 10 个。
因此,您需要使用偏移量 百分比 -- 计算起来并不难。
然而...
根据您发布的图片,您显示了大约 13 行和大约 3 个单元格。当您向下滚动时,转换为百分比偏移量,当第 2 行位于顶部时,单元格 2 将位于左边缘。当第 3 行位于顶部时,单元格 3 将位于左边缘。等等……太棒了。
直到您一直向下滚动。现在,如果您总共有 100 行,则顶行将是第 87 行,并且您的 collectionView 中的可见单元格将是 87、88 和 89 ...无法到达单元格 90 到 100。
这是一些显示同步滚动的示例代码 和 显示当您到达 table 底部时的问题(使用 50 个数据项):
class CvTvViewController: UIViewController {
var myData: [String] = []
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue,
.systemPink, .systemYellow, .systemTeal,
]
var collectionView: UICollectionView!
var tableView: UITableView!
var tableViewActive: Bool = false
var cvCellWidth: CGFloat = 120
let tvRowHeight: CGFloat = 50
override func viewDidLoad() {
super.viewDidLoad()
// fill myData array with 50 strings
myData = (1...50).map { "Data: \([=10=])" }
let cvl = UICollectionViewFlowLayout()
cvl.itemSize = CGSize(width: cvCellWidth, height: 100)
cvl.minimumLineSpacing = 0
cvl.minimumInteritemSpacing = 0
cvl.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: cvl)
tableView = UITableView()
tableView.rowHeight = tvRowHeight
collectionView.translatesAutoresizingMaskIntoConstraints = false
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
view.addSubview(tableView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
collectionView.heightAnchor.constraint(equalToConstant: 100.0),
tableView.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 0.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0)
])
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(MyCVCell.self, forCellWithReuseIdentifier: "cvCell")
tableView.dataSource = self
tableView.delegate = self
tableView.register(MyTVCell.self, forCellReuseIdentifier: "tvCell")
}
}
extension CvTvViewController: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
tableViewActive = scrollView == tableView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if tableViewActive && scrollView == tableView {
let pctOffset = tableView.contentOffset.y / (CGFloat(myData.count) * tableView.rowHeight)
let xOffset = (CGFloat(myData.count) * cvCellWidth) * pctOffset
collectionView.contentOffset.x = xOffset
}
if !tableViewActive && scrollView == collectionView {
let pctOffset = collectionView.contentOffset.x / (CGFloat(myData.count) * cvCellWidth)
let xOffset = (CGFloat(myData.count) * tvRowHeight) * pctOffset
tableView.contentOffset.y = xOffset
}
}
}
extension CvTvViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "tvCell", for: indexPath) as! MyTVCell
c.label.text = myData[indexPath.row]
return c
}
}
extension CvTvViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let c = collectionView.dequeueReusableCell(withReuseIdentifier: "cvCell", for: indexPath) as! MyCVCell
c.contentView.backgroundColor = colors[indexPath.item % colors.count]
c.label.text = myData[indexPath.item]
return c
}
}
class MyTVCell: UITableViewCell {
let label: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.9, 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() -> Void {
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
])
}
}
class MyCVCell: UICollectionViewCell {
let label: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
])
}
}
启动时看起来像这样:
滚动一点后像这样:
当我们一直向下滚动时像这样:
编辑 如果你想一直滚动到最后一行/项目(这可能有点令人不安,因为这不是用户习惯的,但是...),添加此代码:
// track the table view frame height and
// collection view frame width
var tvHeight: CGFloat = 0
var cvWidth: CGFloat = 0
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// we only want to call this if
// the tableView height or
// the collectionView width changes
if tvHeight != tableView.frame.height || cvWidth != collectionView.frame.width {
tvHeight = tableView.frame.height
cvWidth = collectionView.frame.width
var edge = tableView.contentInset
edge.bottom = tableView.frame.height - tvRowHeight
tableView.contentInset = edge
edge = collectionView.contentInset
edge.right = collectionView.frame.width - cvCellWidth
collectionView.contentInset = edge
}
}
我正在开发一个 UI,它有一个 UICollectionView,它可以水平滚动,下面有一个 UITableView:
我的目标是,每当我在 TableView 中滚动时,CollectionView 将以与 Tableview 完全相同的速度和相同的 amount/direction 移动,反之亦然。
所以我需要以某种方式连接它们。我尝试获取 myTableView.contentOffset.y
并将此值放入 CollectionView myCollectionView.setContentOffset(CGPoint(x: offset, y: 0), animated: true)
.
然而,这个函数需要以尽可能短的间隔一直被调用,而且它不是真正的同步,也不流畅和快速。
有没有其他的可能实现这个?
感谢任何意见!
“这个函数需要在最短的可能间隔内一直被调用” 不是真的。您只需要在 tableView(或 collectionView)scrolled.
时同步滚动为此,实施 scrollViewDidScroll
。
但是,您将有几个问题需要处理...
首先,除非您的行的高度与单元格的宽度相同,否则您不能直接使用 .contentOffset.y
。
例如,如果您的行高 50 磅,单元格宽 100 磅,当您向下滚动 10 行时,.contentOffset.y
将为 500。如果您设置 .contentOffset.x
在你的 collectionView 上设置为 500,它只会滚动 5 个单元格而不是 10 个。
因此,您需要使用偏移量 百分比 -- 计算起来并不难。
然而...
根据您发布的图片,您显示了大约 13 行和大约 3 个单元格。当您向下滚动时,转换为百分比偏移量,当第 2 行位于顶部时,单元格 2 将位于左边缘。当第 3 行位于顶部时,单元格 3 将位于左边缘。等等……太棒了。
直到您一直向下滚动。现在,如果您总共有 100 行,则顶行将是第 87 行,并且您的 collectionView 中的可见单元格将是 87、88 和 89 ...无法到达单元格 90 到 100。
这是一些显示同步滚动的示例代码 和 显示当您到达 table 底部时的问题(使用 50 个数据项):
class CvTvViewController: UIViewController {
var myData: [String] = []
let colors: [UIColor] = [
.systemRed, .systemGreen, .systemBlue,
.systemPink, .systemYellow, .systemTeal,
]
var collectionView: UICollectionView!
var tableView: UITableView!
var tableViewActive: Bool = false
var cvCellWidth: CGFloat = 120
let tvRowHeight: CGFloat = 50
override func viewDidLoad() {
super.viewDidLoad()
// fill myData array with 50 strings
myData = (1...50).map { "Data: \([=10=])" }
let cvl = UICollectionViewFlowLayout()
cvl.itemSize = CGSize(width: cvCellWidth, height: 100)
cvl.minimumLineSpacing = 0
cvl.minimumInteritemSpacing = 0
cvl.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: cvl)
tableView = UITableView()
tableView.rowHeight = tvRowHeight
collectionView.translatesAutoresizingMaskIntoConstraints = false
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
view.addSubview(tableView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
collectionView.heightAnchor.constraint(equalToConstant: 100.0),
tableView.topAnchor.constraint(equalTo: collectionView.bottomAnchor, constant: 0.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0)
])
collectionView.dataSource = self
collectionView.delegate = self
collectionView.register(MyCVCell.self, forCellWithReuseIdentifier: "cvCell")
tableView.dataSource = self
tableView.delegate = self
tableView.register(MyTVCell.self, forCellReuseIdentifier: "tvCell")
}
}
extension CvTvViewController: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
tableViewActive = scrollView == tableView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if tableViewActive && scrollView == tableView {
let pctOffset = tableView.contentOffset.y / (CGFloat(myData.count) * tableView.rowHeight)
let xOffset = (CGFloat(myData.count) * cvCellWidth) * pctOffset
collectionView.contentOffset.x = xOffset
}
if !tableViewActive && scrollView == collectionView {
let pctOffset = collectionView.contentOffset.x / (CGFloat(myData.count) * cvCellWidth)
let xOffset = (CGFloat(myData.count) * tvRowHeight) * pctOffset
tableView.contentOffset.y = xOffset
}
}
}
extension CvTvViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: "tvCell", for: indexPath) as! MyTVCell
c.label.text = myData[indexPath.row]
return c
}
}
extension CvTvViewController: UICollectionViewDataSource, UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let c = collectionView.dequeueReusableCell(withReuseIdentifier: "cvCell", for: indexPath) as! MyCVCell
c.contentView.backgroundColor = colors[indexPath.item % colors.count]
c.label.text = myData[indexPath.item]
return c
}
}
class MyTVCell: UITableViewCell {
let label: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.9, 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() -> Void {
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
])
}
}
class MyCVCell: UICollectionViewCell {
let label: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
])
}
}
启动时看起来像这样:
滚动一点后像这样:
当我们一直向下滚动时像这样:
编辑 如果你想一直滚动到最后一行/项目(这可能有点令人不安,因为这不是用户习惯的,但是...),添加此代码:
// track the table view frame height and
// collection view frame width
var tvHeight: CGFloat = 0
var cvWidth: CGFloat = 0
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// we only want to call this if
// the tableView height or
// the collectionView width changes
if tvHeight != tableView.frame.height || cvWidth != collectionView.frame.width {
tvHeight = tableView.frame.height
cvWidth = collectionView.frame.width
var edge = tableView.contentInset
edge.bottom = tableView.frame.height - tvRowHeight
tableView.contentInset = edge
edge = collectionView.contentInset
edge.right = collectionView.frame.width - cvCellWidth
collectionView.contentInset = edge
}
}