20

I'm currently using a UICollectionView to display 3 images (each image spans the entire cell). I also have a UIPageControl that I placed on top of the UICollectionView. What I want to happen is to have the UIPageControl show the number of images (which in this case is 3), and also which image the user is currently viewing. The effect I am trying to go for is that of the Instagram app.

The way that I am currently using to achieve this effect is by placing the updating of the UIPageControl within the UICollectionView's willDisplay function, like so:

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    pictureDots.currentPage = indexPath.item
}

This manages to correctly hook up the paging effect between the collection view and page control. However, the problem that I have is that the UIPageControl starts off saying the user is on the third image, even though it is displaying the first image.

Does anyone know why this is happening and how to fix this problem?

Dennis Vennink
  • 1,083
  • 1
  • 7
  • 23
kbunarjo
  • 1,277
  • 2
  • 11
  • 27

5 Answers5

40

Firstly add your UIPageControl into your storyboard with your UICollectionView, then connect them as outlets to your view controller.

@IBOutlet var pageControl: UIPageControl!
@IBOutlet var collectionView: UICollectionView!

Adjust your numberOfItemsInSection method in UICollectionViewDataSource to set the count of the page control to always be equal to the number of cells in the collection view.

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    let count = ...

    pageControl.numberOfPages = count
    pageControl.isHidden = !(count > 1)

    return count
}

Lastly, using the UIScrollViewDelegate, we can tell which cell the UICollectionView stops on. If you are not using a UICollectionViewController, you may have to add the delegate protocol.

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {

    pageControl?.currentPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
}

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {

    pageControl?.currentPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
}

This is possible because a UICollectionView is in fact a UIScrollView under the hood.

Callam
  • 11,409
  • 2
  • 34
  • 32
  • How do you decide the number of pages when there can be n number of pages? – Hemang Mar 08 '19 at 03:31
  • 1
    @Hemang the number of pages should be equivalent to the number of items in the collection view. Inside `numberOfItemsInSection`, you usually return the number of elements in the data source. The number is intercepted here as `count` and the number of pages is set. – Callam Mar 08 '19 at 04:08
  • In my case, I have a fixed size collection view which will show 5 rows in a column. So lets say if there are 20 records it will show 4 columns with 5 rows in each. So what I need is to get that 4 runtime. – Hemang Mar 08 '19 at 05:17
  • Will this work if Collection view is inside a TableView cell ? and collection view datasource and delegate is ViewController class that holds TableView not TableViewcell class – Anees Sep 20 '20 at 20:28
21

Step 1 Create the variables for Collection View and Page Control

@IBOutlet weak var collectionView: UICollectionView!
@IBOutlet var pageControl:UIPageControl!     

Step 2 Set the number of pages of Page Control

override func viewDidLoad() {
    super.viewDidLoad()         
    self.pageControl.numberOfPages = procedures.count
    //Set the delegate
    self.collectionView.delegate = self
}

*Step 3 In the scrollViewDidScroll function calculate the width of collection cell and the index for the current page.

    extension YourCollectionVC: UICollectionViewDataSource, UICollectionViewDelegate {
            override func scrollViewDidScroll(_ scrollView: UIScrollView) {
                let witdh = scrollView.frame.width - (scrollView.contentInset.left*2)
                let index = scrollView.contentOffset.x / witdh
                let roundedIndex = round(index)
                self.pageControl?.currentPage = Int(roundedIndex)
            }
}

Note: This is for collection view displayed horizontally, for vertical direccion change the method.

Tested in swift 4.

Hardik Thakkar
  • 15,269
  • 2
  • 94
  • 81
Ariel Antonio Fundora
  • 1,330
  • 15
  • 20
4

Use this for smooth functioning.

 func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let witdh = scrollView.frame.width - (scrollView.contentInset.left*2)
    let index = scrollView.contentOffset.x / witdh
    let roundedIndex = round(index)
    self.pageControl.currentPage = Int(roundedIndex)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {

    pageControl.currentPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
}

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {

    pageControl.currentPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
}
Pathak Ayush
  • 726
  • 12
  • 24
3

With help of Combine, we can easily connect UIPageControl with any ScrollView.

var collectionView: UICollectionView
var bin: Set<AnyCancellable> = []
var pageControl: UIPageControl
// setup observer
collectionView
    .publisher(for: \.contentOffset)
    .map { [unowned self] offset in
        Int(round(offset.x / max(1, self.collectionView.bounds.width)))
    }
    .removeDuplicates()
    .assign(to: \.currentPage, on: pageControl) // may cause memory leak use sink
    .store(in: &bin)
SPatel
  • 4,768
  • 4
  • 32
  • 51
0
//For every iPhone screen, it is working(Swift 5)...

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
    let offSet = scrollView.contentOffset.x
    let width = scrollView.frame.width
    let horizontalCenter = width / 2

    _pageControl.currentPage = Int(offSet + horizontalCenter) / Int(width)        
}

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    pageControl.currentPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
}
Saurabh Sharma
  • 1,671
  • 2
  • 13
  • 16