UICollectionView 保持水平列表滚动位置
UICollectionView keep horizontal list scroll position
我的 UICollectionView
中有一些水平 UICollectionViewCell
。
我的问题是,如果我将第一个单元格中的 horizontal list
滚动到另一个位置,第四个单元格也会在同一位置。
第二个和第五个,第三个和第六个也一样...
也不要keep horizontal scroll position
从纵向到横向或相反。
有没有办法让单元格中的 UICollectionView
保持其位置?
更新二:
我知道我必须将内部 horizontal
集合视图 content offsets
保存在数组中。我在下面的 link 中读到了这一点,但在下面的 link 中,他们希望在 UITableView
和 UIScrollView
中实现这一点。我想在 UICollectionViewController
.
内的 UICollectionView
中实现这一点
更新 1:
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)
}
}
内部集合视图得到重用。如果您想保留每个水平集合视图的滚动位置,您必须在 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
}
我终于明白了:)
感谢 3 年前的项目!来自 irfanlone
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
}
}
根据#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
}
@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
}
我的 UICollectionView
中有一些水平 UICollectionViewCell
。
我的问题是,如果我将第一个单元格中的 horizontal list
滚动到另一个位置,第四个单元格也会在同一位置。
第二个和第五个,第三个和第六个也一样...
也不要keep horizontal scroll position
从纵向到横向或相反。
有没有办法让单元格中的 UICollectionView
保持其位置?
更新二:
我知道我必须将内部 horizontal
集合视图 content offsets
保存在数组中。我在下面的 link 中读到了这一点,但在下面的 link 中,他们希望在 UITableView
和 UIScrollView
中实现这一点。我想在 UICollectionViewController
.
UICollectionView
中实现这一点
更新 1:
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)
}
}
内部集合视图得到重用。如果您想保留每个水平集合视图的滚动位置,您必须在 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
}
我终于明白了:)
感谢 3 年前的项目!来自 irfanlone
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
}
}
根据#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
}
@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
}