iosxamarin.iosuicollectionviewuipagecontrol

How to get a dotted page indicator for UICollectionView?


I have a UICollectionView that has the following settings,

public CollectionView(CollectionLayout layout) : base(CGRect.Empty, layout)
{
    RegisterClassForCell(typeof(CollectionCell), CollectionCell.CellIdentifier);
    CollectionViewLayout = layout;
    ShowsHorizontalScrollIndicator = false;
    PagingEnabled = true;
}

The CollectionViewLayout is,

public CollectionLayout()
{
    ScrollDirection = UICollectionViewScrollDirection.Horizontal;
    MinimumInteritemSpacing = 0f;
    MinimumLineSpacing = 0f;
    ItemSize = new CGSize(UIScreen.MainScreen.Bounds.Width, 200f);
}

so the cells in the CollectionView are stretched so that a cell fills the CollectionView. The CollectionView is only horizontally scrollable.

Now I want to have a dotted page indicator instead of the scroll bar for the CollectionView. Is there anyway I can achieve this properly?


Solution

  • Perfectly correct approach for 2020:

    Simply add a UIPageControl in storyboard.

    Put it below (i.e., visible on top of) your collection view.

    enter image description here

    Link to it...

    class YourVC: UIViewController, UICollectionViewDelegate,
             UICollectionViewDataSource,
             UICollectionViewDelegateFlowLayout {
        
        @IBOutlet var collectionView: UICollectionView!
        @IBOutlet var dots: UIPageControl!
    

    Simply add a constraint centering it horizontally to the collection view, and add a constraint to align the bottoms.

    That will give the standard positioning / spacing.

    (Of course, you can place the dots anywhere you want, but that is the standard.)

    enter image description here

    Tip 1 - colors

    Bizarrely the default colors for the dots are .. clear!

    So set them to gray/black or whatever you wish:

    enter image description here

    Or you can do that in code:

    override func viewDidLoad() {
        super.viewDidLoad()
        dots.pageIndicatorTintColor = .systemGray5
        dots.currentPageIndicatorTintColor = .yourCorporateColor
    }
    

    Next. In numberOfItemsInSection, add ...

    func collectionView(_ collectionView: UICollectionView,
                  numberOfItemsInSection section: Int) -> Int {
        
        let k = ... yourData.count, or whatever your count is
        
        dots.numberOfPages = k
        return k
    }
    

    Tip 2 - in fact, do NOT use the deceleration calls

    Add this code:

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        dots.currentPage = Int(
            (collectionView.contentOffset.x / collectionView.frame.width)
                .rounded(.toNearestOrAwayFromZero)
            )
        )
    }
    

    You simply set the page in "scrollViewDidScroll".

    In fact

    do not use scrollViewWillBeginDecelerating

    do not use scrollViewDidEndDecelerating.

    To see why: try it using either of those calls. Now skim quickly through many pages. Notice it does not work properly.

    Simply use scrollViewDidScroll for the correct, perfect result, including initialization.

    Tip 3 - do NOT use Int division - it completely changes the behavior and is totally wrong.

    You will often see this example code:

    // wrong, do not do this
    dots.currentPage = Int(collectionView.contentOffset.x) /
                         Int(collectionView.frame.width)
    // wrong, do not do this
    

    That often-seen example code is completely wrong.

    If you try that, it will result in the dots "jumping" in a non-standard way, as you skim through pages.

    Best explanation is to try it and see.

    For the usual, correct, Apple-style behavior as you scroll through or skim through the pages, the code is:

        dots.currentPage = Int(
            (collectionView.contentOffset.x / collectionView.frame.width)
            .rounded(.toNearestOrAwayFromZero)
        )
    

    Final example...

    enter image description here