[英]How to make a horizontal UICollectionView have the same spacing between dynamic cells
我有一个动态collectionView,基本上单元格之间的间距需要相同,无论单元格的宽度如何。
在这里和互联网上找到了类似的答案,但都是针对垂直滚动的 collectionViews。 因此,我继续尝试进一步研究其中一个答案以实现我想要的,但运气不佳。
目前,我的 collectionView 在单元格之间具有相同的间距,但是在每个单元格之后,它会移动到下一行,尽管我没有更改或操作属性的 y 偏移量。 此外,并非所有单元格都是可见的。
拜托,你能指出我做错了什么吗? 谢谢。
我正在使用的 UICollectionViewFlowLayout 的子类是:
class TagsLayout: UICollectionViewFlowLayout {
let cellSpacing: CGFloat = 20
override init(){
super.init()
scrollDirection = .horizontal
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.scrollDirection = .horizontal
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else {
return nil
}
guard let attributesToReturn = attributes.map( { $0.copy() }) as? [UICollectionViewLayoutAttributes] else {
return nil
}
var leftMargin = sectionInset.left
var maxX: CGFloat = -1.0
attributesToReturn.forEach { layoutAttribute in
if layoutAttribute.frame.origin.x >= maxX {
leftMargin = sectionInset.left
}
layoutAttribute.frame.origin.x = leftMargin
leftMargin += layoutAttribute.frame.width + cellSpacing
maxX = max(layoutAttribute.frame.maxX , maxX)
}
return attributesToReturn
}
}
正如我在评论中所说,您正在使用“左对齐垂直滚动”集合视图的代码。
一个水平滚动的集合视图像这样布置单元格:
您的代码正在按顺序为每个单元格计算一个新的origin.x
,结果如下:
您可以修改自定义流布局以跟踪每个“行”的maxX
...但是,如果您在滚动时有很多单元格,那么前几个“列”就看不见了,那些单元格将不再被考虑到布局中。
因此,您可以尝试“预先计算”所有单元格的帧宽度和 x 原点,并接近您的目标:
不过还有两个问题...
首先,假设您的单元格包含比这些图像中显示的更长的字符串,则集合视图无法很好地确定哪些单元格实际上需要显示。 也就是说,集合视图将使用估计的项目大小来决定是否需要渲染单元格。 如果对单元格origin.x
值的修改不在预期范围内,则某些单元格将不会被渲染,因为集合视图不会要求它们。
其次,如果你有不同宽度的标签,你可能会得到这样的结果:
并旋转到横向以强调(第一行实际上一直到 24):
您可能需要重新考虑您的方法,要么使用垂直滚动左对齐的集合视图,要么使用具有等宽单元格的水平滚动集合视图,或者其他一些方法(例如放置子视图的普通滚动视图-通过您自己的代码输出)。
我确实使用“预计算”方法创建了类——如果你想尝试一下,它们就在这里。
带有标签的简单单元格:
class TagCell: UICollectionViewCell {
let label = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(label)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: g.topAnchor, constant: 4.0),
label.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
label.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
label.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0),
])
// default (unselected) appearance
contentView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
label.textColor = .black
// let's round the corners so it looks nice
contentView.layer.cornerRadius = 12
}
}
修改后的自定义流布局:
class TagsLayout: UICollectionViewFlowLayout {
var cachedFrames: [[CGRect]] = []
var numRows: Int = 3
let cellSpacing: CGFloat = 20
override init(){
super.init()
commonInit()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
commonInit()
}
func commonInit() {
scrollDirection = .horizontal
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// guard let attributes = super.layoutAttributesForElements(in: rect) else {
// return nil
// }
// we want to force the collection view to ask for the attributes for ALL the cells
// instead of the cells in the rect
var r: CGRect = rect
// we could probably get and use the max-width from the cachedFrames array...
// but let's just set it to a very large value for now
r.size.width = 50000
guard let attributes = super.layoutAttributesForElements(in: r) else {
return nil
}
guard let attributesToReturn = attributes.map( { $0.copy() }) as? [UICollectionViewLayoutAttributes] else {
return nil
}
attributesToReturn.forEach { layoutAttribute in
let thisRow: Int = layoutAttribute.indexPath.item % numRows
let thisCol: Int = layoutAttribute.indexPath.item / numRows
layoutAttribute.frame.origin.x = cachedFrames[thisRow][thisCol].origin.x
}
return attributesToReturn
}
}
带有生成的标签字符串的示例控制器类:
class HorizontalTagColViewVC: UIViewController {
var collectionView: UICollectionView!
var myData: [String] = []
// number of cells that will fit vertically in the collection view
let numRows: Int = 3
override func viewDidLoad() {
super.viewDidLoad()
// let's generate some rows of "tags"
// we're using 3 rows for this example
for i in 0...28 {
switch i % numRows {
case 0:
// top row will have long tag strings
myData.append("A long tag name \(i)")
case 1:
// 2nd row will have short tag strings
myData.append("Tag \(i)")
default:
// 3nd row will have numeric strings
myData.append("\(i)")
}
}
// now we'll pre-calculate the tag-cell widths
let szCell = TagCell()
let fitSize = CGSize(width: 1000, height: 50)
var calcedFrames: [[CGRect]] = Array(repeating: [], count: numRows)
for i in 0..<myData.count {
szCell.label.text = myData[i]
let sz = szCell.systemLayoutSizeFitting(fitSize, withHorizontalFittingPriority: .defaultLow, verticalFittingPriority: .required)
let r = CGRect(origin: .zero, size: sz)
calcedFrames[i % numRows].append(r)
}
// loop through each "row" setting the origin.x to the
// previous cell's origin.x + width + 20
for row in 0..<numRows {
for col in 1..<calcedFrames[row].count {
var thisRect = calcedFrames[row][col]
let prevRect = calcedFrames[row][col - 1]
thisRect.origin.x += prevRect.maxX + 20.0
calcedFrames[row][col] = thisRect
}
}
let fl = TagsLayout()
// for horizontal flow, this is becomes the minimum-inter-line spacing
fl.minimumInteritemSpacing = 20
// we need this so the last cell does not get clipped
fl.minimumLineSpacing = 20
// a reasonalbe estimated size
fl.estimatedItemSize = CGSize(width: 120, height: 50)
// set the number of rows in our custom layout
fl.numRows = numRows
// set our calculated frames in our custom layout
fl.cachedFrames = calcedFrames
collectionView = UICollectionView(frame: .zero, collectionViewLayout: fl)
// so we can see the collection view frame
collectionView.backgroundColor = .cyan
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
collectionView.heightAnchor.constraint(equalToConstant: 180.0),
])
collectionView.register(TagCell.self, forCellWithReuseIdentifier: "cell")
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension HorizontalTagColViewVC: UICollectionViewDataSource, UICollectionViewDelegate {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return myData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let c = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! TagCell
c.label.text = myData[indexPath.item]
return c
}
}
请注意,这只是示例代码!!! 它尚未经过测试,可能适合也可能不适合您的需求。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.