iosswiftuicollectionviewuipagecontrol

How to set UIPageControl currentPage state same for both collectionView and next/prev buttons?


I have a 5 images in my horizontal collectionView and it appears like carousel, one cell overlaps the other ones. Also, I have a UIPageControl and next/previous buttons for changing carousel items.

class ViewController:
UIViewController,
UICollectionViewDelegate,
UICollectionViewDataSource {

@IBOutlet private var collectionView: UICollectionView!
@IBOutlet private var nextButton: UIButton!
@IBOutlet private var previousButton: UIButton!
@IBOutlet private var pageControl: UIPageControl!

let images: [UIImage] = [
    image1,
    image2,
    image3,
    image4,
    image5,
]

var currentPage: Int = 0 {
    didSet {
        pageControl.currentPage = currentPage
    }
}

In viewDidLoad method I'm setting the pageControl items like:

 override func viewDidLoad() { 
    collectionView.delegate = self
    collectionView.dataSource = self
    previousButton.isHidden = true
    nextButton.isEnabled = true

    pageControl.numberOfPages = images.count
    pageControl.currentPage = 0
    pageControl.clipsToBounds = false
}

When it comes to showing carousel items in collectionView using next/prev buttons it works well. But the problem is currentPage state is not updating for the scrollViewDidEndDecelerating method and after some traverse using next/prev buttons it scrolls to first cell or the last cell of the collectionView automatically and the currentPage state is confusing.

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    let pageNumber = ceil(scrollView.contentOffset.x / scrollView.frame.size.width)
    pageControl.currentPage = Int(pageNumber)
    currentPage = Int(pageNumber)
    updateButtonStates(with: currentPage)
    updateUI(with: currentPage)
}

@IBAction func didTapOnPreviousButton(_ sender: Any) {
    if currentPage > 0 {
        currentPage -= 1
        print("IndexPath(item: currentPage, section: 0)", IndexPath(item: currentPage, section: 0))
        collectionView?.isPagingEnabled = false
        collectionView.scrollToItem(
            at: IndexPath(item: currentPage,
                          section: 0),
            at: .centeredHorizontally,
            animated: true)
        collectionView?.isPagingEnabled = true
        pageControl.currentPage = currentPage
        updateButtonStates(with: currentPage)
        updateUI(with: currentPage)
    }
}

@IBAction func didTapNextButton(_ sender: Any) {
    if currentPage < images.count - 1 {
        currentPage += 1
        print("IndexPath(item: currentPage, section: 0)", IndexPath(item: currentPage, section: 0))
        collectionView?.isPagingEnabled = false
        collectionView.scrollToItem(
            at: IndexPath(item: currentPage,
                          section: 0),
            at: .centeredHorizontally,
            animated: true)
        collectionView?.isPagingEnabled = true
        pageControl.currentPage = currentPage
        updateButtonStates(with: currentPage)
        updateUI(with: currentPage)
    }
}

private func updateUI(with currentPage: Int) {
    pageControl.currentPage = currentPage
    previousButton.isHidden = currentPage == 0
    nextButton.isHidden = currentPage == images.count - 1
}

func updateButtonStates(with currentPage: Int) {
    pageControl.currentPage = currentPage
    previousButton.isEnabled = currentPage > 0
    nextButton.isEnabled = currentPage < images.count
}

What I want to do is when the user scrolls between the cells and then click on the next/prev buttons, pageControl state should be same for both 2 different actions.


Solution

  • This is how I solved the problem:

    I used scrollViewWillEndDragging method to get exact point of the scrollView and the current itemWidth of the collectionView.

    func scrollViewWillEndDragging(
        _: UIScrollView,
        withVelocity _: CGPoint,
        targetContentOffset: UnsafeMutablePointer<CGPoint>
    ) {
        let offset = targetContentOffset.pointee.x
        let widthPerItem = collectionView.bounds.width / images.count
        pageControl.currentPage = Int(floor(offset / widthPerItem))
        updateButtonStates(with: pageControl.currentPage)
        updateUI(with: pageControl.currentPage)
    }
    

    And also updated UI and next/prev button states with the IBAction updates:

     @IBAction
     func didTapOnPreviousButton(_: Any) {
            let prevIndex = max(pageControl.currentPage - 1, 0)
            let indexPath = IndexPath(item: prevIndex, section: 0)
            pageControl.currentPage = prevIndex
            collectionView?.isPagingEnabled = false
            collectionView.scrollToItem(
                at: indexPath,
                at: .centeredHorizontally,
                animated: true
            )
            updateButtonStates(with: pageControl.currentPage)
            updateUI(with: pageControl.currentPage)
        }
    
     @IBAction
     func didTapOnNextButton(_: Any) {
            let nextIndex = min(pageControl.currentPage + 1, images.count - 1)
            let indexPath = IndexPath(item: nextIndex, section: 0)
            pageControl.currentPage = nextIndex
            collectionView?.isPagingEnabled = false
            collectionView.scrollToItem(
                at: indexPath,
                at: .centeredHorizontally,
                animated: true
            )
            updateButtonStates(with: pageControl.currentPage)
            updateUI(with: pageControl.currentPage)
        }
    

    Briefly, pageControl.currentPage state should be updated in the corresponding methods like next/prev button actions and the scrollView.