I'm developing an app which has 3 main Views and in order to enable users to change the 3 Views with just swiping(like Tinder app), I created a PageViewController and it works well. This PageViewController works like below.
I also added buttons at the upper side of each 3 Views and want users to able to tap those buttons to slide to other Views just like it does when they swipe those Views(This is also like Tinder).
So for experiment, I added two buttons on the SecondViewController. The left one is for moving to the FirstViewController and the right one is for moving to the ThirdViewController. And then, at the SecondViewController file, I added "UIPageViewControllerDelegate" and tried to call 「getFirst」 method for the left button and call 「getThird」 method for the right button from the PageViewController file.
But When I tap the left button, I've got this error message on the 「getFirst」 method line in the PageViewController file(I would probably get the same error for the right button as well).
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
The codes are below.
PageViewController
import UIKit
class PageViewController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil)
self.dataSource = self as UIPageViewControllerDataSource
}
}
extension PageViewController : UIPageViewControllerDataSource {
//FirstViewController
@objc func getFirst() -> FirstViewController {
return storyboard!.instantiateViewController(withIdentifier: "FirstViewController") as! FirstViewController
}
//SecondViewController
@objc func getSecond() -> SecondViewController {
return storyboard!.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
}
//ThirdViewController
@objc func getThird() -> ThirdViewController {
return storyboard!.instantiateViewController(withIdentifier: "ThirdViewController") as! ThirdViewController
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if viewController.isKind(of: ThirdViewController.self) {
// 3 -> 2
return getSecond()
} else if viewController.isKind(of: SecondViewController.self) {
// 2 -> 1
return getFirst()
} else {
// 1 -> end of the road
return nil
}
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if viewController.isKind(of: FirstViewController.self) {
// 1 -> 2
return getSecond()
} else if viewController.isKind(of: SecondViewController.self) {
// 2 -> 3
return getThird()
} else {
// 3 -> end of the road
return nil
}
}
}
SecondViewController
import UIKit
class SecondViewController: UIViewController, UIPageViewControllerDelegate {
let pageHelper = PageViewController()
override func viewDidLoad() {
super.viewDidLoad()
pageHelper.delegate = self
}
@IBAction func toFirstTapped(_ sender: Any) {
pageHelper.getFirst()
}
}
What am I missing?? Maybe I can't make it work this way(I can't reuse the PageViewController from other files?).
Could someone help me please!
First, PageViewController
instantiates a child controller which instantiates a PageViewController
.. Is that really a good idea? Then in your toFirstTapped
, you instantiate a FirstViewController
but do nothing with it..
If you do nothing with it, how do you expect it to work?
In any case, try the below:
protocol PageNavigationDelegate : class {
weak var pageController: PageViewController? { get set }
}
class PageViewController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.setViewControllers([getFirst()], direction: .forward, animated: true, completion: nil)
self.dataSource = self as UIPageViewControllerDataSource
}
}
extension PageViewController : UIPageViewControllerDataSource {
func getFirst() -> FirstViewController {
let controller = storyboard!.instantiateViewController(withIdentifier: "FirstViewController") as! FirstViewController
controller.pageController = self
return controller
}
func getSecond() -> SecondViewController {
let controller = storyboard!.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
controller.pageController = self
return controller
}
func getThird() -> ThirdViewController {
let controller = storyboard!.instantiateViewController(withIdentifier: "ThirdViewController") as! ThirdViewController
controller.pageController = self
return controller
}
@discardableResult
func goToPreviousPage() -> Bool {
let controller = self.viewControllers!.first!
if controller.isKind(of: SecondViewController.self) {
self.setViewControllers([getFirst()], direction: .reverse, animated: true, completion: nil)
return true
}
if controller.isKind(of: ThirdViewController.self) {
self.setViewControllers([getSecond()], direction: .reverse, animated: true, completion: nil)
return true
}
return false
}
@discardableResult
func goToNextPage() -> Bool {
let controller = self.viewControllers!.first!
if controller.isKind(of: FirstViewController.self) {
self.setViewControllers([getSecond()], direction: .forward, animated: true, completion: nil)
return true
}
if controller.isKind(of: SecondViewController.self) {
self.setViewControllers([getThird()], direction: .forward, animated: true, completion: nil)
return true
}
return false
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
if viewController.isKind(of: SecondViewController.self) {
return getFirst()
}
if viewController.isKind(of: ThirdViewController.self) {
return getSecond()
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
if viewController.isKind(of: FirstViewController.self) {
return getSecond()
}
if viewController.isKind(of: SecondViewController.self) {
return getThird()
}
return nil
}
}
class PageNavigationController : UIViewController, PageNavigationDelegate {
var pageController: PageViewController?
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func goToPreviousController(_ sender: Any) {
pageController?.goToPreviousPage()
}
@IBAction func goToNextController(_ sender: Any) {
pageController?.goToNextPage()
}
}
class FirstViewController: PageNavigationController {
}
class SecondViewController: PageNavigationController {
}
class ThirdViewController: PageNavigationController {
}
First we created a protocol. We made sure that all our PageController's sub-controllers will implement it. This way, they can navigate to previous and next pages..
Next, we create our extension which has the goToPreviousPage
and goToNextPage
function.. When we instantiate each controller page, we assign self
to its protocol implementation variable. That way, the controller can access the PageViewController
's methods..
Finally, in our First
, Second
, and Third
controllers, we just tell it to go forward or backward on button press.
Personally, I'd prefer to keep track of controllers in an array and just return those in my delegates.