Search code examples
uikituipageviewcontroller

UIPageViewController: how to prevent the content on the front of the page partially showing through to the back when turning pages


I have a UIPageViewController using transitionStyle .pageCurl. I noticed that when turning the page, the front of the turning page shows trough on the back of this turning page. This is the case even when there is a solid background and even if isOpaque is true for the viewcontroller's view of the turning page.

Apple's documentation for isDoubleSided states:

If the back of pages has no content (the value is false), then the content on the front of the page will partially show through to the back when turning pages.

Is there any way to change this behavior, so that the back of the page just shows the solid background (and not anything else that might be on top of it)?


Solution

  • First thought ... use a Double-Sided page view controller, and insert a "blank" page after every "real" page.

    Quick example...

    I'll use a "base" view controller to hold two Child page view controllers - one single-sided, the other double-sided. We'll create the pages arrays in that "base" controller, inserting the blank (dark-gray) pages into the array for the second one:

    Base controller - will hold two instances of a page view controller as children:

    class BaseViewController: UIViewController {
        
        var singleSidedPVC: SamplePageViewController!
        var doubleSidedPVC: SamplePageViewController!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // views to hold the page view controllers
            let ssContainer = UIView()
            ssContainer.backgroundColor = .gray
            
            let dsContainer = UIView()
            dsContainer.backgroundColor = .gray
            
            [ssContainer, dsContainer].forEach { v in
                v.translatesAutoresizingMaskIntoConstraints = false
                view.addSubview(v)
            }
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                
                ssContainer.topAnchor.constraint(equalTo: g.topAnchor, constant: 60.0),
                ssContainer.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                ssContainer.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                ssContainer.heightAnchor.constraint(equalTo: ssContainer.widthAnchor, multiplier: 0.75),
                
                dsContainer.topAnchor.constraint(equalTo: ssContainer.bottomAnchor, constant: 60.0),
                dsContainer.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                dsContainer.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                dsContainer.heightAnchor.constraint(equalTo: dsContainer.widthAnchor, multiplier: 0.75),
                
            ])
    
            // single-sided - back of page shows through
            singleSidedPVC = SamplePageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal)
            singleSidedPVC.isDoubleSided = false
            addChild(singleSidedPVC)
            singleSidedPVC.view.translatesAutoresizingMaskIntoConstraints = false
            ssContainer.addSubview(singleSidedPVC.view)
            
            NSLayoutConstraint.activate([
                
                singleSidedPVC.view.topAnchor.constraint(equalTo: ssContainer.topAnchor),
                singleSidedPVC.view.leadingAnchor.constraint(equalTo: ssContainer.leadingAnchor),
                singleSidedPVC.view.trailingAnchor.constraint(equalTo: ssContainer.trailingAnchor),
                singleSidedPVC.view.bottomAnchor.constraint(equalTo: ssContainer.bottomAnchor),
                
            ])
            
            singleSidedPVC.didMove(toParent: self)
    
            // double-sided - we'll insert a blank page between every page
            doubleSidedPVC = SamplePageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal)
            doubleSidedPVC.isDoubleSided = true
            addChild(doubleSidedPVC)
            doubleSidedPVC.view.translatesAutoresizingMaskIntoConstraints = false
            dsContainer.addSubview(doubleSidedPVC.view)
            
            NSLayoutConstraint.activate([
                
                doubleSidedPVC.view.topAnchor.constraint(equalTo: dsContainer.topAnchor),
                doubleSidedPVC.view.leadingAnchor.constraint(equalTo: dsContainer.leadingAnchor),
                doubleSidedPVC.view.trailingAnchor.constraint(equalTo: dsContainer.trailingAnchor),
                doubleSidedPVC.view.bottomAnchor.constraint(equalTo: dsContainer.bottomAnchor),
                
            ])
            
            doubleSidedPVC.didMove(toParent: self)
            
    
            // let's add pages to each controller
            let colors: [UIColor] = [
                .systemRed,
                .systemGreen,
                .systemBlue,
                .red,
                .green,
                .blue,
            ]
    
            var pvcControllers: [UIViewController]!
            
            pvcControllers = []
            for i in 0..<colors.count {
                let vc = MyExamplePageViewVC()
                vc.theLabel.text = "Page: \(i)"
                vc.view.backgroundColor = colors[i]
                pvcControllers.append(vc)
            }
            
            singleSidedPVC.pageViewControllers = pvcControllers
            
            pvcControllers = []
            for i in 0..<colors.count {
                let vc = MyExamplePageViewVC()
                vc.theLabel.text = "Page: \(i)"
                vc.view.backgroundColor = colors[i]
                pvcControllers.append(vc)
                
                // for double-sided controller, we're inserting
                //  a "blank" page VC after every "real" page VC
                //  so *that* is what shows on the back of the curl
                let blankVC = UIViewController()
                blankVC.view.backgroundColor = .darkGray
                pvcControllers.append(blankVC)
            }
            
            doubleSidedPVC.pageViewControllers = pvcControllers
    
        }
        
    }
    

    SamplePageViewController - pretty standard:

    class SamplePageViewController: UIPageViewController {
        
        // this will be set by the parent controller
        var pageViewControllers: [UIViewController] = [] {
            didSet {
                setViewControllers([pageViewControllers[0]], direction: .forward, animated: false, completion: nil)
            }
        }
        
        override init(transitionStyle style: UIPageViewController.TransitionStyle, navigationOrientation: UIPageViewController.NavigationOrientation, options: [UIPageViewController.OptionsKey : Any]? = nil) {
            super.init(transitionStyle: style, navigationOrientation: navigationOrientation, options: options)
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            dataSource = self
        }
        
    }
    
    extension SamplePageViewController: UIPageViewControllerDataSource {
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
            
            guard let viewControllerIndex = pageViewControllers.firstIndex(of: viewController) else { return nil }
            
            let previousIndex = viewControllerIndex - 1
            guard previousIndex >= 0 else { return nil }
            
            return pageViewControllers[previousIndex]
        }
        
        func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
            
            guard let viewControllerIndex = pageViewControllers.firstIndex(of: viewController) else { return nil }
            
            let nextIndex = viewControllerIndex + 1
            guard nextIndex < pageViewControllers.count else { return nil }
            
            return pageViewControllers[nextIndex]
        }
        
    }
    

    MyExamplePageViewVC - again, pretty standard... each page will have a label that fills most of the frame:

    class MyExamplePageViewVC: UIViewController {
        
        let theLabel: UILabel = {
            let v = UILabel()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .yellow
            v.textAlignment = .center
            v.font = .systemFont(ofSize: 36.0, weight: .light)
            return v
        }()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .blue
            
            view.addSubview(theLabel)
            NSLayoutConstraint.activate([
                theLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
                theLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
                theLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.9),
                theLabel.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.9),
            ])
            
        }
        
    }
    

    Looks like this when running:

    enter image description here enter image description here