#1。使用UICollectionViewCompositionalLayout
(需要 iOS 13)
从 iOS 13 开始,您可以使用UICollectionViewCompositionalLayout
并将NSCollectionLayoutSection
'orthogonalScrollingBehavior
属性设置.groupPagingCentered
为以具有居中的水平轮播式布局。
以下 Swift 5.1 示例代码显示了一个可能的实现,UICollectionViewCompositionalLayout
以便获得您想要的布局:
CollectionView.swift
import UIKit
class CollectionView: UICollectionView {
override var safeAreaInsets: UIEdgeInsets {
return UIEdgeInsets(top: super.safeAreaInsets.top, left: 0, bottom: super.safeAreaInsets.bottom, right: 0)
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
var collectionView: CollectionView!
var dataSource: UICollectionViewDiffableDataSource<Int, Int>!
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Collection view"
// Compositional layout
let layout = UICollectionViewCompositionalLayout(sectionProvider: {
(sectionIndex: Int, layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalHeight(1), heightDimension: .fractionalHeight(1))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = UICollectionLayoutSectionOrthogonalScrollingBehavior.groupPagingCentered
return section
})
// Set collection view
collectionView = CollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = .systemGroupedBackground
collectionView.showsHorizontalScrollIndicator = false
collectionView.register(Cell.self, forCellWithReuseIdentifier: "Cell")
// View layout
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.heightAnchor.constraint(equalToConstant: 160).isActive = true
collectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
// Collection view diffable data source
dataSource = UICollectionViewDiffableDataSource<Int, Int>(collectionView: collectionView, cellProvider: {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: Int) -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
return cell
})
var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
snapshot.appendSections([0])
snapshot.appendItems(Array(0 ..< 5))
dataSource.apply(snapshot, animatingDifferences: false)
}
}
细胞.swift
import UIKit
class Cell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .orange
}
required init?(coder: NSCoder) {
fatalError("not implemnted")
}
}
#2。使用UICollectionViewFlowLayout
如果您的目标是低于 iOS 13 的 iOS 版本,您可以子类UICollectionViewFlowLayout
化、计算水平插入prepare()
并实现targetContentOffset(forProposedContentOffset:withScrollingVelocity:)
以在用户滚动后强制单元格居中。
以下 Swift 5.1 示例代码展示了如何实现 的子类UICollectionViewFlowLayout
:
ViewController.swift
import UIKit
class ViewController: UIViewController {
let flowLayout = PaggedFlowLayout()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Collection view"
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
collectionView.backgroundColor = .systemGroupedBackground
collectionView.showsHorizontalScrollIndicator = false
collectionView.decelerationRate = .fast
collectionView.dataSource = self
collectionView.contentInsetAdjustmentBehavior = .never
collectionView.register(Cell.self, forCellWithReuseIdentifier: "Cell")
view.addSubview(collectionView)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.heightAnchor.constraint(equalToConstant: 160).isActive = true
collectionView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 9
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
return cell
}
}
PaggedFlowLayout.swift
import UIKit
class PaggedFlowLayout: UICollectionViewFlowLayout {
override init() {
super.init()
scrollDirection = .horizontal
minimumLineSpacing = 5
minimumInteritemSpacing = 0
sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare() {
super.prepare()
guard let collectionView = collectionView else { fatalError() }
// itemSize
let itemHeight = collectionView.bounds.height - sectionInset.top - sectionInset.bottom
itemSize = CGSize(width: itemHeight, height: itemHeight)
// horizontal insets
let horizontalInsets = (collectionView.bounds.width - itemSize.width) / 2
sectionInset.left = horizontalInsets
sectionInset.right = horizontalInsets
}
/*
Add some snapping behaviour to center the cell after scrolling.
Source: https://stackoverflow.com/a/14291208/1966109
*/
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return .zero }
var proposedContentOffset = proposedContentOffset
var offsetAdjustment = CGFloat.greatestFiniteMagnitude
let horizontalCenter = proposedContentOffset.x + collectionView.bounds.size.width / 2
let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.size.width, height: collectionView.bounds.size.height)
guard let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect) else { return .zero }
for layoutAttributes in layoutAttributesArray {
let itemHorizontalCenter = layoutAttributes.center.x
if abs(itemHorizontalCenter - horizontalCenter) < abs(offsetAdjustment) {
offsetAdjustment = itemHorizontalCenter - horizontalCenter
}
}
var nextOffset = proposedContentOffset.x + offsetAdjustment
let snapStep = itemSize.width + minimumLineSpacing
func isValidOffset(_ offset: CGFloat) -> Bool {
let minContentOffset = -collectionView.contentInset.left
let maxContentOffset = collectionView.contentInset.left + collectionView.contentSize.width - itemSize.width
return offset >= minContentOffset && offset <= maxContentOffset
}
repeat {
proposedContentOffset.x = nextOffset
let deltaX = proposedContentOffset.x - collectionView.contentOffset.x
let velX = velocity.x
if deltaX.sign.rawValue * velX.sign.rawValue != -1 {
break
}
nextOffset += CGFloat(velocity.x.sign.rawValue) * snapStep
} while isValidOffset(nextOffset)
return proposedContentOffset
}
}
细胞.swift
import UIKit
class Cell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .orange
}
required init?(coder: NSCoder) {
fatalError("not implemnted")
}
}
iPhone 11 Pro Max 上的显示: