How to add PageControl inside UICollectionView Image Scrolling
Asked Answered
L

14

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.
    }
    
    
    
}
License answered 5/12, 2016 at 13:24 Comment(0)
B
68

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)
}
Ballyhoo answered 5/12, 2016 at 19:48 Comment(4)
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 }License
That's because in order to make a page for each item, you have to use sections, not rowsBallyhoo
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 answerChatterton
That's something you should clearly state in your answerPiane
C
151

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.

Confiscatory answered 12/9, 2017 at 16:18 Comment(2)
how to make it work with a right to left pageControl?Soll
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?Ravens
B
68

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)
}
Ballyhoo answered 5/12, 2016 at 19:48 Comment(4)
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 }License
That's because in order to make a page for each item, you have to use sections, not rowsBallyhoo
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 answerChatterton
That's something you should clearly state in your answerPiane
F
60

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)
}
Felisafelise answered 17/8, 2018 at 16:16 Comment(4)
Best solution! Responsive and accurateAllard
Noticeably better than using the laggy scrollViewDidEndDecelerating solution.Entomophilous
Luke's solution somehow does not work any more for me. But David's answer works and it saved my life, ty.Lonna
This was exactly what I was looking for for years.Quartern
C
30

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
    }
}
Cabalist answered 8/3, 2019 at 13:43 Comment(3)
fyi row is for table views, item is for collection views.Loxodrome
@RickvanderLinde even though it is mentioned as row instead of item it works fine.Autoroute
This works smoothly if we disabled UIPageControl's userInteraction. For those who needs to support pagecontrol's action, put this calculation in scrollViewDidEndDeceleratingBowleg
D
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
    }
}
Declinometer answered 22/12, 2017 at 3:6 Comment(3)
This should be the only accepted answer since it's the most clean and responsive way to do it.Playroom
I have to substitute section for itemQuondam
If you swipe fast enough, the current page can produce the wrong result.Quondam
L
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)
}
Lejeune answered 18/12, 2019 at 12:11 Comment(0)
D
7

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
    }
}
Duleba answered 25/9, 2018 at 13:43 Comment(0)
H
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
        }
    }
Homocentric answered 3/12, 2018 at 10:54 Comment(0)
E
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)
}
Erupt answered 8/10, 2021 at 18:18 Comment(0)
M
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)
    }
Manipulate answered 11/8, 2022 at 20:4 Comment(0)
J
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
    }
}
Jere answered 3/2, 2023 at 7:55 Comment(0)
P
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
}

}

Pubescent answered 23/3, 2023 at 12:53 Comment(0)
P
0

This one works well too:

@IBAction func didChangePageControlValue(_ sender: UIPageControl) {
    
    currentPageValue = sender.currentPage
    
    let cellSize = collectionView.visibleSize
    let contentOffset = collectionView.contentOffset

    if previousPageValue > currentPageValue {
        collectionView.scrollRectToVisible(CGRectMake(contentOffset.x - cellSize.width, contentOffset.y, cellSize.width, cellSize.height), animated: true)
    } else {
        collectionView.scrollRectToVisible(CGRectMake(contentOffset.x + cellSize.width, contentOffset.y, cellSize.width, cellSize.height), animated: true)
    }
    
    previousPageValue = currentPageValue
}

previousPageValue, currentPageValue are class variables.

Piping answered 28/5 at 13:14 Comment(0)
G
-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);
}
Gleam answered 9/4, 2020 at 4:35 Comment(2)
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.Exarate
This is another method if you can Compare to AnotherGleam

© 2022 - 2024 — McMap. All rights reserved.