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