У меня есть собственный макет для collectionView.Этот пользовательский макет увеличивает ширину центральной ячейки.Вот пользовательский класс макета, который делает это.Посмотрите на функцию shifttedAttributes, чтобы увидеть, как это делается
class CustomCollectionViewLayout: UICollectionViewLayout {
private var cache = [IndexPath: UICollectionViewLayoutAttributes]()
private var contentWidth = CGFloat()
private var visibleLayoutAttributes = [UICollectionViewLayoutAttributes]()
private var oldBounds = CGRect.zero
private var cellWidth: CGFloat = 5
private var collectionViewStartY: CGFloat {
guard let collectionView = collectionView else {
return 0
}
return collectionView.bounds.minY
}
private var collectionViewHeight: CGFloat {
return collectionView!.frame.height
}
override public var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: collectionViewHeight)
}
override public func prepare() {
print("calling prepare")
guard let collectionView = collectionView,
cache.isEmpty else {
return
}
updateInsets()
collectionView.decelerationRate = .fast
cache.removeAll(keepingCapacity: true)
cache = [IndexPath: UICollectionViewLayoutAttributes]()
oldBounds = collectionView.bounds
var xOffset: CGFloat = 0
var cellWidth: CGFloat = 5
for item in 0 ..< collectionView.numberOfItems(inSection: 0) {
let cellIndexPath = IndexPath(item: item, section: 0)
let cellattributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)
cellattributes.frame = CGRect(x: xOffset, y: 0, width: cellWidth, height: collectionViewHeight)
xOffset = xOffset + cellWidth
contentWidth = max(contentWidth,xOffset)
cache[cellIndexPath] = cellattributes
}
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
visibleLayoutAttributes.removeAll(keepingCapacity: true)
for (_, attributes) in cache {
visibleLayoutAttributes.append(self.shiftedAttributes(from: attributes))
}
return visibleLayoutAttributes
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let attributes = cache[indexPath] else { fatalError("No attributes cached") }
return shiftedAttributes(from: attributes)
}
override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
if oldBounds.size != newBounds.size {
cache.removeAll(keepingCapacity: true)
}
return true
}
override func invalidateLayout(with context: UICollectionViewLayoutInvalidationContext) {
if context.invalidateDataSourceCounts { cache.removeAll(keepingCapacity: true) }
super.invalidateLayout(with: context)
}
}
extension CustomCollectionViewLayout {
func updateInsets() {
guard let collectionView = collectionView else { return }
collectionView.contentInset.left = (collectionView.bounds.size.width - cellWidth) / 2
collectionView.contentInset.right = (collectionView.bounds.size.width - cellWidth) / 2
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
guard let collectionView = collectionView else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset) }
let midX: CGFloat = collectionView.bounds.size.width / 2
guard let closestAttribute = findClosestAttributes(toXPosition: proposedContentOffset.x + midX) else { return super.targetContentOffset(forProposedContentOffset: proposedContentOffset) }
return CGPoint(x: closestAttribute.center.x - midX, y: proposedContentOffset.y)
}
private func findClosestAttributes(toXPosition xPosition: CGFloat) -> UICollectionViewLayoutAttributes? {
guard let collectionView = collectionView else { return nil }
let searchRect = CGRect(
x: xPosition - collectionView.bounds.width, y: collectionView.bounds.minY,
width: collectionView.bounds.width * 2, height: collectionView.bounds.height
)
let closestAttributes = layoutAttributesForElements(in: searchRect)?.min(by: { abs($0.center.x - xPosition) < abs($1.center.x - xPosition) })
return closestAttributes
}
private var continuousFocusedIndex: CGFloat {
guard let collectionView = collectionView else { return 0 }
let offset = collectionView.bounds.width / 2 + collectionView.contentOffset.x - cellWidth / 2
return offset / cellWidth
}
private func shiftedAttributes(from attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
guard let attributes = attributes.copy() as? UICollectionViewLayoutAttributes else { fatalError("Couldn't copy attributes") }
let roundedFocusedIndex = round(continuousFocusedIndex)
let focusedItemWidth = CGFloat(20)
if attributes.indexPath.item == Int(roundedFocusedIndex){
attributes.transform = CGAffineTransform(scaleX: 10, y: 1)
} else {
let translationDirection: CGFloat = attributes.indexPath.item < Int(roundedFocusedIndex) ? -1 : 1
attributes.transform = CGAffineTransform(translationX: translationDirection * 20, y: 0)
}
return attributes
}
}
Вот контроллер представления, который содержит collectionView, который использует этот макет:
class ViewController: UIViewController, UICollectionViewDelegate,UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = customCollectionView.dequeueReusableCell(withReuseIdentifier: "singleCell", for: indexPath)
cell.backgroundColor = UIColor.random()
return cell
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
@IBOutlet weak var picker: UIPickerView!
@IBOutlet weak var customCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
customCollectionView.delegate = self
customCollectionView.dataSource = self
picker.delegate = self
picker.dataSource = self
// Do any additional setup after loading the view, typically from a nib.
}
@IBAction func goTo(_ sender: Any) {
let indexPath = IndexPath(item: picker.selectedRow(inComponent: 0), section: 0)
customCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
}
extension ViewController: UIPickerViewDelegate, UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return 10
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return String(row)
}
}
Обратите внимание на представление выбора, где вы можете выбратьИндекс, который используется в кнопке «Перейти» для прокрутки до элемента с этим индексом.Вот оно в действии:
Посмотрите, как, несмотря на то, что я остаюсь с тем же индексом, он продолжает прокручивать и на самом деле не прокручиваетк этому индексу в любом случае.Когда я не сдвигаю атрибуты (с помощью shifttedAttributes) и просто возвращаю их нормально в пользовательском макете, scrollTo работает нормально.
Так что, кажется, что-то в отношении размещения каждой ячейки используется при выполнении scrollToItem (at :), которое сбивается с толку смещенными атрибутами?Как прокрутить до определенного индекса, когда размеры ячеек могут быть изменены?
РЕДАКТИРОВАТЬ: здесь - это весь код проекта, если вы хотите попробовать его самостоятельно: