swiftuicollectionviewsegment

Update Segmented Progress Bar When CollectionView Index is Changed Swift


Working Example Video

I am trying to create an onboarding view such as Instagram stories with a progress bar on top of the view.

So far I was able to animate to the second or third index in the given time. This animation also changes stack view progress bar. But when I try to scroll to the next or previous collection view index I can not show this action inside the progress bar.

I can read the current page index with page control but can not reflect this reading to progress bar.

class OnboardingViewController: UIViewController, SegmentedProgressBarDelegate {
    
    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var stackView: UIStackView!
    
    private var spb: SegmentedProgressBar!
    
    var currentPage = 0
    
    lazy var pageControl: UIPageControl = {
        let pageControl = UIPageControl()
        pageControl.numberOfPages = slides.count
        return pageControl
    }()
    
    //Burası Presenter'dan gelecek.
    var slides: [OnboardingSlide] = [
        OnboardingSlide(title: "Delicious Dishes", image: #imageLiteral(resourceName: "1")),
        OnboardingSlide(title: "World-Class Chefs", image: #imageLiteral(resourceName: "2")),
        OnboardingSlide(title: "Instant World-Wide Delivery", image: #imageLiteral(resourceName: "3"))
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        spb = SegmentedProgressBar(numberOfSegments: slides.count, duration: 3)
        spb.frame = CGRect(x: 15, y: 56, width: collectionView.frame.width - 30, height: 4)
        spb.delegate = self
        spb.topColor = UIColor.white
        spb.bottomColor = UIColor.white.withAlphaComponent(0.25)
        spb.padding = 2
        collectionView.delegate = self
        collectionView.dataSource = self
        stackView.addArrangedSubview(spb)
        spb.startAnimation()
        collectionView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tappedView)))
    }
    
    func segmentedProgressBarChangedIndex(index: Int) {
        updateView(index: index)
    }
    
    override var prefersStatusBarHidden: Bool {
        return true
    }
    
    func segmentedProgressBarFinished() {
        print("Finished!")
    }
    
    @objc private func tappedView() {
        spb.isPaused = !spb.isPaused
    }
    
    private func updateView(index: Int) {
        print("index: \(index)")
        let indexPath = IndexPath(row: index, section: 0)
        collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
    }
   
    @IBAction func loginButtonTapped(_ sender: UIButton) {
    }
    
    @IBAction func signupButtonTapped(_ sender: UIButton) {
    }
    
}

extension OnboardingViewController: UICollectionViewDelegate, UICollectionViewDataSource {
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return slides.count
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: OnboardingCollectionViewCell.identifier, for: indexPath) as! OnboardingCollectionViewCell
        cell.setup(slides[indexPath.row])
        return cell
    }
    
    
}

extension OnboardingViewController: UICollectionViewDelegateFlowLayout {
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        let width = scrollView.frame.width
        currentPage = Int(scrollView.contentOffset.x / width)
        pageControl.currentPage = currentPage
        updateView(index: currentPage)
        print("current page: \(currentPage)")
    }
}

I have used https://github.com/D-32/SegmentedProgressBar

as the segmentedProgressBar

class OnboardingCollectionViewCell: UICollectionViewCell {
    
    static let identifier = String(describing: OnboardingCollectionViewCell.self)
    
    @IBOutlet weak var slideImageView: UIImageView!
    @IBOutlet weak var slideTitleLabel: UILabel!
    
    func setup(_ slide: OnboardingSlide) {
        slideImageView.image = slide.image
        slideTitleLabel.text = slide.title
    }
}

struct OnboardingSlide {
    let title: String
    let image: UIImage
}

Solution

  • extension OnboardingViewController: UICollectionViewDelegateFlowLayout {
        
        func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
            if scrollView.panGestureRecognizer .translation(in: view).x > 0 {
                spb.rewind()
                } else {
                    spb.skip()
                }
        }
    }
    

    with this, I was able to understand if collection view was scrolled left or right. Thus calling related functions inside the spb does the trick.