Swift中

时间:2018-01-26 12:48:48

标签: ios swift xcode uitableview uicollectionview

我有这样的结构:

  

UITableView - > UITableViewCell - > UICollectionView - >   UICollectionViewCell

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

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

Click to see the image

Click to see the gif

3 个答案:

答案 0 :(得分:5)

好的,我设法让这个工作正常,请记住,代码仅用于问题目的,包含许多非通用参数和强制转换,应该不惜一切代价避免。

MainViewController的类,包含tableView:

    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上扩展代码。

答案 1 :(得分:0)

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

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

这是未经测试的代码,所以不是一个完整的答案,但它应该让你开始。

答案 2 :(得分:0)

我想出了一个可以在操场上测试的工作解决方案:

//: 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。我希望无论如何基本想法都很明确。

说明

基本上我创建了一个名为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
      }
 }