How to implement UIPageViewController that utilizes multiple ViewControllers
First of all, you are absolutely right that the view controllers that constitute the "pages" of the UIPageViewController can be completely different in nature. Nothing whatever says that they have to be instances of the same view controller class.
Now let's get to the actual problem, which is that you very sensibly need a way to provide the next or previous view controller given the current view controller. That is, indeed, the main issue when using a page view controller.
It would not really be terrible to hold an array of view controllers. After all, a view controller is a lightweight object (it is the view that is the heavyweight object). However, you are also right that the way you're handling this seems clumsy.
My suggestion is: if you are going to hold the view controller instances in a storyboard, then why not just keep an array of their identifiers? Now you've got an array of three strings. How simple can you get? You will also need a single instance variable that keeps track of which identifier corresponds to the view controller that having its view used as the current page (so that you can work out which one is "next" or "previous"); this could just be an integer indexing into the array.
There is then absolutely nothing wrong with instantiating a view controller each time the user "turns the page". That is what you are supposed to do when a view controller is needed. And you can readily do this by identifier.
Finally, note that if you use the scroll style of page view controller, you won't even have to do that, because the page view controller caches the view controllers and stops calling the delegate methods (or, at least, calls them less).
How do I add multiple ViewControllers to my UIPageViewController?
Here is the fully working code if anyone is interested. Took me a while to get it right, but eventually this worked perfectly!
#import "PageViewController.h"
@interface PageViewController ()
@property (nonatomic, retain) UIViewController *first;
@property (nonatomic, retain) UIViewController *second;
@property (nonatomic, retain) UIViewController *third;
@property (nonatomic, retain) UIViewController *fourth;
@property (nonatomic, retain) UIViewController *fifth;
@end
@implementation PageViewController {
NSArray *viewControllers;
}
- (UIViewController *)first {
if (!_first) {
UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Secondary" bundle:nil];
_first = [sb instantiateViewControllerWithIdentifier:@"1"];
}
return _first;
}
- (UIViewController *)second {
if (!_second) {
UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Secondary" bundle:nil];
_second = [sb instantiateViewControllerWithIdentifier:@"2"];
}
return _second;
}
- (UIViewController *)third {
if (!_third) {
UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Secondary" bundle:nil];
_third = [sb instantiateViewControllerWithIdentifier:@"3"];
}
return _third;
}
- (UIViewController *)fourth {
if (!_fourth) {
UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Secondary" bundle:nil];
_fourth = [sb instantiateViewControllerWithIdentifier:@"4"];
}
return _fourth;
}
- (UIViewController *)fifth {
if (!_fifth) {
UIStoryboard *sb = [UIStoryboard storyboardWithName:@"Secondary" bundle:nil];
_fifth = [sb instantiateViewControllerWithIdentifier:@"5"];
}
return _fifth;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.dataSource = self;
// Aggancio il view controller iniziale.
[self setViewControllers:@[self.first]
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
UIViewController *nextViewController = nil;
if (viewController == self.first) {
nextViewController = self.second;
}
if (viewController == self.second) {
nextViewController = self.third;
}
if (viewController == self.third) {
nextViewController = self.fourth;
}
if (viewController == self.fourth) {
nextViewController = self.fifth;
}
return nextViewController;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
UIViewController *prevViewController = nil;
if (viewController == self.fifth) {
prevViewController = self.fourth;
}
if (viewController == self.fourth) {
prevViewController = self.third;
}
if (viewController == self.third) {
prevViewController = self.second;
}
if (viewController == self.second) {
prevViewController = self.first;
}
return prevViewController;
}
@end
IOS - How to create a UIPageViewController with multiple ViewController from different XIB files
You need to create like a main view controller and in this view controller you initialise all VC you need.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
// initialize controllers
self.controllers = [[NSMutableArray alloc]
initWithObjects:
[[Document1ViewController alloc] initWithNibName:@"Document1ViewController" bundle:nil],
[[Document2ViewController alloc] initWithNibName:@"Document2ViewController" bundle:nil],
[[Document3ViewController alloc] initWithNibName:@"Document3ViewController" bundle:nil],
nil];
}
return self;
}
Then you need to create a scrollview with pagination with the number of VC you want.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
for (NSUInteger i =0; i < [self.controllers count]; i++) {
[self loadScrollViewWithPage:i];
}
self.pageControl.currentPage = 0;
_page = 0;
[self.pageControl setNumberOfPages:[self.controllers count]];
UIViewController *viewController = [self.controllers objectAtIndex:self.pageControl.currentPage];
if (viewController.view.superview != nil) {
[viewController viewWillAppear:animated];
}
self.scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * [self.controllers count], scrollView.frame.size.height);
}
When you scroll you need to change the VC order in the scrollView and is possible because you have a array with the references of all VC's.
You can see one example here
Using UIPageViewController with swift and multiple view controllers
OK, I got it figured out. since I needed the page view controller to be the root and handle everything, I subclassed UIPageViewController
as iiFreeman suggested and conformed to the delegate and data source protocols. I then set up the controller in viewDidLoad
, and implemented the delegate and data source methods. I added a property store an array of storyboard identifiers as well as one to keep track of the current index of that array. I also added a helper method to return the correct view controller given an index. I didn't need anything in my other tableView
controller subclasses since my page view controller was handling everything. One thing I did realize is since my tableViewController
s were embedded in navigation controllers, my page view controller actually needed to show the navigation controllers, not the tableView
controllers. below is my implementation:
import Foundation
import UIKit
class PageViewController: UIPageViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate {
var index = 0
var identifiers: NSArray = ["FirstNavigationController", "SecondNavigationController"]
override func viewDidLoad() {
self.dataSource = self
self.delegate = self
let startingViewController = self.viewControllerAtIndex(self.index)
let viewControllers: NSArray = [startingViewController]
self.setViewControllers(viewControllers, direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
func viewControllerAtIndex(index: Int) -> UINavigationController! {
//first view controller = firstViewControllers navigation controller
if index == 0 {
return self.storyboard.instantiateViewControllerWithIdentifier("FirstNavigationController") as UINavigationController
}
//second view controller = secondViewController's navigation controller
if index == 1 {
return self.storyboard.instantiateViewControllerWithIdentifier("SecondNavigationController") as UINavigationController
}
return nil
}
func pageViewController(pageViewController: UIPageViewController!, viewControllerAfterViewController viewController: UIViewController!) -> UIViewController! {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier)
//if the index is the end of the array, return nil since we dont want a view controller after the last one
if index == identifiers.count - 1 {
return nil
}
//increment the index to get the viewController after the current index
self.index = self.index + 1
return self.viewControllerAtIndex(self.index)
}
func pageViewController(pageViewController: UIPageViewController!, viewControllerBeforeViewController viewController: UIViewController!) -> UIViewController! {
let identifier = viewController.restorationIdentifier
let index = self.identifiers.indexOfObject(identifier)
//if the index is 0, return nil since we dont want a view controller before the first one
if index == 0 {
return nil
}
//decrement the index to get the viewController before the current one
self.index = self.index - 1
return self.viewControllerAtIndex(self.index)
}
func presentationCountForPageViewController(pageViewController: UIPageViewController!) -> Int {
return self.identifiers.count
}
func presentationIndexForPageViewController(pageViewController: UIPageViewController!) -> Int {
return 0
}
}
this post helped me out a lot
How can I have multiple viewcontrollers in UIPageViewController display at once
I don't think you can do what you are describing. A page view controller shows one page, or 2 pages with a spine in the middle. It has pages "waiting in the wings" for when the user flips/slides new pages, but I don't think it supports showing pages off the edge.
That said, it wouldn't be that hard to create your own parent/child view controller arrangement that does what you want. you might even be able to base it off of a UICollectionView.
UIPageViewController and different view controllers
I use this template to achieve this.
It comes from a (possible duplicate) question with two interesting answers that you can find there.
EDIT:
TL;DR the idea is to use storyboard identifiers for your different view controllers to instantiate them when needed, using the PageViewControllerDataSource
protocol methods.
Swipe between multiple view controllers using UIPageViewController
The crash occurs while trying to access the restorationIdentifier of your view controller. You used !
to unwrap it but it's nil ; as a first solution, set the restorationIdentifier in the storyboard.
In general, use
!
to unwrap a value only if you're sure it's not nil (by adding a if statement just before).
Related Topics
This Action Could Not Be Completed. Try Again (-22421)
Difference Between Dispatch_Async and Dispatch_Sync on Serial Queue
Xcode 6 - Launch Simulator from Command Line
Calayer with Transparent Hole in It
Save Images with Phimagemanager to Custom Album
Simple and Clean Way to Convert JSON String to Object in Swift
Which iOS App Version/Build Number(S) Must Be Incremented Upon App Store Release
Uiview Hide/Show with Animation
How to Center Align the Cells of a Uicollectionview
Dismissmodalviewcontrolleranimated Deprecated
View with Continuous Scroll; Both Horizontal and Vertical
Draw Dotted (Not Dashed!) Line, with Ibdesignable in 2017
Uinavigationcontroller "Back Button" Custom Text
How to Specify Size for iPhone 6/7 Customised Edge-To-Edge Image
How to Use Autolayout to Set Constraints on My Uiscrollview
Ios 8.1 Simulator Always Uses Us Keyboard Layout Despite German Hardware Keyboard