Swift 中 UITableViewCell 中的同步滚动 UICollectionViews
Posted
技术标签:
【中文标题】Swift 中 UITableViewCell 中的同步滚动 UICollectionViews【英文标题】:Synchronised Scrolling UICollectionViews in UITableViewCell in Swift 【发布时间】:2018-01-26 12:48:48 【问题描述】:我的结构是这样的:
UITableView -> UITableViewCell -> UICollectionView -> UICollectionViewCell
所以我想要实现的是我想让 UITableViewCells 中的 UICollectionViews 滚动同步。例如,当您手动滚动第一行的第一个 UICollectionView 时,我希望 UICollectionViews 的其余部分跟随,但文本标签始终保持在同一位置. (请看下图)
编辑:我知道我必须以某种方式使用contentOffset
,但不知道如何在这种情况下实现。任何帮助将不胜感激。
Click to see the image
Click to see the gif
【问题讨论】:
很抱歉,目前尚不清楚您想要实现什么。能否请您张贴您想要的 GIF,或者至少是一个更好的解释? @GiuseppeLanza 通过添加 gif 来编辑问题。谢谢! 所以如果一个内部集合视图滚动,所有其他的必须滚动相同。对吗? @GiuseppeLanza 完全正确。 由于UICollectionView
和UITableView
是UIScrollView
的子类,您可以使用UIScrollViewDelegate
来检测scrollViewDidScroll:
中特定UICollectionView
的滚动(可能在@ 987654330@)。之后,您只需将相同的contentOffset
应用于您的集合引用的每个集合视图。
【参考方案1】:
好的,我设法让它工作,请记住,代码仅用于提问目的,包含许多非泛型参数和强制转换,应不惜一切代价避免。
包含 tableView 的 MainViewController 类:
protocol TheDelegate: class
func didScroll(to position: CGFloat)
class ViewController: UIViewController, TheDelegate
func didScroll(to position: CGFloat)
for cell in tableView.visibleCells as! [TableViewCell]
(cell.collectionView as UIScrollView).contentOffset.x = position
@IBOutlet var tableView: UITableView!
override func viewDidLoad()
super.viewDidLoad()
tableView.dataSource = self
extension ViewController: UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return 100
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
guard let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath) as? TableViewCell else return UITableViewCell()
cell.scrollDelegate = self
return cell
tableViewCell 的类:
class TableViewCell: UITableViewCell
@IBOutlet var collectionView: UICollectionView!
weak var scrollDelegate: TheDelegate?
override func awakeFromNib()
super.awakeFromNib()
(collectionView as UIScrollView).delegate = self
collectionView.dataSource = self
extension TableViewCell: UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int
return 1
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return 100
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCell", for: indexPath) as! CollectionViewCell
cell.imageView.image = #imageLiteral(resourceName: "litecoin.png")
return cell
extension TableViewCell: UIScrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView)
scrollDelegate?.didScroll(to: scrollView.contentOffset.x)
collectionViewCell 的类是无关紧要的,因为它只是实现细节。稍后我会将此解决方案发布到 github。
免责声明:这仅适用于可见单元格。您还需要为准备重用的单元格实现当前滚动状态。我会在github上扩展代码。
【讨论】:
【参考方案2】:我想出了一个可以在操场上测试的可行解决方案:
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyCollectionCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate
var originatingChange: Bool = false
var observationToken: NSKeyValueObservation!
var offsetSynchroniser: OffsetSynchroniser?
didSet
guard let offsetSynchroniser = offsetSynchroniser else return
collection.setContentOffset(offsetSynchroniser.currentOffset, animated: false)
observationToken = offsetSynchroniser.observe(\.currentOffset) (_, _) in
guard !self.originatingChange else return
self.collection.setContentOffset(offsetSynchroniser.currentOffset, animated: false)
lazy var collection: UICollectionView =
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: 40, height: 40)
layout.scrollDirection = .horizontal
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.backgroundColor = .white
collection.dataSource = self
collection.delegate = self
collection.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
return collection
()
override func layoutSubviews()
super.layoutSubviews()
collection.frame = contentView.bounds
contentView.addSubview(collection)
func numberOfSections(in collectionView: UICollectionView) -> Int
return 1
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return 10
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
cell.backgroundColor = .white
return cell
func scrollViewDidScroll(_ scrollView: UIScrollView)
originatingChange = true
offsetSynchroniser?.currentOffset = scrollView.contentOffset
originatingChange = false
class OffsetSynchroniser: NSObject
@objc dynamic var currentOffset: CGPoint = .zero
class MyViewController : UIViewController, UITableViewDataSource
var tableView: UITableView!
let offsetSynchroniser = OffsetSynchroniser()
override func loadView()
let view = UIView()
view.backgroundColor = .white
tableView = UITableView(frame: .zero, style: .plain)
tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(tableView)
tableView.dataSource = self
tableView.register(MyCollectionCell.self, forCellReuseIdentifier: "cell")
self.view = view
override func viewDidLoad()
super.viewDidLoad()
tableView.reloadData()
func numberOfSections(in tableView: UITableView) -> Int
return 1
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return 10
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyCollectionCell
cell.selectionStyle = .none
cell.collection.reloadData()
cell.offsetSynchroniser = offsetSynchroniser
return cell
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
要使其与 Playground 一起使用,您会看到很多代码,如果您使用的是故事板或 xib,则不需要这些代码。无论如何,我希望基本思想是明确的。
说明
基本上我创建了一个名为OffsetSynchroniser
的对象,它有一个名为currentOffset
的可观察属性。 tableView 的每个单元格都接受一个 offsetSynchroniser,并且在 didSet 上它们向 KVO 注册以获取 currentOffset 更改的通知。
每个单元格还注册到自己的集合的委托并实现 didScroll 委托方法。
当任何这些 collectionView 导致此方法被触发时,同步器的 currentOffset var 会发生变化,并且通过 KVO 订阅的所有单元格都会对这些变化做出反应。
Observable 对象非常简单:
class OffsetSynchroniser: NSObject
@objc dynamic var currentOffset: CGPoint = .zero
那么您的 tableViewCell 将拥有此对象类型的实例,并且在 didSet 上将使用 KVO 注册到 var currentOffset:
var originatingChange: Bool = false
var observationToken: NSKeyValueObservation!
var offsetSynchroniser: OffsetSynchroniser?
didSet
guard let offsetSynchroniser = offsetSynchroniser else return
collection.setContentOffset(offsetSynchroniser.currentOffset, animated: false)
observationToken = offsetSynchroniser.observe(\.currentOffset) (_, _) in
guard !self.originatingChange else return
self.collection.setContentOffset(offsetSynchroniser.currentOffset, animated: false)
originatingChange
变量是为了避免实际启动偏移量更改的 collectionView 会通过导致偏移量被重新设置两次来做出反应。
最后,始终在您的 TableViewCell 中,将自身注册为 collectionViewDelegate 后,您将实现 didScroll 的方法
func scrollViewDidScroll(_ scrollView: UIScrollView)
originatingChange = true
offsetSynchroniser?.currentOffset = scrollView.contentOffset
originatingChange = false
在这里我们可以改变同步器的currentOffset。
此时 tableViewController 将拥有同步器的所有权
class YourTableViewController: UItableViewController // or whatever ViewController contains an UITableView
let offsetSynchroniser = OffsetSynchroniser()
...
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyCollectionCell
...
cell.offsetSynchroniser = offsetSynchroniser
return cell
【讨论】:
很好地使用了 keyValueObserving 和 + 来使它更通用一点! 我编辑了答案以详细解释解决方案 它仅适用于可见单元格。请提供任何可用的解决方案...【参考方案3】:我能想到的最好的方法就是将你所有的collectionViews 存储在一个collection 对象中。然后,您可以从这些 collectionView 中使用 UIScrollView
的 scrollViewDidScroll
委托方法。只需确保您的委托设置正确即可。
func scrollViewDidScroll(_ scrollView: UIScrollView)
for view in collectionViewCollection where view.scrollView != scrollView
view.scrollView.contentOffset = scrollView.contentOffset
这是未经测试的代码,因此不是完整的答案,但它应该可以帮助您入门。
【讨论】:
感谢您的回复!以上是关于Swift 中 UITableViewCell 中的同步滚动 UICollectionViews的主要内容,如果未能解决你的问题,请参考以下文章
Swift 中 UITableViewCell 中的同步滚动 UICollectionViews
在嵌入在 UITableviewCell 中的 UICollectionView 中进行 Segue,Swift