The effect I'm trying to achieve: https://imgur.com/pyaZlen.
Basically, a simultaneous page curl and translation on the x axis (since the video was registered in landscape, the translation appears on the y axis).
The page curl animation is performed under the hood whenever setViewControllers
method of UIPageViewController
is called, while the translation is achieved through UIView.animate
by updating constraints.
Until iOS 15, this was perfectly fine, however since iOS 16 the animation is laggy and does not work as intended, see below.
- Regular page curl transition (no translation): https://imgur.com/4CQ3KU8 - the back side of the page is fully visible. This was also the behaviour with translation on iOS 14 and 15.
- Page curl with translation: https://imgur.com/guMolIe - the back side of the page is partially transparent.
The issue is clearly related to the x translation occurring while the curl animation is being performed, as without it everything works fine. Probably this is not the right way to perform multiple animations I assume, however since the page curl happens under the hood I'm not really sure on how to handle it.
My question is: how can I achieve a page curl and x translation animations simultaneously? And on a side note, does anyone knows why the strange behaviour I encountered happens only on newer versions of iOS?
Sample project which reproduces the issue - note, it is intended to work only in landscape:
import UIKit
final class ViewController: UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
private var pageController: UIPageViewController?
private var pageControllerViewLeadingAnchor: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
// init
pageController = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil)
pageController?.dataSource = self
pageController?.delegate = self
// adding it
addChild(pageController!)
view.addSubview(pageController!.view)
// constraints
pageController?.view.translatesAutoresizingMaskIntoConstraints = false
pageController?.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
pageController?.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
pageControllerViewLeadingAnchor = pageController?.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: -view.bounds.width/4)
pageControllerViewLeadingAnchor?.isActive = true
pageController?.view.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
// x translation animation
UIView.animate(withDuration: 0.3, animations: {
self.pageControllerViewLeadingAnchor?.constant = 0
self.view.layoutIfNeeded()
})
// pageController curl animation
let vc = UIViewController()
let vc2 = UIViewController()
vc.view.backgroundColor = .red
vc2.view.backgroundColor = .blue
self.pageController?.setViewControllers([vc, vc2], direction: .forward, animated: true, completion: nil)
}
}
func pageViewController(_ pageViewController: UIPageViewController, spineLocationFor orientation: UIInterfaceOrientation) -> UIPageViewController.SpineLocation {
// setting spine to mid and adding vcs
let vc = UIViewController()
let vc2 = UIViewController()
vc2.view.backgroundColor = .green
pageController?.setViewControllers([vc, vc2], direction: .forward, animated: true, completion: nil)
pageController?.isDoubleSided = true
return .mid
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
return UIViewController()
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
return UIViewController()
}
}