我有一個動態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
}
}

uj5u.com熱心網友回復:
正如我在評論中所說,您正在使用“左對齊垂直滾動”集合視圖的代碼。
一個水平滾動的集合視影像這樣布置單元格:

您的代碼正在按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
}
}
請注意,這只是示例代碼!!!它尚未經過測驗,可能適合也可能不適合您的需求。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/490186.html
標籤:IOS 迅速 uicollectionview uicollectionviewflowlayout
