Search code examples
iosswiftuipageviewcontroller

UIPageViewController viewController disappears after transition


I've embedded a UIPageViewController in a UINavigationController, which in turn is embedded in a UITabBarController. I'm simply trying to make it so that the pageViewController loops through its viewControllers that are stored in an array. However every time I try to move to the next page, the first viewController snaps back into place before disappearing. I've made the first viewController red and the second one blue and oddly enough when loading them in I'm presented with the second viewController.

This gif shows what I mean This gif shows what I mean

I've tried to set up a pageViewController in the same manner in a new project and everything worked as expected so I can't see where the problem is.

import UIKit

final internal class TabBarController: UITabBarController, ApplicationLoginDelegate {

private let newsFeedTableViewController: NewsFeedTableViewController = NewsFeedTableViewController(style: UITableViewStyle.grouped)

private let substitutionPlanTableViewController: SubstitutionPlanTableViewController = SubstitutionPlanTableViewController(style: UITableViewStyle.grouped)

private let loginTableViewController: LoginTableViewController = LoginTableViewController(style: UITableViewStyle.grouped)

private let timeTableViewController: TimeTablePageViewController = TimeTablePageViewController(transitionStyle: UIPageViewControllerTransitionStyle.scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.horizontal, options: nil)

private let moreTableViewController: MoreTableViewController = MoreTableViewController(style: UITableViewStyle.grouped)

//
// MARK: - Override point
//

/**
 Called after the controller's view is loaded into memory.

 This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView() method. You usually override this method to perform additional initialization on views that were loaded from nib files.
 */
override func viewDidLoad() {

    super.viewDidLoad()

    self.setUpTabBar()

}

/**
 Notifies the view controller that its view is about to be added to a view hierarchy.

 This method is called before the view controller's view is about to be added to a view hierarchy and before any animations are configured for showing the view. You can override this method to perform custom tasks associated with displaying the view. For example, you might use this method to change the orientation or style of the status bar to coordinate with the orientation or style of the view being presented. If you override this method, you must call super at some point in your implementation.

 For more information about the how views are added to view hierarchies by a view controller, and the sequence of messages that occur, see Supporting Accessibility.
 */
override func viewWillAppear(_ animated: Bool) {

    super.viewWillAppear(animated)

    if UIApplication.boolForKey(UserDefaultKey.openSubstitutionPlanOnStartup) == true {
        self.selectedViewController = self.viewControllers?[1]
    }

}

//
// MARK: - Functions
//

private func setUpTabBar() {

    // General
    self.tabBar.tintColor = UIColor.applicationBaseColor
    self.tabBar.unselectedItemTintColor = UIColor.lightGray
    self.tabBar.backgroundColor = UIColor.white

    // Create tab bar items
    let newsFeedTabBarItem: UITabBarItem = UITabBarItem(title: "Aktuelles", image: #imageLiteral(resourceName: "News"), tag: 0)
    let substitutionTabBarItem: UITabBarItem = UITabBarItem(title: "Vertretungen", image: #imageLiteral(resourceName: "SubstitutionPlan"), tag: 1)
    let timeTableTabBarItem: UITabBarItem = UITabBarItem(title: "Stundenplan", image: #imageLiteral(resourceName: "TimeTable"), tag: 2)
    let moreTabBarItem: UITabBarItem = UITabBarItem(title: "Entdecken", image: #imageLiteral(resourceName: "MoreMenu"), tag: 3)

    // Link items and controllers
    self.newsFeedTableViewController.tabBarItem = newsFeedTabBarItem
    self.substitutionPlanTableViewController.tabBarItem = substitutionTabBarItem
    self.loginTableViewController.tabBarItem = substitutionTabBarItem
    self.timeTableViewController.tabBarItem = timeTableTabBarItem
    self.moreTableViewController.tabBarItem = moreTabBarItem

    // Set delegates
    self.loginTableViewController.delegate = self

    // Set tab bar view controllers
    var viewControllers: [UIViewController] = []

    if UIApplication.boolForKey(UserDefaultKey.isUserLoggedIn) == true {
        viewControllers = [newsFeedTableViewController, substitutionPlanTableViewController, timeTableViewController, moreTableViewController]
    } else {
        viewControllers = [newsFeedTableViewController, timeTableViewController, moreTableViewController]
    }

    self.viewControllers = viewControllers.map({ (controller) -> UIViewController in

        controller.navigationItem.largeTitleDisplayMode = .always

        let navigationController = UINavigationController(rootViewController: controller)
        navigationController.navigationBar.prefersLargeTitles = true

        return navigationController
    })

    if UIApplication.boolForKey(UserDefaultKey.isUserLoggedIn) == false {
        self.viewControllers?.insert(self.loginTableViewController, at: 1)
    }

}




}

The UITabBarController and the UIPageViewController:

class TimeTablePageViewController: UIPageViewController, UIPageViewControllerDataSource {

private var timeTableViewControllers: [UIViewController]!

override func viewDidLoad() {

    super.viewDidLoad()

    self.dataSource = self

    self.timeTableViewControllers = Array.init(repeating: UIViewController(), count: 2)

    self.timeTableViewControllers[0].view.backgroundColor = .red
    self.timeTableViewControllers[1].view.backgroundColor = .blue

    self.setViewControllers([self.timeTableViewControllers[0]], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil)

}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {

    if let index = self.timeTableViewControllers.index(of: viewController) {
        if viewController == self.timeTableViewControllers.first {
            return self.timeTableViewControllers.last
        } else {
            return self.timeTableViewControllers[index - 1]
        }
    } else {
        return nil
    }

}

func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {

    if let index = self.timeTableViewControllers.index(of: viewController) {
        if viewController == self.timeTableViewControllers.last {
            return self.timeTableViewControllers.first
        } else {
            return self.timeTableViewControllers[index + 1]
        }
    } else {
        return nil
    }

}

}

Solution

  • The repeatedValue parameter of Array.init(repeating repeatedValue: Array.Element, count: Int) is not a closure. It's a single object that will be used to fill the array.

    The code won't call UIViewController() for each element it creates. You are creating an array that contains the same UIViewController instance two times. A view can't have two superViews, so when you scroll to the second page, the UIPageViewController adds the view of the only viewController to its view, which means that it will be removed from its view as well.

    Replace

    self.timeTableViewControllers = Array.init(repeating: UIViewController(), count: 2)
    

    with

    self.timeTableViewControllers = [UIViewController(), UIViewController()]