将 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
    }
}