Swift - 使用 UICollectionViewFlowLayout 的 Facebook 风格图像网格
Swift - Facebook style image grid using UICollectionViewFlowLayout
我正在尝试在 Swift 中实现 Facebook 风格的图像布局。我找到 并遵循其中一个答案建议的方法。结果,我几乎实现了我想要的。
但是,正如您在上图中看到的那样,有一个溢出:三个较小的单元格占用的空间似乎超过了屏幕宽度。但是,如果我将0.1
减去每个单元格的宽度,问题似乎已经解决了。
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let item = indexPath.item
let width = collectionView.bounds.size.width
let padding:CGFloat = 5
if item < 2 {
let itemWidth:CGFloat = (width - padding) / 2.0
return CGSize(width: itemWidth, height: itemWidth)
} else {
let itemWidth:CGFloat = (width - 2 * padding) / 3.0
// let itemWidth:CGFloat = (width - 2 * padding) / 3.0 - 0.1 // This WORKS!!
return CGSize(width: itemWidth, height: itemWidth)
}
}
我猜溢出是由 itemWidth
的值向上舍入引起的。但我觉得在处理此类问题时,减去一个随机的硬编码值并不是最佳做法。
谁能为此提出更好的方法?
以下是可重现的完整代码。
class ContentSizeCollectionView: UICollectionView {
override var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
class GridVC: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 5
layout.minimumInteritemSpacing = 5
let collectionView = ContentSizeCollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "ident")
return collectionView
}()
override func viewDidLoad() {
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let item = indexPath.item
let width = collectionView.bounds.size.width
let padding:CGFloat = 5
if item < 2 {
let itemWidth:CGFloat = (width - padding) / 2.0
return CGSize(width: itemWidth, height: itemWidth)
} else {
let itemWidth:CGFloat = (width - 2 * padding) / 3.0
return CGSize(width: itemWidth, height: itemWidth)
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ident", for: indexPath)
cell.backgroundColor = .red
return cell
}
}
更新
floor((width - 2 * padding) / 3.0)
确实防止了溢出,但最后留下了一个小空隙。
更改此行:
let itemWidth:CGFloat = (width - 2 * padding) / 3.0
对此:
let itemWidth:CGFloat = floor((width - 2 * padding) / 3.0)
您可能还想对 2 项行执行此操作。
编辑
最初的问题是浮点数是近似值。
因此,例如,在 iPhone 8 上,视图宽度为 375
点。单元格宽度计算为:
let padding: CGFloat = 5
let itemWidth:CGFloat = (width - 2 * padding) / 3.0
itemWidth
最终成为 121.666... (repeating)
,UIKit 将其解释为 121.66666666666667
.
因此,121.66666666666667 * 3.0 + 10
等于(大致)375.00000000000011
并且...大于 375.0
。
因此,集合视图显示“不能将它放在一行中。”
使用 floor()
解决了这个问题,除了... 它遇到了一个非常奇怪的错误!
如果将 numberOfItemsInSection
从 5
更改为 8
,您将得到两行 3,并且会有 no gap右边.
我们可以通过使 side 单元格比 center 单元格稍微窄一些来解决这个问题,如下所示:
// we want all three to be the same heights
// 1/3 of (width - 2 * padding)
let itemHeight: CGFloat = (width - 2 * padding) / 3.0
// left and right cells will be 1/3 of (width - 2 * padding)
// rounded down to a whole number
let sideW: CGFloat = floor(itemHeight)
// center cell needs to be
// full-width minus 2 * padding
// minus
// side-width * 2
let centerW: CGFloat = (width - 2 * padding) - sideW * 2
// is it left (0), center (1) or right (2)
let n = (item - 2) % 3
// use the proper width value
let itemWidth: CGFloat = n == 1 ? centerW : sideW
return CGSize(width: itemWidth, height: itemHeight)
或者,似乎 起作用的是使宽度 比浮点数的 1/3 略小。您使用了 -0.1
,但它也适用于:
let itemWidth:CGFloat = ((width - 2 * padding) / 3.0) - 0.0000001
无论如何,这有望解释“2 个单元而不是 3 个”问题的原因,以及避免它的两个选项。
我正在尝试在 Swift 中实现 Facebook 风格的图像布局。我找到
但是,正如您在上图中看到的那样,有一个溢出:三个较小的单元格占用的空间似乎超过了屏幕宽度。但是,如果我将0.1
减去每个单元格的宽度,问题似乎已经解决了。
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let item = indexPath.item
let width = collectionView.bounds.size.width
let padding:CGFloat = 5
if item < 2 {
let itemWidth:CGFloat = (width - padding) / 2.0
return CGSize(width: itemWidth, height: itemWidth)
} else {
let itemWidth:CGFloat = (width - 2 * padding) / 3.0
// let itemWidth:CGFloat = (width - 2 * padding) / 3.0 - 0.1 // This WORKS!!
return CGSize(width: itemWidth, height: itemWidth)
}
}
我猜溢出是由 itemWidth
的值向上舍入引起的。但我觉得在处理此类问题时,减去一个随机的硬编码值并不是最佳做法。
谁能为此提出更好的方法?
以下是可重现的完整代码。
class ContentSizeCollectionView: UICollectionView {
override var contentSize: CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
class GridVC: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate, UICollectionViewDataSource {
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 5
layout.minimumInteritemSpacing = 5
let collectionView = ContentSizeCollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "ident")
return collectionView
}()
override func viewDidLoad() {
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let item = indexPath.item
let width = collectionView.bounds.size.width
let padding:CGFloat = 5
if item < 2 {
let itemWidth:CGFloat = (width - padding) / 2.0
return CGSize(width: itemWidth, height: itemWidth)
} else {
let itemWidth:CGFloat = (width - 2 * padding) / 3.0
return CGSize(width: itemWidth, height: itemWidth)
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ident", for: indexPath)
cell.backgroundColor = .red
return cell
}
}
更新
floor((width - 2 * padding) / 3.0)
确实防止了溢出,但最后留下了一个小空隙。
更改此行:
let itemWidth:CGFloat = (width - 2 * padding) / 3.0
对此:
let itemWidth:CGFloat = floor((width - 2 * padding) / 3.0)
您可能还想对 2 项行执行此操作。
编辑
最初的问题是浮点数是近似值。
因此,例如,在 iPhone 8 上,视图宽度为 375
点。单元格宽度计算为:
let padding: CGFloat = 5
let itemWidth:CGFloat = (width - 2 * padding) / 3.0
itemWidth
最终成为 121.666... (repeating)
,UIKit 将其解释为 121.66666666666667
.
因此,121.66666666666667 * 3.0 + 10
等于(大致)375.00000000000011
并且...大于 375.0
。
因此,集合视图显示“不能将它放在一行中。”
使用 floor()
解决了这个问题,除了... 它遇到了一个非常奇怪的错误!
如果将 numberOfItemsInSection
从 5
更改为 8
,您将得到两行 3,并且会有 no gap右边.
我们可以通过使 side 单元格比 center 单元格稍微窄一些来解决这个问题,如下所示:
// we want all three to be the same heights
// 1/3 of (width - 2 * padding)
let itemHeight: CGFloat = (width - 2 * padding) / 3.0
// left and right cells will be 1/3 of (width - 2 * padding)
// rounded down to a whole number
let sideW: CGFloat = floor(itemHeight)
// center cell needs to be
// full-width minus 2 * padding
// minus
// side-width * 2
let centerW: CGFloat = (width - 2 * padding) - sideW * 2
// is it left (0), center (1) or right (2)
let n = (item - 2) % 3
// use the proper width value
let itemWidth: CGFloat = n == 1 ? centerW : sideW
return CGSize(width: itemWidth, height: itemHeight)
或者,似乎 起作用的是使宽度 比浮点数的 1/3 略小。您使用了 -0.1
,但它也适用于:
let itemWidth:CGFloat = ((width - 2 * padding) / 3.0) - 0.0000001
无论如何,这有望解释“2 个单元而不是 3 个”问题的原因,以及避免它的两个选项。