Прокрутка и разбивка на страницы в UICollectionView

Это логика привязки для представления коллекции с ячейками одинакового размера и одним разделом (можно легко добавить логику для большего количества разделов).

scrollViewWillEndDragging имеет параметр inout targetContentOffset, что означает, что мы можем читать и изменять конечную позицию прокрутки. К счастью, нам не нужно учитывать вставки, интервалы между строками или элементами (потеряли много времени, включив их, а затем не смогли понять, почему правильная математика дает неправильные результаты), но нам действительно необходимо рассмотреть случай, когда пользователь прокручивает последнюю страницу - targetContentOffset будет в пределах границ, а текущий contentOffset - нет, поэтому нам нужно также проверьте это:

// Get our cell width 
let cellWidth = collectionView(
   collectionView, 
   layout: collectionView.collectionViewLayout, 
   sizeForItemAtIndexPath: NSIndexPath(forItem: 0, inSection: 0) ).width 
let page: CGFloat // Calculate the proposed "page" 
let proposedPage = targetContentOffset.memory.x / cellWidth 
// 3.25 should return page 3: floor(3.95) == floor(3)
// 3.3+ should return page 4: floor(4.0+) != floor(3)
if floor(proposedPage + 0.7) == floor(proposedPage) 
   && scrollView.contentOffset.x <= targetContentOffset.memory.x { 
   page = floor(proposedPage) 
} 
else { 
   page = floor(proposedPage + 1) 
}
// Replace the end position of the scroll 
targetContentOffset.memory = CGPoint(
   x: cellWidth * page, 
   y: targetContentOffset.memory.y
)

Если требуется «истинная» разбивка на страницы, например, при прокрутке по одной странице за раз, нам нужно немного изменить:

// We need to save the starting point 
private var startingScrollingOffset = CGPoint.zero 
func scrollViewWillBeginDragging(scrollView: UIScrollView) { 
   startingScrollingOffset = scrollView.contentOffset 
} 
func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { 
   // [...] 
   // First, we use the current contentOffset 
   // instead of the target one 
   let proposedPage = scrollView.contentOffset.x / cellWidth
  
   // If we scroll forward, we need to pass 10% of a page: 
   // floor(3.1 + 0.9) != floor(3)
   
   // If we scroll backwards, we need to reach below 90% 
   // of the previous one: floor(2.89 + 0.1) == floor(2)
   
   let delta: CGFloat = scrollView.contentOffset.x 
      > startingScrollingOffset.x ? 0.9 : 0.1
   // Then, instead of using a flat value, we use the delta value, 
   // and we also remove the targetContentOffset logic 
   if floor(proposedPage + delta) == floor(proposedPage) { 
   // [...]
}

Хотя процентные значения были выбраны случайным образом, 0,1 кажется немного лучше для истинной разбивки на страницы, а 0,3 - лучше для мгновенной прокрутки.

Вы можете найти больше подобных статей в моем блоге или подписаться на мою ежемесячную рассылку новостей. Первоначально опубликовано на rolandleth.com.