Search code examples
iosswiftswift3uiscrollviewuipageviewcontroller

UIPageViewController - Detect scrolling halfway into the next view controller (almost working) to change button color?


I have a class that subclasses a UIPageViewController that which contains 4 controllers, I am trying to figure out how I can change color of the button as I scroll halfway to the second view controller

The button color should be different only on the first controller

This code here almost works but the behaviour is not correct only for the third controller which means the solution is not correct.

I Would really appreciate it if someone could help out. Thanks

    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let point = scrollView.contentOffset
        let width = scrollView.frame.width
        let percentComplete = abs(point.x - width) / width
        let page = Int(round(percentComplete))

        if percentComplete >= 0.5 {
            if page == 1, currentIndex == 1 {
                nextButton.backgroundColor = .red
            } else {
                nextButton.backgroundColor = .blue
            }
            print ("percentComplete: ", percentComplete,
                   "page: ", page,
                   "currentIndex: ",
                   currentIndex, "point: ", point.x)
        }
    }

Solution

  • Even though you say you want to use UIPageViewController, here is an example implementation using UIScrollView that you may find easier to manage:

    class PagedScrollViewController: UIViewController, UIScrollViewDelegate {
        
        let scrollView: UIScrollView = {
            let v = UIScrollView()
            v.isPagingEnabled = true
            v.bounces = false
            return v
        }()
        
        let pageControl: UIPageControl = {
            let v = UIPageControl()
            return v
        }()
        
        let stack: UIStackView = {
            let v = UIStackView()
            v.axis = .horizontal
            v.distribution = .fillEqually
            return v
        }()
        
        var pages: [UIViewController] = []
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            scrollView.translatesAutoresizingMaskIntoConstraints = false
            pageControl.translatesAutoresizingMaskIntoConstraints = false
            stack.translatesAutoresizingMaskIntoConstraints = false
            
            scrollView.addSubview(stack)
            view.addSubview(scrollView)
            view.addSubview(pageControl)
            
            let g = view.safeAreaLayoutGuide
            let svCLG = scrollView.contentLayoutGuide
            let svFLG = scrollView.frameLayoutGuide
            
            NSLayoutConstraint.activate([
                
                scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -80.0),
                
                stack.topAnchor.constraint(equalTo: svCLG.topAnchor, constant: 0.0),
                stack.leadingAnchor.constraint(equalTo: svCLG.leadingAnchor, constant: 0.0),
                stack.trailingAnchor.constraint(equalTo: svCLG.trailingAnchor, constant: 0.0),
                stack.bottomAnchor.constraint(equalTo: svCLG.bottomAnchor, constant: 0.0),
                
                stack.heightAnchor.constraint(equalTo: svFLG.heightAnchor, constant: 0.0),
                
                pageControl.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 8.0),
                pageControl.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
                pageControl.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
    
            ])
            
            // if we're loading "page" view controllers from Storyboard
            /*
            if let vc = storyboard?.instantiateViewController(withIdentifier: "psFirst") as? PSFirstViewController {
                pages.append(vc)
            }
            if let vc = storyboard?.instantiateViewController(withIdentifier: "psSecond") as? PSSecondViewController {
                pages.append(vc)
            }
            if let vc = storyboard?.instantiateViewController(withIdentifier: "psThird") as? PSThirdViewController {
                pages.append(vc)
            }
            if let vc = storyboard?.instantiateViewController(withIdentifier: "psFourth") as? PSFourthViewController {
                pages.append(vc)
            }
            pages.forEach { vc in
                self.addChild(vc)
                stack.addArrangedSubview(vc.view)
                vc.view.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: 0.0).isActive = true
                vc.didMove(toParent: self)
            }
            */
    
            // for this example,
            //  create 4 view controllers, with background colors
            let colors: [UIColor] = [
                .red, .brown, .blue, .magenta
            ]
            colors.forEach { c in
                let vc = BasePageController()
                vc.view.backgroundColor = c
                self.addChild(vc)
                stack.addArrangedSubview(vc.view)
                vc.view.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: 0.0).isActive = true
                vc.didMove(toParent: self)
                pages.append(vc)
            }
            
            pageControl.numberOfPages = pages.count
            
            scrollView.delegate = self
            
            pageControl.addTarget(self, action: #selector(self.pgControlChange(_:)), for: .valueChanged)
        }
        
        var pgControlScroll: Bool = false
        
        @objc func pgControlChange(_ sender: UIPageControl) {
            pgControlScroll = true
            let w = scrollView.frame.size.width
            guard w != 0 else { return }
            let x = scrollView.contentOffset.x
            let cp = min(Int(round(x / w)), pages.count - 1)
            let np = sender.currentPage
            var r = CGRect.zero
            if np > cp {
                r = CGRect(x: w * CGFloat(np + 1) - 1.0, y: 0, width: 1, height: 1)
            } else {
                r = CGRect(x: w * CGFloat(np), y: 0, width: 1, height: 1)
            }
            scrollView.scrollRectToVisible(r, animated: true)
        }
        func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
            pgControlScroll = false
        }
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            let w = scrollView.frame.size.width
            guard w != 0 else { return }
            let x = scrollView.contentOffset.x
            let pg = min(Int(round(x / w)), pages.count - 1)
            let v = stack.arrangedSubviews[pg]
            pageControl.backgroundColor = v.backgroundColor
            if pgControlScroll { return }
            pageControl.currentPage = pg
        }
    
    }
    
    class BasePageController: UIViewController {
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // add a label at each corner
            for (i, s) in ["top-left", "top-right", "bot-left", "bot-right"].enumerated() {
                let v = UILabel()
                v.backgroundColor = UIColor(white: 0.8, alpha: 1.0)
                v.translatesAutoresizingMaskIntoConstraints = false
                v.text = s
                view.addSubview(v)
                let g = view.safeAreaLayoutGuide
                switch i {
                case 1:
                    v.topAnchor.constraint(equalTo: g.topAnchor, constant: 4.0).isActive = true
                    v.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -4.0).isActive = true
                case 2:
                    v.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0).isActive = true
                    v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 4.0).isActive = true
                case 3:
                    v.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0).isActive = true
                    v.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -4.0).isActive = true
                default:
                    v.topAnchor.constraint(equalTo: g.topAnchor, constant: 4.0).isActive = true
                    v.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 4.0).isActive = true
                }
            }
        }
        
    }
    

    The code adds a scroll view that takes up most of the screen (with some padding so the frame of the scroll view is obvious), with a UIPageControl underneath.

    We add 4 view controllers as child view controllers, and add their views to a UIStackView in the scroll view.

    When you scroll from "page-to-page" the page control will update and change background color as you get half-way to the next / previous page.