I\'ve been working on a simple test app to learn the ins and outs of the UIPageViewController. I have it working but I\'m not convinced my execution is the best way. I hop
Basically, I managed to get a slightly different way which is based on the template provided by XCode 6.4 (Page-Based Application) methods and the insights from other authors (including this, @Ben from other answers):
- (void)viewDidLoad {
[super viewDidLoad];
self.viewControllersArray = @[@"FirstViewController", @"SecondViewController"];
...
}
...
- (UIViewController *)viewControllerAtIndex:(NSUInteger)index {
UIViewController *childViewController = [self.storyboard instantiateViewControllerWithIdentifier:[self.viewControllersArray objectAtIndex:index]];
//childViewController.index = index;
return childViewController;
}
- (NSUInteger)indexOfViewController:(UIViewController *)viewController {
NSString *restorationId = viewController.restorationIdentifier;
return [self.viewControllersArray indexOfObject:restorationId];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
NSUInteger index = [self indexOfViewController:(UIViewController *)viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSUInteger index = [self indexOfViewController:(UIViewController *)viewController];
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [self.viewControllersArray count]) {
return nil;
}
return [self viewControllerAtIndex:index];
}
@interface ViewController ()
{
NSMutableArray * StoryboardIds;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
StoryboardIds = [[NSMutableArray alloc]init];
[StoryboardIds addObject:@"vc1"];
[StoryboardIds addObject:@"vc2"];
UIViewController *selectedController = [self viewControllerAtIndex:0];
self.ProfilePageViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"PageControl"];
self.ProfilePageViewController.dataSource = self;
_viewcontrollers = [NSMutableArray new];
[_viewcontrollers addObject:selectedController];
[self.ProfilePageViewController setViewControllers:_viewcontrollers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
// Change the size of page view controller
self.ProfilePageViewController.view.frame = CGRectMake(0, 0, self.bodypage.frame.size.width, self.bodypage.frame.size.height);
[self addChildViewController:self.ProfilePageViewController];
[self.ProfilePageViewController.view setFrame:self.bodypage.bounds];
[self.bodypage addSubview:self.ProfilePageViewController.view];
[self.ProfilePageViewController didMoveToParentViewController:self];
}
- (UIViewController *)viewControllerAtIndex:(NSUInteger)index
{
if (([StoryboardIds count] == 0) || (index >= [StoryboardIds count])) {
return nil;
}
if (index == 0) {
vc1 *fvc = [self.storyboard instantiateViewControllerWithIdentifier:@"vc1"];
fvc.Pageindex = index;
if ([_viewcontrollers count]) {
[_viewcontrollers replaceObjectAtIndex:0 withObject:fvc];
}
return fvc;
}
else
{
vc2 *fvc = [self.storyboard instantiateViewControllerWithIdentifier:@"vc2"];
fvc.Pageindex = index;
if ([_viewcontrollers count]) {
[_viewcontrollers replaceObjectAtIndex:0 withObject:fvc];
}
return fvc;
}
}
-(NSUInteger)indexofViewController
{
UIViewController *currentView = [self.ProfilePageViewController.viewControllers objectAtIndex:0];
if ([currentView isKindOfClass:[vc2 class]]) {
return 1;
}
else{
return 0;
}
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
NSUInteger index = [self indexofViewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSUInteger index = [self indexofViewController];
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [StoryboardIds count]) {
return nil;
}
return [self viewControllerAtIndex:index];
}
Came across this question as I was looking for a solution to this same problem - thanks to Matt for the guidance that he provided and to Ben for the solution description.
I built a sample project to understand this myself and since I noticed some comments asking for sample code I've uploaded this to GitHub. My solution mimics Matt's suggested approach & Ben's stated solution by:
Additionally, the implementation problem I was trying to solve required the ability to navigate backwards/forwards from the child view controllers, so this sample project also supports that functionality by asking the root view controller to go to the previous or next page (this could also be applied to go to a specific page).
Sample code on GitHub
Side note: I was admittedly hoping that similar to a UITabBarController that I could simply wire everything up from within the Storyboard and specify the order of the view controllers but alas it doesn't seem like we're there yet (as of Xcode 6 / iOS 8.1). The code required for this solution however is pretty minimal and straightforward.
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).