While studying iOS6 new features I got a question about UICollectionView.
I am currently testing it with Flow layout and the scroll direction set to horizontal, scrolli
I would recommend a little tuned calculation and handling as it will update page control immediately in any scroll position with better accuracy.
The solution below works with any scroll view or it subclass (UITableView UICollectionView and others)
in viewDidLoad method write this
scrollView.delegate = self
then use code for your language:
Swift 3
func scrollViewDidScroll(_ scrollView: UIScrollView)
{
let pageWidth = scrollView.frame.width
pageControl.currentPage = Int((scrollView.contentOffset.x + pageWidth / 2) / pageWidth)
}
Swift 2:
func scrollViewDidScroll(scrollView: UIScrollView)
{
let pageWidth = CGRectGetWidth(scrollView.frame)
pageControl.currentPage = Int((scrollView.contentOffset.x + pageWidth / 2) / pageWidth)
}
Objective C
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat pageWidth = self.collectionView.frame.size.width;
self.pageControl.currentPage = (self.collectionView.contentOffset.x + pageWidth / 2) / pageWidth;
}
You must setup yourself as UIScrollViewDelegate
and implement the scrollViewDidEndDecelerating:
method like so:
Objective-C
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGFloat pageWidth = self.collectionView.frame.size.width;
self.pageControl.currentPage = self.collectionView.contentOffset.x / pageWidth;
}
Swift
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let pageWidth = self.collectionView.frame.size.width
pageControl.currentPage = Int(self.collectionView.contentOffset.x / pageWidth)
}
I know this is an old one but I've just needed to implement this sort of feature again and have a bit to add which gives a more complete answer.
Firstly: Using scrollViewDidEndDecelerating
assumes that the user lifted their finger while dragging (more like a flick action) and therefore there is a deceleration phase. If the user drags without lifting the finger the UIPageControl
will still indicate the old page from before the drag began. Instead using the scrollViewDidScroll
callback means that the view is updated both after dragging and flicking and also during dragging and scrolling so it feels much more responsive and accurate for the user.
Secondly: Relying on the pagewidth
for calculating the selected index assumes all the cells have the same width and that there is one cell per screen. taking advantage of the indexPathForItemAtPoint
method on UICollectionView
gives a more resilient result which will work for different layouts and cell sizes. The implementation below assumes the centre of the frame is the desired cell to be represented in the pagecontrol
. Also if there are intercell spacings there will times during scrolling when the selectedIndex could be nil or optional so this needs to be checked and unwrapped before setting on the pageControl.
func scrollViewDidScroll(scrollView: UIScrollView) {
let contentOffset = scrollView.contentOffset
let centrePoint = CGPointMake(
contentOffset.x + CGRectGetMidX(scrollView.frame),
contentOffset.y + CGRectGetMidY(scrollView.frame)
)
if let index = self.collectionView.indexPathForItemAtPoint(centrePoint){
self.pageControl.currentPage = index.row
}
}
One more thing - set the number of pages on the UIPageControl
with something like this:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
self.pageControl.numberOfPages = 20
return self.pageControl.numberOfPages
}
Another option with less code is to use visible item index path and set the page control.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.pageControl.currentPage = [[[self.collectionView indexPathsForVisibleItems] firstObject] row];
}
int pages =floor(ImageCollectionView.contentSize.width/ImageCollectionView.frame.size.width); [pageControl setNumberOfPages:pages];
Add the ScrollView Delegate method,
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
CGFloat pageWidth = ImageCollectionView.frame.size.width;
float currentPage = ImageCollectionView.contentOffset.x / pageWidth;
if (0.0f != fmodf(currentPage, 1.0f))
{
pageControl.currentPage = currentPage + 1;
}
else
{
pageControl.currentPage = currentPage;
}
NSLog(@"finishPage: %ld", (long)pageControl.currentPage);
}
Simple Swift
public func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
pageControl.currentPage = (collectionView.indexPathsForVisibleItems().first?.row)!
}
UIScrollViewDelegate
is already implemented if you implement UICollectionViewDelegate