如何在UICollectionView图像滚动中添加PageControl

49

我有一个横向图片列表的UICollectionView代码。我想在滚动图片时添加PageControl,我已经加了pagecontrol selector和IBOutlet,但是如何将它集成到UICollecitonView中呢?

我的代码如下:

 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.
    }
    
    
    
}
13个回答

151

我不是支持那个被认可的答案。有时,基于用户的交互行为,willDisplay cell会返回错误的页面控制器。

我使用的是:

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

这将在用户完成滚动后更新页面,从而使pageControlcurrentPage更准确。


按照我的期望工作。谢谢。 - Hardik Darji
1
应该是最佳答案 - Codenator81
如何使它能在从右到左的页面控件中工作? - Ben Shabat
我面临的问题是集合视图总是从第一页开始。我该如何初始化集合视图以从中间页面开始? - user2990765
正常工作。正确答案就是我所寻找的。 - shahana mh
显示剩余2条评论

67

在这里,您可以找到一个完整的类,其中包含水平集合视图中的页面控制分页功能。目前,这个类已经在我的一个应用程序中运行,并且运行正确。如果您无法使它工作,请随时向我提问,我会帮助您。

首先,在Storyboard中,您必须设置您的CollectionView:

布局: Flow
滚动方向: 水平
启用滚动
启用分页

@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)
}

兄弟,当我添加了以下函数时,不要使用PageControl:func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.myarray.count // 这里不适用于PageControl } - SwiftDeveloper
2
这是因为要为每个项目创建页面,必须使用节而不是行。 - Juan
6
有了这个解决方案,当用户在不抬起手指的情况下向下一页滑动然后再向后滑动时,页码控件会显示错误的页面。我认为你应该考虑@luke的答案。 - gasparuff
这是你应该在回答中明确说明的事情。 - Nico

58

为了实现更灵敏的UIPageControl,我更喜欢使用 scrollViewDidScroll 并计算相对于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)
}

4
最佳解决方案!反应迅速准确。 - Sarthak Mishra
1
明显比使用滞后的'scrollViewDidEndDecelerating'方案好。 - Graham Perks
1
Luke的解决方案对我来说不再起作用了。但是David的答案有效并且拯救了我的生命,谢谢。 - user3788747
1
这正是我多年来一直在寻找的。 - Eray Alparslan

29

Swift 5非常流畅

//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
    }
}

1
FYI,row 用于表视图,item 用于集合视图。 - Rick van der Linde
1
@RickvanderLinde 即使它被称为行而不是项,它也可以正常工作。 - Prashanth Thota
1
如果我们禁用UIPageControl的用户交互,则此操作可以顺利完成。对于需要支持pagecontrol操作的人,请将此计算放在scrollViewDidEndDecelerating中。 - Lal Krishna

12

等待 scrollViewDidEndDecelerating 的结果会导致 UIPageControl 更新缓慢。如果您想要更加响应快的用户界面,我建议实现代替它的 scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)

以下是 Swift 4 示例,其中每个集合视图部分都是一个页面:

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
    }
}

1
这应该是唯一被接受的答案,因为这是最干净和响应迅速的方法。 - Axy
1
我必须将“section”替换为“item”。 - pixelfreak
2
如果您滑动得足够快,当前页面可能会产生错误的结果。 - pixelfreak

10

Swift 5示例:

根据CollectionView的索引设置pageController当前页面的函数:

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
   }
}

在选择PageController时滚动CollectionView的PageControllerAction函数:

@IBAction func pageControllerAction(_ sender: UIPageControl) {
       self.collectionView.scrollToItem(at: IndexPath(row: sender.currentPage, section: 0), at: .centeredHorizontally, animated: true)
}

8

Luke的回答对我来说是一个很好的开始,但它有两个问题:它没有经常更新(已在其他答案中提到),但最重要的是它没有显示我的集合视图(水平方向)中最后一项的正确页面,并且只有当该项几乎离开屏幕时才切换页面。

对于第一个问题,正如其他人所指出的那样,使用scrollViewDidScroll提供了更好的体验。我担心频繁调用会导致性能问题,但没有注意到任何问题。

至于第二个问题,在我的用例中,找到位于屏幕中心的单元格的indexPath提供了更好的解决方案。请记住,如果您的用例可以适应超过两个单元格,则可能需要进行调整。这是因为您的最后一个单元格永远不会达到屏幕的中心。尽管如此,我认为在这种情况下使用UIPageControl也许并不理想。

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
    }
}

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
        }
    }

0

类 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

}

}

扩展 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
}

}

扩展 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
}

}


0

这对我来说似乎可行:

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        let x = targetContentOffset.pointee.x
        pageControl.currentPage = Int(x / view.frame.width)
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接