类似日历的 UICollectionView - 如何仅在第一项之前添加左插图?
Calendar-like UICollectionView - how to add left inset before first item only?
我有以下 UICollectionView
:
它具有 vertical
滚动、1 section
和 31 items
。
它具有基本设置,我正在计算 itemSize
以完全适合 7 per row
。
目前看起来像这样:
不过,我想在第一个项目之前插入一个,这样布局就均匀了,而且第一行和最后一行的项目数量相同。这是静态的,将始终包含 31 个项目,所以我基本上是尝试在第一个项目之前添加 left space/inset,这样它看起来像这样:
我试过使用自定义 UICollectionViewDelegateFlowLayout
方法:
collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int)
但是由于只有一个部分有 31 行,它会插入所有行,而不仅仅是第一行。
我知道我可以再添加两个“空白”项目,但我觉得有一个我可能不知道的更好的解决方案。有什么想法吗?
编辑:我试过 Tarun 的回答,但这不起作用。第一项的来源发生变化,但其余的保持原样,因此首先与第二项重叠,其余的保持原样。将它们全部转移也不起作用。我最终得到:
您需要子class UICollectionViewFlowLayout
,这将使您有机会为 collectionView 中的所有项目自定义框架。
import UIKit
class LeftAlignCellCollectionFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
guard let collectionView = self.collectionView else { return nil }
var newAttributes: [UICollectionViewLayoutAttributes] = []
let leftMargin = self.sectionInset.left
let layout = collectionView.collectionViewLayout
for attribute in attributes {
if let cellAttribute = layout.layoutAttributesForItem(at: attribute.indexPath) {
// Check for `indexPath.item == 0` & do what you want
// cellAttribute.frame.origin.x = 0 // 80
newAttributes.append(cellAttribute)
}
}
return newAttributes
}
}
现在您可以使用此自定义布局 class 作为您的流程布局,如下所示。
let flowLayout = LeftAlignCellCollectionFlowLayout()
collectionView.collectionViewLayout = flowLayout
根据 Taran 的建议,我决定使用自定义 UICollectionViewFlowLayout。这是适用于 collectionView 中任意数量的项目以及任意插入值的通用答案:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = self.collectionView else { return nil }
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
var newAttributes: [UICollectionViewLayoutAttributes] = []
for attribute in attributes {
if let cellAttribute = collectionView.collectionViewLayout.layoutAttributesForItem(at: attribute.indexPath) {
let itemSize = cellAttribute.frame.size.width + self.minimumInteritemSpacing
let targetOriginX = cellAttribute.frame.origin.x + CGFloat(self.itemInset) * itemSize
if targetOriginX <= collectionView.bounds.size.width {
cellAttribute.frame.origin.x = targetOriginX
} else {
let shiftedPosition = lround(Double((targetOriginX / itemSize).truncatingRemainder(dividingBy: CGFloat(self.numberOfColumns))))
cellAttribute.frame.origin.x = itemSize * CGFloat(shiftedPosition)
cellAttribute.frame.origin.y += itemSize
}
newAttributes.append(cellAttribute)
}
}
return newAttributes
}
其中:
self.itemInset
是我们要从左边插入的值(我最初的问题是 2,但它可以是从 0
到 number of columns-1
之间的任何数字)
self.numberOfColumns
是——顾名思义——collectionView 中的列数。这与我的示例中的天数有关,并且始终等于 7
,但人们可能希望这是其他一些用例的通用值。
为了完整起见,我提供了一种根据列数(天)计算我的 callendar 集合视图大小的方法:
private func collectionViewItemSize() -> CGSize {
let dimension = self.collectionView.frame.size.width / CGFloat(Constants.numberOfDaysInWeek) - Constants.minimumInteritemSpacing
return CGSize(width: dimension, height: dimension)
}
对我来说,Constants.numberOfDaysInWeek
自然是 7
,而 Constants.minimumInteritemSpacing
等于 2
,但这些可以是您想要的任何数字。
我有以下 UICollectionView
:
它具有 vertical
滚动、1 section
和 31 items
。
它具有基本设置,我正在计算 itemSize
以完全适合 7 per row
。
目前看起来像这样:
不过,我想在第一个项目之前插入一个,这样布局就均匀了,而且第一行和最后一行的项目数量相同。这是静态的,将始终包含 31 个项目,所以我基本上是尝试在第一个项目之前添加 left space/inset,这样它看起来像这样:
我试过使用自定义 UICollectionViewDelegateFlowLayout
方法:
collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int)
但是由于只有一个部分有 31 行,它会插入所有行,而不仅仅是第一行。 我知道我可以再添加两个“空白”项目,但我觉得有一个我可能不知道的更好的解决方案。有什么想法吗?
编辑:我试过 Tarun 的回答,但这不起作用。第一项的来源发生变化,但其余的保持原样,因此首先与第二项重叠,其余的保持原样。将它们全部转移也不起作用。我最终得到:
您需要子class UICollectionViewFlowLayout
,这将使您有机会为 collectionView 中的所有项目自定义框架。
import UIKit
class LeftAlignCellCollectionFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
guard let collectionView = self.collectionView else { return nil }
var newAttributes: [UICollectionViewLayoutAttributes] = []
let leftMargin = self.sectionInset.left
let layout = collectionView.collectionViewLayout
for attribute in attributes {
if let cellAttribute = layout.layoutAttributesForItem(at: attribute.indexPath) {
// Check for `indexPath.item == 0` & do what you want
// cellAttribute.frame.origin.x = 0 // 80
newAttributes.append(cellAttribute)
}
}
return newAttributes
}
}
现在您可以使用此自定义布局 class 作为您的流程布局,如下所示。
let flowLayout = LeftAlignCellCollectionFlowLayout()
collectionView.collectionViewLayout = flowLayout
根据 Taran 的建议,我决定使用自定义 UICollectionViewFlowLayout。这是适用于 collectionView 中任意数量的项目以及任意插入值的通用答案:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let collectionView = self.collectionView else { return nil }
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
var newAttributes: [UICollectionViewLayoutAttributes] = []
for attribute in attributes {
if let cellAttribute = collectionView.collectionViewLayout.layoutAttributesForItem(at: attribute.indexPath) {
let itemSize = cellAttribute.frame.size.width + self.minimumInteritemSpacing
let targetOriginX = cellAttribute.frame.origin.x + CGFloat(self.itemInset) * itemSize
if targetOriginX <= collectionView.bounds.size.width {
cellAttribute.frame.origin.x = targetOriginX
} else {
let shiftedPosition = lround(Double((targetOriginX / itemSize).truncatingRemainder(dividingBy: CGFloat(self.numberOfColumns))))
cellAttribute.frame.origin.x = itemSize * CGFloat(shiftedPosition)
cellAttribute.frame.origin.y += itemSize
}
newAttributes.append(cellAttribute)
}
}
return newAttributes
}
其中:
self.itemInset
是我们要从左边插入的值(我最初的问题是 2,但它可以是从0
到number of columns-1
之间的任何数字)self.numberOfColumns
是——顾名思义——collectionView 中的列数。这与我的示例中的天数有关,并且始终等于7
,但人们可能希望这是其他一些用例的通用值。
为了完整起见,我提供了一种根据列数(天)计算我的 callendar 集合视图大小的方法:
private func collectionViewItemSize() -> CGSize {
let dimension = self.collectionView.frame.size.width / CGFloat(Constants.numberOfDaysInWeek) - Constants.minimumInteritemSpacing
return CGSize(width: dimension, height: dimension)
}
对我来说,Constants.numberOfDaysInWeek
自然是 7
,而 Constants.minimumInteritemSpacing
等于 2
,但这些可以是您想要的任何数字。