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)?

1

There are 1 best solutions below

4
DonMag On BEST ANSWER

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