我使用默认的
UICollectionViewFlowLayout
并在滚动不运动时为所选单元格设置自定义大小。我可以做什么来模仿所选单元格与其相邻单元格之间的额外间距?
如果我理解正确,为了在单元格之间具有不同的间距,我必须编写 UICollectionViewLayout 的自定义子类。然而,这个间距与滚动行为有关,并且将它们混合在一起,我对如何编写实现一无所知。我正在处理的公共 github 上的一些代码:
private func setupCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumInteritemSpacing = itemSpacing
let sideInset = (view.bounds.width - 44) / 2 // Ensure first and last items center-align
layout.sectionInset = UIEdgeInsets(top: 0, left: sideInset, bottom: 0, right: sideInset)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
// ...
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath == collectionView.indexPathsForSelectedItems?.first {
if !isScrollingInMotion {
return .init(width: 44, height: 44)
}
}
return .init(width: 32, height: 44)
}
这里我使用
isScrollingInMotion
来检查 collectionView 是否正在滚动。当调用 scrollViewDidScroll
时,它会变为 true,而当调用 scrollViewDidEndDecelerating
或 scrollViewDidEndDragging
时,它会变为 false。它还跟踪滚动是来自用户 panGesture 还是其他原因(例如,选择该项目直接导致滚动视图将单元格居中)
感谢 @matt 的建议,我成功地按预期增加了间距:
private var isScrollingInMotion = false { didSet {
guard isScrollingInMotion != oldValue else { return }
if isScrollingInMotion {
collectionView.collectionViewLayout = photoViewerScrollingLayout
} else {
collectionView.collectionViewLayout = photoViewerStaticLayout
}
}}
private let photoViewerScrollingLayout = UICollectionViewFlowLayout()
private lazy var photoViewerStaticLayout = PhotoViewerCollectionViewLayout(scrollingLayout: photoViewerScrollingLayout)
private func setupCollectionView() {
photoViewerScrollingLayout.scrollDirection = .horizontal
photoViewerScrollingLayout.minimumInteritemSpacing = itemSpacing
let sideInset = (view.bounds.width - 44) / 2 // Ensure first and last items center-align
photoViewerScrollingLayout.sectionInset = UIEdgeInsets(top: 0, left: sideInset, bottom: 0, right: sideInset)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: photoViewerStaticLayout)
我的布局属性:
/// use when scrolling is not in motion
final class PhotoViewerCollectionViewLayout: UICollectionViewFlowLayout {
init(scrollingLayout: UICollectionViewFlowLayout) {
super.init()
scrollDirection = scrollingLayout.scrollDirection
minimumInteritemSpacing = scrollingLayout.minimumInteritemSpacing
sectionInset = scrollingLayout.sectionInset
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }
guard let collectionView = collectionView else { return attributes }
guard let selectedIndexPath = collectionView.indexPathsForSelectedItems?.first else { return attributes }
let updatedAttributes = attributes.map { $0.copy() as! UICollectionViewLayoutAttributes }
let itemSpacing = minimumInteritemSpacing + 5
for attribute in updatedAttributes {
if attribute.indexPath.item > selectedIndexPath.item {
// Custom item on the right
attribute.frame.origin.x += itemSpacing
} else if attribute.indexPath.item == selectedIndexPath.item {
attribute.size.width = attribute.size.height
} else {
// Custom item on the left
attribute.frame.origin.x -= itemSpacing
}
}
return updatedAttributes
}
}
动画看起来不错(有点抓不住,但可以接受)
private func snapSelectedCenter() {
guard !isUserScrolling else { return }
guard let indexPath = collectionView.indexPathsForSelectedItems?.first else { return }
UIView.animate(withDuration: 0.2) {
if !self.isScrollingInMotion {
self.collectionView.collectionViewLayout.invalidateLayout()
}
self.isScrollingInMotion = false
self.collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
isUserScrolling = true
UIView.animate(withDuration: 0.2) {
self.isScrollingInMotion = true
self.collectionView.collectionViewLayout.invalidateLayout()
}
}
现在,只有默认值
minimumInteritemSpacing
存在问题:如果itemSpacing: CGFloat
不是8(例如0),当我滚动(userBeginDragging)时,间距突然被设置为默认值(8)而不是我的预设号码(例如 0)。也许我应该进一步子类化?我不确定...