Fast scroll UIPageViewController prevents viewcontroller from updating - ios

I have a UIPageviewcontroller which got two controllers inside. As you swipe to the next, I use the viewController argument to set the appropriate delegate. But I experience that if you swipe too fast, the function viewControllerAfter isn't updating the viewController correctly. The initially swipe should update the index of the viewcontroller from 0 to 1, but doesn't do so if you swipe too fast.
import UIKit
class WizardPageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
lazy var orderedViewControllers: [UIViewController] = {
return [self.newVc(viewController: "intro"),
self.newVc(viewController: "welcome")]
}()
var pageControl = UIPageControl()
override func viewDidLoad() {
super.viewDidLoad()
self.dataSource = self
self.delegate = self
configurePageControl()
// This sets up the first view that will show up on our page control
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
let pageContentViewController = pageViewController.viewControllers![0]
self.pageControl.currentPage = orderedViewControllers.index(of: pageContentViewController)!
}
func newVc(viewController: String) -> UIViewController {
return UIStoryboard(name: "Wizard", bundle: nil).instantiateViewController(withIdentifier: viewController)
}
func configurePageControl() {
// The total number of pages that are available is based on how many available colors we have.
pageControl = UIPageControl(frame: CGRect(x: 0,y: UIScreen.main.bounds.maxY - 225,width: UIScreen.main.bounds.width,height: 50))
self.pageControl.numberOfPages = orderedViewControllers.count
self.pageControl.currentPage = 0
pageControl.isEnabled = false
//self.pageControl.tintColor = UIColor.black
self.pageControl.pageIndicatorTintColor = UIColor.gray
self.pageControl.currentPageIndicatorTintColor = UIColor(red:0.647, green:0.192, blue:0.216, alpha:1.00)
self.view.addSubview(pageControl)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
print(orderedViewControllers.index(of: viewController))
let previousIndex = viewControllerIndex - 1
// User is on the first view controller and swiped left to loop to
// the last view controller.
guard previousIndex >= 0 else {
return nil
// Uncommment the line below, remove the line above if you don't want the page control to loop.
// return nil
}
guard orderedViewControllers.count > previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.index(of: viewController) else {
return nil
}
print(orderedViewControllers.index(of: viewController)) // Returns 0 is I swipe too fast, otherwise 1
if let vc = orderedViewControllers[viewControllerIndex] as? WelcomeViewController {
vc.delegate = self
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = orderedViewControllers.count
// User is on the last view controller and swiped right to loop to
// the first view controller.
guard orderedViewControllersCount != nextIndex else {
return nil
// Uncommment the line below, remove the line above if you don't want the page control to loop.
// return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
}

I've encountered exactly the same problem. It essentially boils down to UIPageViewController (_UIQueuingScrollView to be exact) not updating its view hierarchy correctly.
I noticed UIPageViewController is clever enough not to add/remove the content view if the page remains the same (even if it's in wrong place in the view hierarchy it somehow manages to cope with it so it looks good to the user). That's why I added a PageTrackingView to observe the content view being added (or not) to UIPageViewController view hierarchy. By tracking if the change happens I can calculate the current index by flipping it. So currently the workaround is good enough for 2 pages only.
class PageTrackingView: UIView {
var pageIndexable: PageIndexable?
override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)
if var pageIndexable = pageIndexable {
let newIndex = (pageIndexable.internalIndex == 0) ? 1 : 0
pageIndexable.internalIndex = newIndex
}
//Reset pageIndexable so that the index flip is fired only once
pageIndexable = nil
}
}
protocol PageIndexable {
var internalIndex: Int { get set }
}
class PageViewController: UIPageViewController,
UIPageViewControllerDataSource,
UIPageViewControllerDelegate,
PageIndexable {
private var supportedViewControllers = [UIViewController]()
internal var internalIndex: Int = 0 {
didSet {
if supportedViewControllers.indices.contains(internalIndex) {
//Do something with the actual internalIndex value
} else {
assertionFailure()
}
}
init(transitionStyle style: UIPageViewControllerTransitionStyle = .scroll,
navigationOrientation: UIPageViewControllerNavigationOrientation = .horizontal,
options: [String: Any]? = nil,
viewControllers: [T]) {
supportedViewControllers = viewControllers
if !supportedViewControllers.isEmpty {
let viewController = supportedViewControllers[0]
let trackingView = PageTrackingView(frame: viewController.view.frame)
viewController.view.frame = viewController.view.bounds
trackingView.addSubview(viewController.view)
viewController.view = trackingView
}
super.init(transitionStyle: style,
navigationOrientation: navigationOrientation,
options: options)
}
func pageViewController(_ pageViewController: UIPageViewController,
didFinishAnimating finished: Bool,
previousViewControllers: [UIViewController],
transitionCompleted completed: Bool) {
if !supportedViewControllers.isEmpty, let trackingView = supportedViewControllers[0].view as? PageTrackingView {
trackingView.pageIndexable = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
datasource = self
}
}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = arrayVCs.index(of: viewController) else {
return nil
}
if index == 0 {
return nil
}
let prevIndex = abs((index - 1) % arrayVCs.count)
return arrayVCs[prevIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = arrayVCs.index(of: viewController) else {
return nil
}
if index == arrayVCs.count - 1 {
return nil
}
let nextIndex = abs((index + 1) % arrayVCs.count)
return arrayVCs[nextIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if let viewController = pageViewController.viewControllers?[0] {
guard let index = arrayVCs.index(of: viewController) else {
return
}
self.segment.selectedSegmentIndex = index
}
}
Try this. It's work for me.

Related

How to pass data between UIPageViewControllers in swift?

I have a UIPageViewController with 4 View Controllers in it. In the second VC, the user enters some data. In the last VC, there is a button that the user clicks to insert these data into DB. I want to pass the data from the second VC to the fourth one. All my search results show segues, but the VCs are not directly connected.
So I thought of using delegates, but it is not working for me.
Code:
UIPageViewController:
import UIKit
class PageViewController: UIPageViewController {
var pageControl = UIPageControl()
fileprivate lazy var pages: [UIViewController] = {
return [
self.getViewController(withIdentifier: "locationStoryBoard"),
self.getViewController(withIdentifier: "notificationStoryboard"),
self.getViewController(withIdentifier: "weightStoryboard"),
self.getViewController(withIdentifier: "bloodTypeStoryboard"),
self.getViewController(withIdentifier: "ageStoryboard"),
self.getViewController(withIdentifier: "genderStoryboard"),
self.getViewController(withIdentifier: "mobileNumberStoryboard"),
self.getViewController(withIdentifier: "preventionStoryboard")
]
}()
fileprivate func getViewController(withIdentifier identifier: String) -> UIViewController
{
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: identifier)
}
override func viewDidLoad()
{
super.viewDidLoad()
self.dataSource = self
self.delegate = self
if let firstVC = pages.first
{
setViewControllers([firstVC], direction: .forward, animated: true, completion: nil)
}
configurePageControl()
}
//Page Control (3 dots)
func configurePageControl() {
pageControl = UIPageControl(frame: CGRect(x: 0,y: UIScreen.main.bounds.maxY - 50,width: UIScreen.main.bounds.width,height: 50))
self.pageControl.numberOfPages = pages.count
self.pageControl.currentPage = 0
self.pageControl.alpha = 0.5
self.pageControl.tintColor = UIColor.black
self.pageControl.pageIndicatorTintColor = UIColor.white
self.pageControl.currentPageIndicatorTintColor = #colorLiteral(red: 0.986409843, green: 0.4042935669, blue: 0.4366002679, alpha: 1)
self.view.addSubview(pageControl)
}
}
//MARK: - UIPageViewControllerDataSource methods
extension PageViewController: UIPageViewControllerDataSource
{
func pageViewController(_ pageViewController: UIPageViewController,
viewControllerBefore viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.firstIndex(of: viewController)!
let previousIndex = currentIndex - 1
return (previousIndex == -1) ? nil : pages[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController,
viewControllerAfter viewController: UIViewController) -> UIViewController? {
let currentIndex = pages.firstIndex(of: viewController)!
let nextIndex = currentIndex + 1
return (nextIndex == pages.count) ? nil : pages[nextIndex]
}
}
//MARK: - UIPageViewControllerDelegate methods
extension PageViewController: UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
let pageContentViewController = pageViewController.viewControllers![0]
self.pageControl.currentPage = pages.firstIndex(of: pageContentViewController)!
}
}
First VC
protocol NameAndMobileDelegates {
func passName(nameToBePassed: String)
}
class MobileNumberViewController: UIViewController, UITextFieldDelegate {
var delegate: NameAndMobileDelegates?
...
func textFieldDidEndEditing(_ textField: UITextField) {
switch textField.tag {
case 1:
name = textField.text!
self.delegate?.passName(nameToBePassed: name)
print(name)
case 2:
mobileNumber = textField.text!
print(mobileNumber)
default: break
}
}
In the 4th VC:
// MARK: - Name and Number Delegate Methods
extension DiseasesViewController: NameAndMobileDelegates {
func passName(nameToBePassed: String) {
print("protocol worked")
}
Thank you in advance
Do
if let fourVC = pages[3] as? FourthVC {
fourVC.delegate = pages.first as! FirstVC
}
then
class FourthVC:UIViewController {
weak var delegate:FirstVC?

Got nil for delegate at ViewController included in UIPageViewController

I have a UIPageViewController and several ViewControllers in it. And I want to perform a function in UIPageViewController from a ViewController using delegate protocol. But get nil in the delegate. Can anybody determine what I'm doing wrong?
PageViewController.swift
import UIKit
class PageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource, Storyboarded {
weak var coordinator: MainCoordinator?
lazy var orderedViewControllers: [UIViewController] = {
return [self.newInstanceVC(viewController: "FirstLoadViewController"), self.newInstanceVC(viewController: "FirstLoad2ViewController")]
}()
var pageControl = UIPageControl()
override func viewDidLoad() {
super.viewDidLoad()
let storyboardVC = newInstanceVC(viewController: "FirstLoad2ViewController") as? FirstLoad2ViewController
storyboardVC?.delegate = self
self.dataSource = self
if let firstViewController = orderedViewControllers.first {
setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
self.delegate = self
configurePageControl()
}
func configurePageControl() {
pageControl = UIPageControl(frame: CGRect(x: 0, y: UIScreen.main.bounds.maxY - 50, width: UIScreen.main.bounds.width, height: 50))
pageControl.numberOfPages = orderedViewControllers.count
pageControl.currentPage = 0
//pageControl.tintColor = .red
pageControl.pageIndicatorTintColor = .systemGray5
pageControl.currentPageIndicatorTintColor = .systemGray
self.view.addSubview(pageControl)
}
//return view controller by string identifier
func newInstanceVC(viewController: String) -> UIViewController {
return UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: viewController)
}
//MARK: - Setup page controllers
// view controller that appears on swiping to the -> (left)
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.firstIndex(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
//return view controller from the end if it is last index from the left side (infinit scroll)
//return orderedViewControllers.last
return nil
}
guard orderedViewControllers.count >= previousIndex else {
return nil
}
return orderedViewControllers[previousIndex]
}
// view controller that appears on swiping to the <- (right)
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = orderedViewControllers.firstIndex(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
guard orderedViewControllers.count != nextIndex else {
//return view controller from the begining if it is last index from the right side (infinit scroll)
//return orderedViewControllers.first
return nil
}
guard orderedViewControllers.count > nextIndex else {
return nil
}
return orderedViewControllers[nextIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
//switch the bullet of the page
let pageContentVievController = pageViewController.viewControllers![0]
self.pageControl.currentPage = orderedViewControllers.firstIndex(of: pageContentVievController)!
}
}
extension PageViewController: PageViewDelegate {
func handleButtonTap() {
coordinator?.goToMainFromPresentation()
}
}
OneOfTheViewControllers.swift
import UIKit
protocol PageViewDelegate: class {
func handleButtonTap()
}
class FirstLoad2ViewController: UIViewController, Storyboarded {
weak var coordinator: MainCoordinator?
weak var child: ChildCoordinator?
weak var delegate: PageViewDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func toMainScreenTap(_ sender: Any) {
delegate!.handleButtonTap() //got nil for delegate here
}
}
The problem is simple, it's hiding in your viewDidLoad() method inside PageViewController
...
override func viewDidLoad() {
super.viewDidLoad()
let storyboardVC = newInstanceVC(viewController: "FirstLoad2ViewController") as? FirstLoad2ViewController
storyboardVC?.delegate = self
...
}
/// (storyboardVC) --- will be destroyed when out of viewDidLoad() scope.
So, why you getting your storyboardVC?.delegate = self nil? Because you created storyboardVC property inside viewDidLoad() method and nothing is keeping reference to it. So, at the end viewDidLoad() method it just deinitialize as well as it's PageViewDelegate delegate

How to jump to a specific page in UIPageViewController, setViewControllers not working

I am trying to jump to a specific page on a button press, I have made a ViewController which contains a ContainerView of the PageViewController, so that I can have permanent buttons on top. From there i do:
#IBAction func jump(_ sender: Any) {
PageViewController().jump()
}
and here is the Jump function in the PageViewController
func jump() {
var lastCoinNumber:Int = Manager.shared.coins.index(of: last!)!
lastNumber = lastCoinNumber
if let jumpView = viewControllerAtIndex(lastNumber, storyboard: self.storyboard!) {
self.setViewControllers([jumpView], direction: .forward, animated: false, completion: nil)
}
}
For some reason, I always get an error at the ViewControllerAtIndex part of the jump function:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
. even if I manually choose an index it does the same.
if let firstViewController = viewControllerAtIndex(0, storyboard: self.storyboard!) {
self.setViewControllers([firstViewController], direction: .forward, animated: false, completion: nil)
}
Above is how I set up the ViewControllers initialy in viewDidLoad(), and that works fine, if I am essentially doing the same thing, why doesn't it work? Here is how I index the pages and instantiate the viewcontrollers, they use the same single ViewController so a new instance of it is created for each page, otherwise I would have created an array of viewControllers.
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { if !completed { return }
DispatchQueue.main.async() {
self.dataSource = nil
self.dataSource = self
self.pageControl.numberOfPages = (Manager.shared.coins.count)
}
}
func viewControllerAtIndex(_ index: Int, storyboard: UIStoryboard) -> TemplateViewController? {
if Manager.shared.coins.count == 0 || index >= Manager.shared.coins.count {
return nil
}
let templateViewController = storyboard.instantiateViewController(withIdentifier: "templateController") as! TemplateViewController
templateViewController.dataObject = Manager.shared.coins[index]
return templateViewController
}
func indexOfViewController(_ viewController: TemplateViewController) -> Int {
self.pageControl.currentPage = Manager.shared.coins.index(of: viewController.dataObject)!
return Manager.shared.coins.index(of: viewController.dataObject)!
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! TemplateViewController)
if (index == 0) {
return nil
}
index -= 1
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! TemplateViewController)
if index == -1 {
return nil
}
index += 1
if index == Manager.shared.coins.count {
return nil
}
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
I hope this is enough information, thank you.
Edit:
Coin is a string array, I declare this:
var last = Manager.shared.coins.last
var lastNumber: Int = 0
and then the jump() takes care of the rest. So I am getting the last object, finding its index, and then doing setViewControllers with the index of the last number, which should correspond with viewControllerAtIndex, because that uses the same array to index and load the pages.
for some reason, when I try to do:
var lastCoinNumber:Int = Manager.shared.coins.index(of: last)
It forces me to use ! because:
"Value of optional type 'Array.Index?' (aka 'Optional') not unwrapped; did you mean to use '!' or '?'?"
I'm not sure what I'm doing wrong in that regard, here is how coins is declared:
var coins = [""]
Edit: Code in full, PageViewController:
import UIKit
var pageView = PageViewController()
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
//page Control dots
var pageControl = UIPageControl()
var last = Manager.shared.coins.last!
var lastNumber: Int = 0
var viewControllerArray = [UIViewController()]
func configurePageControl() {
// The total number of pages that are available is based on ManagerCoins names array
pageControl = UIPageControl(frame: CGRect(x: 0,y:
UIScreen.main.bounds.maxY - 50,width: UIScreen.main.bounds.width,height:
50))
self.pageControl.numberOfPages = (Manager.shared.coins.count)
self.pageControl.currentPage = 0
self.pageControl.tintColor = UIColor.black
self.pageControl.pageIndicatorTintColor = UIColor.gray
self.pageControl.currentPageIndicatorTintColor = UIColor.white
self.view.addSubview(pageControl)
}
// MARK: Delegate functions
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
}
func viewControllerAtIndex(_ index: Int, storyboard: UIStoryboard) -> TemplateViewController? {
if Manager.shared.coins.count == 0 || index >= Manager.shared.coins.count {
return nil
}
let templateViewController = storyboard.instantiateViewController(withIdentifier: "templateController") as! TemplateViewController
templateViewController.dataObject = Manager.shared.coins[index]
return templateViewController
}
func indexOfViewController(_ viewController: TemplateViewController) -> Int {
self.pageControl.currentPage = Manager.shared.coins.index(of: viewController.dataObject)!
return Manager.shared.coins.index(of: viewController.dataObject)!
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! TemplateViewController)
if (index == 0) {
return nil
}
index -= 1
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = self.indexOfViewController(viewController as! TemplateViewController)
if index == -1 {
return nil
}
index += 1
if index == Manager.shared.coins.count {
return nil
}
return self.viewControllerAtIndex(index, storyboard: viewController.storyboard!)
}
func jump() {
var lastCoinNumber:Int = Manager.shared.coins.index(of: last!)!
lastNumber = lastCoinNumber
if let jumpView = viewControllerAtIndex(lastCoinNumber, storyboard: self.storyboard!){
self.setViewControllers([jumpView], direction: .forward, animated: false, completion: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
configurePageControl()
self.dataSource = self
if let firstViewController = viewControllerAtIndex(0, storyboard: self.storyboard!){
self.setViewControllers([firstViewController], direction: .forward, animated: false, completion: nil)
}
}
}
Edit:
After much research, This seems to be the correct way to jump a page, by calling setViewControllers, the first comment on this answer seems to have the same problem, it works fine for setting the view on viewDidLoad() but trying to programmatically navigate to a page based on the index falls to an error. https://stackoverflow.com/a/24335008/10058239
I think it's crashing because of for force unwrapping:
var lastCoinNumber:Int = Manager.shared.coins.index(of: last!)!
if coins is a collection you can use .last to get the last element for example:
let numbers = [10, 20, 30, 40, 50]
if let lastNumber = numbers.last {
print(lastNumber)
}
// Prints "50"

PageControll error number of page swift 4

I created a PageController with 3 pages .. I allocated and declared everything, I implemented the two functions that allow scrolling views, in the same I entered the commands to enable the controller with the right page reported. the result is that if I flow my pages is all ok, while under the page counter works as he wants.. and I can not understand what is wrong
problemproblem2problem3
import UIKit
import AVFoundation
protocol IntroNavigationDelegate: class {
func showNextViewController()
func showPreviousViewController()
var isPagingEnabled: Bool { get set }
}
final class IntroViewController: UIPageViewController, UIPageViewControllerDelegate, IntroNavigationDelegate, UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let viewControllerIndex = self.displayedControllers.index(of: viewController) {
if viewControllerIndex == 0 {
} else if viewControllerIndex == 1 {
return self.displayedControllers[viewControllerIndex - 1]
}
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if let viewControllerIndex = self.displayedControllers.index(of: viewController) {
if viewControllerIndex < self.displayedControllers.count - 1 {
self.pageControl.currentPage = 1
return self.displayedControllers[viewControllerIndex + 1]
} else {
self.pageControl.currentPage = 0
}
}
return nil
}
var introRouter: IntroRouter?
var pageControl = UIPageControl()
var displayedControllers: [UIViewController] = []
private var scrollView: UIScrollView? {
for view in view.subviews {
if let subView = view as? UIScrollView {
return subView
}
}
return nil
}
var isPagingEnabled: Bool {
get {
return scrollView?.isScrollEnabled ?? true
}
set {
scrollView?.isScrollEnabled = newValue
}
}
override func viewDidLoad() {
super.viewDidLoad()
configurePageControl()
setupUI()
self.pageControl.updateCurrentPageDisplay()
arrangeSubviews()
self.delegate = self
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.isNavigationBarHidden = false
}
func setDiplayedControllers(_ controllers: [UIViewController], visualizedController: UIViewController) {
displayedControllers = controllers
setViewControllers([visualizedController], direction: .forward, animated: false, completion: nil)
}
func showNextViewController() {
if let current = viewControllers?.first, let next = IntroViewController(self, viewControllerAfter: current) {
setViewControllers([next], direction: .forward, animated: true, completion: nil)
}
}
func showPreviousViewController() {
if let current = viewControllers?.first, let previous = IntroViewController(self, viewControllerBefore: current) {
setViewControllers([previous], direction: .reverse, animated: true, completion: nil)
}
}
func configurePageControl(){
self.pageControl.frame = CGRect()
self.pageControl.numberOfPages = self.displayedControllers.count
self.pageControl.translatesAutoresizingMaskIntoConstraints = false
self.pageControl.updateCurrentPageDisplay()
self.view.addSubview(self.pageControl)
pageControl.activate([
pageControl.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -20),
pageControl.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.1),
pageControl.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 1),
])
}
override init(transitionStyle style: UIPageViewControllerTransitionStyle, navigationOrientation: UIPageViewControllerNavigationOrientation, options: [String : Any]? = nil) {
super.init(transitionStyle: style, navigationOrientation: navigationOrientation, options: options)
setup()
}
func setRouter(introRouter: IntroRouter) {
self.introRouter = introRouter
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
view.backgroundColor = .white
dataSource = self
}
}
private extension IntroViewController {
func IntroViewController(_ IntroViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if let viewControllerIndex = self.displayedControllers.index(of: viewController) {
if viewControllerIndex == 0 {
// wrap to last page in array
return self.displayedControllers.last
} else {
// go to previous page in array
return self.displayedControllers[viewControllerIndex + 1]
}
}
return nil
}
func IntroViewController(_ IntroViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
self.pageControl.updateCurrentPageDisplay()
if let viewControllerIndex = self.displayedControllers.index(of: viewController) {
if viewControllerIndex < self.displayedControllers.count - 1 {
// go to next page in array
self.pageControl.currentPage = viewControllerIndex
return self.displayedControllers[viewControllerIndex + 1]
} else {
// wrap to first page in array
self.pageControl.currentPage = viewControllerIndex
return self.displayedControllers.first
}
}
return nil
}
func IntroViewController(_ IntroViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
// set the pageControl.currentPage to the index of the current viewController in pages
if let viewControllers = IntroViewController.viewControllers {
if let viewControllerIndex = self.displayedControllers.index(of: viewControllers[0]) {
self.pageControl.currentPage = viewControllerIndex
self.pageControl.updateCurrentPageDisplay()
}
}
}
func setupUI() {
configurePageControl()
pageControl.do {
$0.numberOfPages = 3
// $0.currentPage = 0
$0.pageIndicatorTintColor = .lightGray
$0.currentPageIndicatorTintColor = Theme.Colors.white
$0.currentPage = self.displayedControllers.count
}
}
func arrangeSubviews() {
view.addSubview(pageControl)
}
}
Just try the below code. It would be helpfull.
class PageViewModel: NSObject {
private var timer: Timer?
private var interval: Double?
var enableAutoScroll: Bool = false {
didSet {
self.toggleTimer()
}
}
var viewControllers: [UIViewController]
var currentIndex: Int = 0
var direction: UIPageViewControllerNavigationDirection = .forward
init(viewControllers: [UIViewController]) {
self.viewControllers = viewControllers
super.init()
}
override convenience init() {
self.init(viewControllers: [])
}
private func stopTimer() {
if self.timer != nil {
timer?.invalidate()
timer = nil
return
}
}
private func startTimer() {
if self.timer != nil {
return
}
if #available(iOS 10.0, *) {
if let time = interval {
timer = Timer.scheduledTimer(withTimeInterval: time, repeats: true, block: { (timer: Timer) in
self.timerFire()
})
}
} else {
// Fallback on earlier versions
if let time = interval {
timer = Timer.scheduledTimer(timeInterval: time, target: self, selector: #selector(PageViewModel.timerFire), userInfo: nil, repeats: true)
}
}
}
#objc private func timerFire() {
if direction == .forward {
var index = currentIndex
if index < self.viewControllers.count - 1 {
index += 1
} else {
index = 0
}
currentIndex = index
scrollToNextPage?(currentIndex)
pageNumberChangedObserver?(currentIndex)
} else {
var index = currentIndex
if index <= 0 {
index = (self.viewControllers.count - 1)
} else {
index -= 1
}
currentIndex = index
scrollToNextPage?(index)
pageNumberChangedObserver?(currentIndex)
}
}
// MARK: - Timer
func toggleTimer() {
if enableAutoScroll == true {
startTimer()
} else {
stopTimer()
}
}
fileprivate var scrollToNextPage: ((_ index: Int) -> Void)?
func scrollToNextPage(callBack: #escaping (_ index: Int) -> Void) {
scrollToNextPage = callBack
}
// MARK: - PageNumberChangedObserver
fileprivate var pageNumberChangedObserver: ((_ index: Int) -> Void)?
func pageNumberChanged(callBack: #escaping (_ index: Int) -> Void) {
pageNumberChangedObserver = callBack
}
}
// MARK: - UIPageViewControllerDataSource
extension PageViewModel: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = viewControllers.index(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
// User is on the first view controller and swiped left to loop to
// the last view controller.
guard previousIndex >= 0 else {
//return orderedViewControllers.last
// Uncommment the line below, remove the line above if you don't want the page control to loop.
return nil
}
guard viewControllers.count > previousIndex else {
return nil
}
return viewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = viewControllers.index(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let orderedViewControllersCount = viewControllers.count
// User is on the last view controller and swiped right to loop to
// the first view controller.
guard orderedViewControllersCount != nextIndex else {
//return orderedViewControllers.first
// Uncommment the line below, remove the line above if you don't want the page control to loop.
return nil
}
guard orderedViewControllersCount > nextIndex else {
return nil
}
return viewControllers[nextIndex]
}
}
// MARK: - UIPageViewControllerDelegate
extension PageViewModel: UIPageViewControllerDelegate {
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if let pageContentViewController = pageViewController.viewControllers?[0] {
if let index = viewControllers.index(of: pageContentViewController) {
currentIndex = index
pageNumberChangedObserver?(currentIndex)
}
}
}
}
Write below code in your Container View Controller: -
// set current page to the page controller
func setNextPage(index: Int, direction: UIPageViewControllerNavigationDirection) {
if let firstViewController = pageViewModel?.viewControllers[selectedCategoryCell] {
pageViewController?.setViewControllers([firstViewController],
direction: direction,
animated: true,
completion: nil)
}
}
// MARK: - SET PAGE CONTROLLER's VIEW's
private func setupPageViewController() {
let pageViewController: UIPageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
pageViewController.dataSource = pageViewModel
pageViewController.delegate = pageViewModel
if let firstViewController = pageViewModel?.viewControllers[selectedCategoryCell] {
pageViewController.setViewControllers([firstViewController],
direction: .forward,
animated: true,
completion: nil)
}
for recognizer in pageViewController.gestureRecognizers {
if recognizer is UITapGestureRecognizer {
recognizer.isEnabled = false
}
}
self.addChildViewController(pageViewController)
self.containerView.addSubview(pageViewController.view)
var pageViewRect = self.containerView.bounds
if UIDevice.current.userInterfaceIdiom == .pad {
pageViewRect = pageViewRect.insetBy(dx: 40.0, dy: 40.0)
}
pageViewController.view.frame = pageViewRect
pageViewController.didMove(toParentViewController: self)
self.pageViewController = pageViewController
}
// set the selected page and accordingly set the above collection view ( that is treatment option's collection view)
func pageNumberChanged(index: Int) {
self.selectedCategoryCell = index
}
// MARK: Init View Controller for Page Controller
func initViewController(index: Int) -> UIViewController {
guard let itemsViewController = self.storyboard?.instantiateViewController(withIdentifier: "MatchesV2ViewController") as? MatchesV2ViewController else {
fatalError("Items View Controller Not Found")
}
itemsViewController.delegate = self
itemsViewController.currentMatch = matches.user[index]
return itemsViewController
}
// MARK: Setting VC In Page View Controller
func instantiateViewControllers() {
matchesScreen.removeAll()
for i in 0..<matches.user.count {
matchesScreen.append(initViewController(index: i))
}
pageViewModel = PageViewModel(viewControllers: matchesScreen)
pageViewModel?.pageNumberChanged(callBack: { (index: Int) in
self.pageNumberChanged(index: index)
})
pageViewModel?.scrollToNextPage(callBack: { (index: Int) in
self.setNextPage(index: index, direction: .forward)
})
if matchesScreen.count > 0 {
self.setupPageViewController()
}
}

Bug when checking back/forward scrolling in UIPageViewCotroller

I am trying to load appropriate images from an array into my UIPageViewController's Views.
I have 3 views which I cycle through in order to prevent crashes.
I keep track whether I am scrolling forward or backwards but sometimes when flipping forth and back really quick, I run into a bug that flips the direction and than I get the same view repeating for couple of scrolls.
Here is the function that updates the local variable index when scrolling completes
func pageViewController(pvc: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if completed {
let thisPage = pvc.viewControllers!.last as! PageContentViewController
let currentIndex = thisPage.pageIndex
let lastIndex = lastPage.pageIndex
if(currentIndex > urlIndex){
urlIndex += 1
}else if(currentIndex < urlIndex) {
urlIndex -= 1
}else{
}
}
}
And here is the rest of my UIPageViewController code:
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
delegate = self
}
private(set) lazy var orderedViewControllers: [PageContentViewController] = {
return [self.newPageContentViewController(),
self.newPageContentViewController(),
self.newPageContentViewController()
]
}()
private func newPageContentViewController() -> PageContentViewController {
return UIStoryboard(name: "Main", bundle: nil) .
instantiateViewControllerWithIdentifier("PageContentViewController") as! PageContentViewController
}
private func getNextViewController() -> PageContentViewController{
let scrollView = orderedViewControllers.removeAtIndex(0)
orderedViewControllers.append(scrollView)
return scrollView
}
private func getPreviousViewController() -> PageContentViewController{
let scrollView = orderedViewControllers.removeLast()
orderedViewControllers.insert(scrollView, atIndex: 0)
return scrollView
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
guard urlIndex > 0 else {
return nil
}
let previousUrlIndex = urlIndex - 1
return getViewControllerAtIndex(previousUrlIndex, next: false)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
let nextUrlIndex = urlIndex + 1
guard arrayRouteName.count > nextUrlIndex else{
return nil
}
return getViewControllerAtIndex(nextUrlIndex, next: true)
}
func getViewControllerAtIndex(index: NSInteger, next: Bool) -> PageContentViewController
{
// Create a new view controller and pass suitable data.
var pageContentViewController:PageContentViewController
if(next){
pageContentViewController = getNextViewController()
}else{
pageContentViewController = getPreviousViewController()
}
pageContentViewController.localImage = arrayLocalImage[index]
pageContentViewController.pageIndex = index
pageContentViewController.view.clipsToBounds = true
return pageContentViewController
}
I have managed to fix the problem. I have removed the pageViewControllerfunction from the UIPageControllerDelegate an simply chaged my pageViewController functions in my data source to get the index from viewController like so
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let index = ((viewController as! UIPageContentViewController).pageIndex) - 1
guard index < 0 {
return nil
}
return getViewControllerAtIndex(index, pageContentViewController: getPreviousViewController() )
}
and it behaves like it should.

Resources