我如何实现这种布局,我只需要为偶数行号应用 Y 偏移量?
Posted
技术标签:
【中文标题】我如何实现这种布局,我只需要为偶数行号应用 Y 偏移量?【英文标题】:How do I achieve this layout, I just need to apply Y offset for even row numbers? 【发布时间】:2021-10-05 02:19:36 【问题描述】:我已经提到了这个问题
Refer to this question and answer
以及提供的解决方案,当我实施它时,我观察到一种奇怪的行为。每当用户将新项目添加到集合视图单元格时。它出现了,但集合视图中已经存在的其他集合视图单元格消失了。
只有在重新启动应用程序和视图后才会调用 load 方法。我能够看到所有集合视图项目。如果我尝试添加两个项目而不重新启动应用程序,它甚至会崩溃。
它在集合视图流布局中出现超出范围错误时崩溃。
这是那个答案中提到的collectionviewflowlayout
`import UIKit
class HiveLayout: UICollectionViewLayout
// Adjust this as you need
fileprivate var offset: CGFloat = 30
// Properties for configuring the layout: the number of columns and the cell padding.
fileprivate var numberOfColumns = 2
fileprivate var cellPadding: CGFloat = 10
// Cache the calculated attributes. When you call prepare(), you’ll calculate the attributes for all items and add them to the cache. You can be efficient and query the cache instead of recalculating them every time.
fileprivate var cache = [UICollectionViewLayoutAttributes]()
// Properties to store the content size.
fileprivate var contentHeight: CGFloat = 0
fileprivate var contentWidth: CGFloat
guard let collectionView = collectionView else
return 0
let insets = collectionView.contentInset
return collectionView.bounds.width - (insets.left + insets.right)
// Using contentWidth and contentHeight from previous steps, calculate collectionViewContentSize.
override var collectionViewContentSize: CGSize
return CGSize(width: contentWidth, height: contentHeight)
override func prepare()
// If cache is empty and the collection view exists – calculate the layout attributes
guard cache.isEmpty == true, let collectionView = collectionView else
return
// xOffset: array with the x-coordinate for every column based on the column widths
// yOffset: array with the y-position for every column, Using odd-even logic to push the even cell upwards and odd cells down.
let columnWidth = contentWidth / CGFloat(numberOfColumns)
var xOffset = [CGFloat]()
for column in 0 ..< numberOfColumns
xOffset.append(CGFloat(column) * columnWidth)
var column = 0
var yOffset = [CGFloat]()
for i in 0..<numberOfColumns
yOffset.append((i % 2 == 0) ? 0 : offset)
for item in 0 ..< collectionView.numberOfItems(inSection: 0)
let indexPath = IndexPath(item: item, section: 0)
let columnWidths = contentWidth / CGFloat(numberOfColumns)
// Calculate insetFrame that can be set to the attribute
// let cellHeight = columnWidth - (cellPadding * 2)
// let height = 250.0
let height = columnWidths * 1.5
let frame = CGRect(x: xOffset[column], y: yOffset[column], width: columnWidth, height: CGFloat(height))
let insetFrame = frame.insetBy(dx: cellPadding, dy: cellPadding)
// Create an instance of UICollectionViewLayoutAttribute, sets its frame using insetFrame and appends the attributes to cache.
let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
attributes.frame = insetFrame
cache.append(attributes)
// Update the contentHeight to account for the frame of the newly calculated item. It then advances the yOffset for the current column based on the frame
contentHeight = max(contentHeight, frame.maxY)
yOffset[column] = yOffset[column] + CGFloat(height)
column = column < (numberOfColumns - 1) ? (column + 1) : 0
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
for attributes in cache
if attributes.frame.intersects(rect)
visibleLayoutAttributes.append(attributes)
return visibleLayoutAttributes
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
return cache[indexPath.item]
【问题讨论】:
“每当用户将新项目添加到集合视图单元格时。它会出现,但集合视图中已经存在的其他集合视图单元格消失了。” -- 首先,这令人困惑...用户是否添加了另一个 CELL?或者用户是否正在做一些向现有单元格添加一些东西(子视图?)的事情?您只展示了您的布局的代码——您没有展示任何与“添加”任何东西相关的代码。 【参考方案1】:我了解您的问题是:
如果您添加新数据并重新加载集合视图,则它不会在视图中显示新数据 如果您将新数据插入列表数据并重新加载集合视图,那么它将显示新数据,同时一些旧数据也会消失 集合视图只能在您按您说的重新启动应用程序时显示我已经尝试过这个问题并像这样修复它,您只需删除条件以检查缓存是否为空,然后集合视图可以重新加载新数据。
import UIKit
class HiveLayout: UICollectionViewLayout
....
override func prepare()
// If cache is empty and the collection view exists – calculate the layout attributes
// Here I remove this codition: cache.isEmpty == true
guard let collectionView = collectionView else
return
....
....
【讨论】:
伙计,你是救命稻草 :) 我已经为此头疼了好几天。知道它只需要一点修复就感觉很愚蠢:)非常感谢:)【参考方案2】:我建议使用UICollectionViewCompositionalLayout
来实现这种布局。一旦你明白了,它就会比UICollectionViewFlowLayout
简单得多(至少在我看来)。
请注意,我使用UICollectionViewDataSource
来保持简单。你当然可以改用UICollectionViewDiffableDataSource
。
视图控制器
class ViewController: UIViewController
private var items: [UIColor] = (1...16).map _ in .systemTeal
private let collectionView = UICollectionView(frame: .zero, collectionViewLayout: .createCompositionalLayout())
override func viewDidLoad()
super.viewDidLoad()
setupCollectionView()
private func setupCollectionView()
collectionView.dataSource = self
collectionView.register(MyCell.self, forCellWithReuseIdentifier: MyCell.reuseID)
collectionView.backgroundColor = .systemBackground
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
let padding: CGFloat = 8
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: view.topAnchor, constant: padding),
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: padding),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -padding),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -padding)
])
extension ViewController: UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return items.count
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCell.reuseID, for: indexPath) as? MyCell
let item = items[indexPath.row]
cell?.set(bgColor: item, text: "\(indexPath.row)")
return cell ?? UICollectionViewCell()
UICollectionViewLayout
分机:
extension UICollectionViewLayout
static func createCompositionalLayout() -> UICollectionViewCompositionalLayout
return UICollectionViewCompositionalLayout sectionIndex, layoutEnvironment in
//Item
let elementSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(0.3)
)
let leftItem = NSCollectionLayoutItem(layoutSize: elementSize)
let rightItem = NSCollectionLayoutItem(layoutSize: elementSize)
rightItem.contentInsets = NSDirectionalEdgeInsets(top: 40, leading: 0, bottom: -40, trailing: 0)
//Vertical Groups
let verticalGroupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(0.48),
heightDimension: .fractionalHeight(1)
)
let leftVerticalGroup = NSCollectionLayoutGroup.vertical(layoutSize: verticalGroupSize, subitem: leftItem, count: 1)
leftVerticalGroup.interItemSpacing = .fixed(10)
let rightVerticalGroup = NSCollectionLayoutGroup.vertical(layoutSize: verticalGroupSize, subitem: rightItem, count: 1)
rightVerticalGroup.interItemSpacing = .fixed(10)
//Horizontal Group
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1),
heightDimension: .fractionalHeight(1/4)
)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [leftVerticalGroup, rightVerticalGroup])
group.interItemSpacing = .flexible(5)
//Section
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 10
return section
集合视图单元格:
class MyCell: UICollectionViewCell
static var reuseID: String String(describing: self)
private let label: UILabel =
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
return label
()
override init(frame: CGRect)
super.init(frame: .zero)
contentView.layer.cornerRadius = 8
setupLabel()
private func setupLabel()
contentView.addSubview(label)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: contentView.topAnchor),
label.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
label.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
label.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
required init?(coder: NSCoder)
fatalError("init(coder:) has not been implemented")
func set(bgColor: UIColor, text: String)
contentView.backgroundColor = bgColor
label.text = text
只需将提供的代码添加到您的项目中,您应该能够看到以下结果(在新的空 Storyboard 应用程序中,只需删除默认的 ViewController
)
现在,如果您将一些项目添加到 items
并调用 collectionView.reloadData()
,应该不会有任何问题。
请参阅此链接以开始使用UICollectionViewCompositionalLayout
:https://jayeshkawli.ghost.io/new-collection-view-apis-composite-layout/
【讨论】:
非常感谢您的详细解释。我是应用程序开发的新手。你刚刚让学习变得更好。肯定会学习并应用。再次感谢您的帮助:) 意义重大 乐于助人。如果我的回答对你有用,请考虑投票:-)... 已经投票了,但是由于我是新人并且声誉低,它说它已记录但不会显示。 :-/以上是关于我如何实现这种布局,我只需要为偶数行号应用 Y 偏移量?的主要内容,如果未能解决你的问题,请参考以下文章
iphone中的所有应用程序都以“网格图标”布局出现,我该如何实现这种布局?