Ios Swift中UITableViewCell中的同步滚动UICollectionViews

Ios Swift中UITableViewCell中的同步滚动UICollectionViews,ios,swift,xcode,uitableview,uicollectionview,Ios,Swift,Xcode,Uitableview,Uicollectionview,我有这样的结构: UITableView->UITableViewCell->UICollectionView-> UICollectionViewCell 因此,我试图实现的是,我希望在UITableViewCells中使UICollectionViews同步滚动。例如,当您手动滚动第一行上的第一个UICollectionView时,我希望其余的UICollectionView跟随,但文本标签始终保持在同一位置。(请参见下图) EDIT:我知道我必须以某种方式使用contentOffset,但

我有这样的结构:

UITableView->UITableViewCell->UICollectionView-> UICollectionViewCell

因此,我试图实现的是,我希望在UITableViewCells中使UICollectionViews同步滚动。例如,当您手动滚动第一行上的第一个UICollectionView时,我希望其余的UICollectionView跟随,但文本标签始终保持在同一位置。(请参见下图)

EDIT:我知道我必须以某种方式使用
contentOffset
,但不知道在这种情况下如何实现。任何帮助都将不胜感激


我能想到的最好的方法就是将所有的CollectionView存储在一个collection对象中。然后可以从这些集合视图中使用
UIScrollView
scrollViewDidScroll
委托方法。只需确保正确设置了代理即可

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    for view in collectionViewCollection where view.scrollView != scrollView{
        view.scrollView.contentOffset = scrollView.contentOffset
    }
}

这是未经测试的代码,因此不是一个完整的答案,但应该可以让您开始使用。

我提出了一个可行的解决方案,您可以在操场上测试:

//: 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()
要使它与游乐场一起工作,您将看到许多代码,如果您使用的是故事板或xib,则不需要这些代码。无论如何,我希望基本想法是明确的

解释 基本上,我创建了一个名为
offsetsynchronizer
的对象,它有一个名为
currentOffset
的可观察属性。tableView的每个单元格都接受OffsetSynchronizer,并在didSet上向KVO注册currentOffset更改通知

每个单元格还注册到自己集合的委托,并实现didScroll委托方法

当这些collectionView中的任何一个触发此方法时,同步器的currentOffset var会发生变化,并且通过KVO订阅的所有单元都会对这些变化做出反应

可观测物体非常简单:

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)
      }
    }
  }
originingchange
变量是为了避免实际启动偏移量更改的collectionView会导致偏移量被重新设置两次

最后,始终在TableViewCell中,在将自身注册为collectionViewDelegate后,您将实现didScroll的方法

  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    originatingChange = true
    offsetSynchroniser?.currentOffset = scrollView.contentOffset
    originatingChange = false
  }
在这里,我们可以更改同步器的电流偏移

此时,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
      }
 }

好的,我设法让这项工作,请记住,代码只是为了问题的目的,并包含了大量的非通用参数和强制铸造,应该避免在任何成本

包含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的类是i相关的,因为它只是实现细节。我将在稍后将此解决方案发布到github


免责声明:这只适用于可见单元格。您还需要为准备好重用的单元实现当前滚动状态。我将在github上扩展代码。

很抱歉,您想要实现什么还不清楚。你能发布一张你想要的gif图片吗,或者至少是一个更好的解释?@GiuseppeLanza通过添加gif编辑了这个问题。谢谢因此,如果一个内部集合视图滚动,那么所有其他视图都必须滚动相同的内容。这是正确的吗?@GiuseppeLanza完全正确。由于
UICollectionView
UITableView
UIScrollView
的子类,您可以使用
UIScrollViewDelegate
scrollViewDidScroll:
中检测特定
UICollectionView
的滚动(可能位于
iOutletCollection
中)。之后,您只需将相同的
contentOffset
应用于您的集合引用的每个集合视图。感谢您的回复!非常好地使用了keyValueObserving和+,使其更加通用!我编辑了答案,以详细解释该解决方案它仅适用于可见单元格。请提供任何解决方案n可用。。。