UICollectionView 保持水平列表滚动位置

Posted

技术标签:

【中文标题】UICollectionView 保持水平列表滚动位置【英文标题】:UICollectionView keep horizontal list scroll position 【发布时间】:2018-09-03 03:22:35 【问题描述】:

我的UICollectionView 中有一些水平的UICollectionViewCell

我的问题是,如果我将第一个单元格中的 horizontal list 滚动到另一个位置,第四个单元格也将位于同一位置。

第二个和第五个,第三个和第六个……

也不要keep horizontal scroll position从纵向到横向或相反。

有没有办法让单元格中的UICollectionView 保持其位置?

更新 2:

我知道我必须将内部 horizontal 集合视图 content offsets 保存在一个数组中。我在下面的链接中读到了这一点,但在下面的链接中,他们希望在UITableViewUIScrollView 中实现这一点。我想在UICollectionView 内部UICollectionViewController 中实现这一点。

Scroll View in UITableViewCell won't save position

更新 1:

https://imgur.com/a/F8EGsTx

ViewController.swift

import UIKit

class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout 

    let cellId = "cellId"

    override func viewDidLoad() 
        super.viewDidLoad()
        collectionView?.backgroundColor = .white
        collectionView?.register(CategoryCell.self, forCellWithReuseIdentifier: cellId)
    

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CategoryCell
        cell.nameLabel.text = "Horizontal list #\(indexPath.item)"
        return cell
    

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return 10
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
        return CGSize(width: view.frame.width, height: 150)
    


CategoryCell.swift

import UIKit

class CategoryCell: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout 

    let cellId = "categoryId"

    override init(frame: CGRect) 
        super.init(frame: frame)
        setupViews()
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    var nameLabel: UILabel = 
        let label = UILabel()
        label.text = "Horizontal list #1"
        label.font = UIFont.systemFont(ofSize: 16)
        label.translatesAutoresizingMaskIntoConstraints = false
        return label
    ()

    let appsCollectionView: UICollectionView = 
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.backgroundColor = .white
        return collectionView
    ()

    let dividerLineView: UIView = 
        let view = UIView()
        view.backgroundColor = UIColor(white: 0.4, alpha: 0.4)
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    ()

    func setupViews() 

        addSubview(appsCollectionView)
        addSubview(dividerLineView)
        addSubview(nameLabel)

        appsCollectionView.dataSource = self
        appsCollectionView.delegate = self

        appsCollectionView.register(AppCell.self, forCellWithReuseIdentifier: cellId)

        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-14-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": nameLabel]))

        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-14-[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": dividerLineView]))

        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": appsCollectionView]))

        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[nameLabel(30)][v0][v1(0.5)]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": appsCollectionView, "v1": dividerLineView, "nameLabel": nameLabel]))

    

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return 10
    

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! AppCell
        cell.imageView.backgroundColor = UIColor(hue: CGFloat(indexPath.item) / 20.0, saturation: 0.8, brightness: 0.9, alpha: 1)
        return cell
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
        return CGSize(width: 100, height: frame.height - 32)
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets 
        return UIEdgeInsetsMake(0, 14, 0, 14)
    


AppCell.swift

import UIKit

class AppCell: UICollectionViewCell 

    override init(frame: CGRect) 
        super.init(frame: frame)
        setupViews()
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    let imageView: UIImageView = 
        let iv = UIImageView()
        iv.contentMode = .scaleAspectFill
        iv.layer.cornerRadius = 16
        iv.layer.masksToBounds = true
        return iv
    ()

    func setupViews() 
        addSubview(imageView)

        imageView.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.width)
    

【问题讨论】:

你能分享一个截图来解释你的问题吗? 您能分享一下您为此编写的代码或为 UICollectionViewCell 设置的约束吗?所以我们可以解决它。 谢谢,屏幕记录和代码已添加。 还要取消选中启用分页,否则带有水平滚动的 UICollectionview 会表现得粘滞,并且不会停留在滚动完成的位置。 【参考方案1】:

我终于明白了:)

感谢 3 年前的项目!来自伊凡隆

https://github.com/irfanlone/Collection-View-in-a-collection-view-cell

它也可以工作并支持界面定向,但需要一些优化。

例如,如果您向右滚动,然后快速向下滚动,然后再滚动回顶部,则单元格的右边缘有额外的边距!界面方向也有一些问题。

如果有人有完整版,请分享,谢谢。

有兴趣的可以在指定文件中添加以下代码。

ViewController.swift

var storedOffsets = [Int: CGFloat]()

override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) 
    guard let collectionViewCell = cell as? CategoryCell else  return 
    collectionViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0


override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) 
    guard let collectionViewCell = cell as? CategoryCell else  return 
    storedOffsets[indexPath.row] = collectionViewCell.collectionViewOffset

CategoryCell.swift

var collectionViewOffset: CGFloat 
    set 
        appsCollectionView.contentOffset.x = newValue
    

    get 
        return appsCollectionView.contentOffset.x
    

【讨论】:

【参考方案2】:

@zahaniza 感谢您的回答,以及您的代码

如果您使用多个部分,代码将如下所示

ViewController.swift

var storedOffsets = [IndexPath: CGFloat]()

override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) 
    guard let collectionViewCell = cell as? CategoryCell else  return 
    collectionViewCell.collectionViewOffset = storedOffsets[indexPath] ?? 0


override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) 
    guard let collectionViewCell = cell as? CategoryCell else  return 
    storedOffsets[indexPath] = collectionViewCell.collectionViewOffset

【讨论】:

【参考方案3】:

内部集合视图被重用。如果要保留每个水平集合视图的滚动位置,则必须在 UIViewController 中保留滚动位置数组,并在内部集合视图滚动时更新它。然后在重用外部集合视图单元格时,您可以在collectionView(collectionView:, cellForItemAt indexPath:) 方法中重置滚动位置。

您的外部集合视图填充方法应如下所示:

 override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
          let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! CategoryCell
          cell.nameLabel.text = "Horizontal list #\(indexPath.item)"
          cell.scrollPosition = this.scrollPositions[indexPath.item]
          return cell

【讨论】:

谢谢,但我如何更新滚动位置?在代码的哪一部分?你能说得更具体些吗? 有一个 UIScrollViewDelegate 函数 'optional func scrollViewDidScroll(_ scrollView: UIScrollView)' 。只需将委托设置为 viewController 即可。 再次感谢,但您的回答细节还不够。在 scrollViewDidScroll 我需要 indexPath 来更新 scrollPositions,但我怎样才能访问它?在 UIViewController 中,scrollViewDidScroll 只返回垂直滚动,如何访问内部集合视图水平滚动偏移量? 如果我在内部集合视图中添加这个函数 scrollViewDidScroll 我如何访问 scrollPositions 数组来更新它?如果我设置 scrollPositions[indexPath.item] = collectionView.contentOffset 来更新 scrollPositions 我超出范围错误!【参考方案4】:

从#zahaniza 的回答来看,这段代码解决了他的问题并且适用于 iPhone X:

var storedOffsets = [Int: CGFloat]()

override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) 
    guard let collectionViewCell = cell as? CategoryCell,
     let innerCollectionView = collectionViewCell.collectionView else  return 

    // Default collection offset is -adjustedContentInset.left and not 0
    let minOffset = -innerCollectionView.adjustedContentInset.left
    let maxOffset = innerCollectionView.contentSize.width - innnerCollectionView.frame.width

    maxOffset += innerCollectionView.adjustedContentInset.left

    if let offset = storedOffsets[indexPath.row] 
        innerCollectionView.contentOffset = CGPoint(max(minOffset, miin(maxOffset, offset.x)), 0)
     else 
        innerCollectionView.contentOffset = CGPoint(minOffset, 0);
    
    collectionViewCell.collectionViewOffset = storedOffsets[indexPath.row] ?? 0


override func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) 
    guard let collectionViewCell = cell as? CategoryCell else  return 
    storedOffsets[indexPath.row] = collectionViewCell.collectionView.contentOffset

【讨论】:

以上是关于UICollectionView 保持水平列表滚动位置的主要内容,如果未能解决你的问题,请参考以下文章

UICollectionView - 基于水平滚动更新数组 indexPath

即使滚动方向设置为水平,UICollectionView 也会垂直显示?

UITableView中的多个UICollectionView在滚动中交织在一起

如何使用 Swift 制作水平 UICollectionView

UICollectionView“流向”

UICollectionView - 水平滚动,水平布局?