49

I have UICollectionView Horizontal Image listing code. I want to add PageControl when scrolling images will shows, I added pagecontrol selector and IBOutlet but how can I integrate it between UICollecitonView?

My code is below:

 class  ViewController: UIViewController,  UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    
    
    @IBOutlet weak var View : DesignableView!
   
    
    @IBOutlet var collectionView: UICollectionView!
    @IBOutlet var collectionViewLayout: UICollectionViewFlowLayout!
    
    @IBOutlet open weak var pageControl: UIPageControl? {
        didSet {
            pageControl?.addTarget(self, action: #selector(ViewController.pageChanged(_:)), for: .valueChanged)
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
         pageControl?.numberOfPages = 11
        
        
    }
    
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 11;
    }
    
    
 
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell: ImageCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCollectionViewCell", for: indexPath) as! ImageCollectionViewCell
        cell.label.text = "Cell \(indexPath.row)"
        cell.backgroundImageView.image = UIImage(named: "Parallax \(indexPath.row + 1)")
        
        // Parallax cell setup
        cell.parallaxTheImageViewScrollOffset(self.collectionView.contentOffset, scrollDirection: self.collectionViewLayout.scrollDirection)
        return cell
    }
    
    
    
    @IBAction open func pageChanged(_ sender: UIPageControl) {
      
        print(sender.currentPage)
        
        
    }
    
  
    
    
    // MARK: Delegate
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return collectionView.bounds.size;
    }
    
    // MARK: Scrolling
    
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // Parallax visible cells
        for cell: ImageCollectionViewCell in collectionView.visibleCells as! [ImageCollectionViewCell] {
            cell.parallaxTheImageViewScrollOffset(self.collectionView.contentOffset, scrollDirection: self.collectionViewLayout.scrollDirection)
        }
    }

    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    
    
}
starball
  • 20,030
  • 7
  • 43
  • 238
SwiftDeveloper
  • 7,244
  • 14
  • 56
  • 85

13 Answers13

150

I'm not a fan of the accepted answer. From time to time, willDisplay cell will return the wrong page for the page control depending on users interaction.

What I use:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    pageControl.currentPage = Int(scrollView.contentOffset.x) / Int(scrollView.frame.width)
}

This will update the page after the user has finished scrolling thus making the currentPage of the pageControl more accurate.

luke
  • 2,743
  • 4
  • 19
  • 43
  • how to make it work with a right to left pageControl? – Ben Shabat Jan 23 '19 at 08:38
  • The problem I am facing is that the collection view always starts with the first page. How do I initialise the collection view to start somewhere from the middle page? – user2990765 Jan 25 '21 at 06:43
67

Here you have a complete class with Page Control pagination in a horizontal collection view. This is working on one of my applications, right now, and is working correctly. If you cannot get it work, do feel free to ask me and I'll help you.

First, in the Storyboard you have to set up your CollectionView with:

Layout: Flow
Scroll Direction: Horizontal
Scrolling enabled
Paging enabled

@IBOutlet weak var collectionView: UICollectionView! //img: 77

@IBOutlet weak var pageControl: UIPageControl!

var thisWidth:CGFloat = 0

override func awakeFromNib() {
    super.awakeFromNib()

    thisWidth = CGFloat(self.frame.width)
    collectionView.delegate = self
    collectionView.dataSource = self

    pageControl.hidesForSinglePage = true

}

func numberOfSections(in collectionView: UICollectionView) -> Int {
    return 10
}

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

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCell", for: indexPath)

    return cell
}

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    self.pageControl.currentPage = indexPath.section
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    thisWidth = CGFloat(self.frame.width)
    return CGSize(width: thisWidth, height: self.frame.height)
}
J Manuel
  • 3,010
  • 22
  • 39
  • dude dont work with pagecontrol when i added func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.myarray.count // HERE dont work page control } – SwiftDeveloper Dec 07 '16 at 10:29
  • 2
    That's because in order to make a page for each item, you have to use sections, not rows – J Manuel Dec 07 '16 at 11:11
  • 6
    But with this solution, when the user swipes to the next page and then swipes back again without lifting his finger, the pageControl shows the wrong page. I think you should consider @luke's answer – gasparuff Sep 19 '17 at 12:10
  • That's something you should clearly state in your answer – Nico Dec 07 '17 at 01:28
57

For a more responsive UIPageControl, I prefer to use scrollViewDidScroll and calculating the offset with regards to the horizontal center of the scrollView.

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

    pageControl.currentPage = Int(offSet + horizontalCenter) / Int(width)
}
David Baez
  • 1,208
  • 1
  • 14
  • 26
28

Swift 5 Very Smooth working

//MARK:- For Display the page number in page controll of collection view Cell
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let visibleRect = CGRect(origin: self.collectionview.contentOffset, size: self.collectionview.bounds.size)
    let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
    if let visibleIndexPath = self.collectionview.indexPathForItem(at: visiblePoint) {
        self.pageControl.currentPage = visibleIndexPath.row
    }
}
Shakeel Ahmed
  • 5,361
  • 1
  • 43
  • 34
12

Waiting for scrollViewDidEndDecelerating results in a slow update to the UIPageControl. If you want a more responsive UI, I suggest instead implementing scrollViewWillEndDragging(_:withVelocity:targetContentOffset:).

Swift 4 example where each collection view section is a page:

override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    if let collectionView = scrollView as? ScoreCollectionView,
        let section = collectionView.indexPathForItem(at: targetContentOffset.pointee)?.section {
        self.pageControl.currentPage = section
    }
}
Chris Chute
  • 3,229
  • 27
  • 18
10

swift 5 example:

function to set pageController CurrentPage according to Index of CollectionView:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
   let visibleRect = CGRect(origin: self.collectionView.contentOffset, size: self.collectionView.bounds.size)
   let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
   if let visibleIndexPath = self.collectionView.indexPathForItem(at: visiblePoint) {
            self.pageController.currentPage = visibleIndexPath.row
   }
}

PageControllerAction function to scroll CollectionView on selecting PageController :

@IBAction func pageControllerAction(_ sender: UIPageControl) {
       self.collectionView.scrollToItem(at: IndexPath(row: sender.currentPage, section: 0), at: .centeredHorizontally, animated: true)
}
Rahul Gusain
  • 269
  • 3
  • 6
8

Luke's answer was a good start for me but it had two issues: it didn't update frequently (already noted in other answers) but most importantly it didn't show the correct page for the last item in my collection view (horizontally oriented) and it only switched page when the item was almost leaving the screen.

For the first issue, as others noted, using scrollViewDidScroll provided a better experience. I was concerned about performance issues for making the call so frequently but didn't notice any.

As for the second issue, for my use case, finding the indexPath of the cell that is at the center of the screen provided a better solution. Keep in mind that if your use case can fit more than two cells on screen this may need to be adjusted. This is because your last cell would never reach the middle of the screen. I would argue though that in that case using the UIPageControl might not be ideal anyway.

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let visibleRect = CGRect(origin: self.collectionView.contentOffset, size: self.collectionView.bounds.size)
    let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
    if let visibleIndexPath = self.collectionView.indexPathForItem(at: visiblePoint) {
        self.pageControl.currentPage = visibleIndexPath.row
    }
}
lbarbosa
  • 2,042
  • 20
  • 23
4
 func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let visibleRect = CGRect(origin: self.cvImageListing.contentOffset, size: self.cvImageListing.bounds.size)
        let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
        if let visibleIndexPath = self.cvImageListing.indexPathForItem(at: visiblePoint) {
            self.pageControl.currentPage = visibleIndexPath.row
        }
    }
Davender Verma
  • 503
  • 2
  • 12
0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let OFFSet = scrollView.contentOffset.x
    let width = scrollView.frame.width
    let horizontalCenter = width / 2
    pageviewControl.currentPage = Int(OFFSet + horizontalCenter) / Int(width)
}
Dhaval Gevariya
  • 870
  • 1
  • 12
  • 16
0

This seems to work for me:

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        let x = targetContentOffset.pointee.x
        pageControl.currentPage = Int(x / view.frame.width)
    }
0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let pageWidth = scrollView.frame.size.width
    let currP = Int(floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1)
    if self.pageC.currentPage != currP {
        self.pageC.currentPage = currP
    }
}
Prakash Raj
  • 1,943
  • 1
  • 15
  • 13
0

class ViewController: UIViewController {

@IBOutlet weak var imagesCollection: UICollectionView!
@IBOutlet weak var pageView: UIPageControl!

var imageArray = [UIImage(named:"background"),
                  UIImage(named:"background") ,
                  UIImage(named:"background"),
                  UIImage(named:"background"),
                  UIImage(named:"background") ,
                  UIImage(named:"background"),
                  UIImage(named:"background"),
                  UIImage(named:"background")]

var selectedIndex: Int?


override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
    imagesCollection.dataSource = self
    imagesCollection.delegate = self
    
    pageView.numberOfPages = imageArray.count
    pageView.currentPage = 0
    pageView.hidesForSinglePage = true

}

}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return imageArray.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailsImagesCell", for: indexPath) as! DetailsImagesCell
    cell.images.image = imageArray[indexPath.row]
    cell.images.backgroundColor = .red
    return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    self.pageView.currentPage = indexPath.item
}

}

extension ViewController: UICollectionViewDelegateFlowLayout {

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    return UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let size = imagesCollection.frame.size
    return CGSize(width: size.width, height: size.height)
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
    return 0.0
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
    return 0.0
}

}

Raj.Rathod
  • 99
  • 4
-3

Simply add this function

func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView){
    //Test the offset and calculate the current page after scrolling ends
    let pageWidth:CGFloat = scrollView.frame.width
    let currentPage:CGFloat = floor((scrollView.contentOffset.x-pageWidth/2)/pageWidth)+1
    //Change the indicator
    self.pageControl.currentPage = Int(currentPage);
}
Wimukthi Rajapaksha
  • 961
  • 1
  • 11
  • 23
  • When answering a three year old question with eight other answers you need to explain what is new and different about your answer. Proper formatting for a block of code is not the same and formatting each line as a section of inline code. Code only answers need explanation of how and why they work. – Jason Aller Apr 09 '20 at 07:00
  • This is another method if you can Compare to Another – Khushtar parvez Apr 09 '20 at 09:02