Search code examples
iosobjective-cxcodeuipageviewcontroller

Change page in PageViewController from a page inside the controller?


I have a UIPageViewController that has 3 pages. How can I switch to a page that is within the UIPageViewController on a button tap? (Each of the pages are a separate UIViewController that I made in the storyboard).

I've tried a lot of code but I keep getting errors as I made my UIPageViewController inside my storyboard and I set it up like this:

PageViewController.m

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.dataSource = self;
    self.navigationController.navigationBarHidden = NO;

    [self setViewControllers:@[[self.storyboard instantiateViewControllerWithIdentifier:@"Main"]] 
                   direction:UIPageViewControllerNavigationDirectionForward 
                    animated:YES
                  completion:nil];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
    if ([viewController isKindOfClass:[NavView2ViewController class]])
       return nil;

    return [self.storyboard instantiateViewControllerWithIdentifier:@"two"];
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
    if ([viewController isKindOfClass:[NavViewController class]])
        return nil;

    return [self.storyboard instantiateViewControllerWithIdentifier:@"one"];
}

Solution

  • You can do this a few different ways, depending on how you want to architect the rest of the app.

    NSNotificationCenter

    Using notifications will allow you to decouple the child view controllers from the parent page view controller. It will be flexible enough to allow for additional code throughout the app to control which page is visible. However one disadvantage is that there is a level of indirection and it's not immediately apparent what has triggered the change of page.

    Setup an observer on your PageViewController, something like:

    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        self.dataSource = self;
        self.navigationController.navigationBarHidden = NO;
    
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(handleButtonTap:)
                                                     name:MyPageViewControllerButtonNotification
                                                   object:nil];
    
        [self setViewControllers:@[[self.storyboard instantiateViewControllerWithIdentifier:@"Main"]]
                       direction:UIPageViewControllerNavigationDirectionForward
                        animated:YES
                      completion:nil];
    }
    
    - (void)handleButtonTap:(NSNotification *)notification
    {
        // Determine which page to move to based
        // on the notification object or userInfo
    }
    

    And post the notification from any of the child view controllers:

    - (void)buttonTapped:(id)sender
    {
        // Customise the sender or userInfo in order to
        // pass further information with the notification.
        [[NSNotificationCenter defaultCenter] postNotificationName:MyPageViewControllerButtonNotification
                                                            object:sender
                                                          userInfo:nil];
    }
    

    You will need to create the string MyPageViewControllerButtonNotification in a file common to both your PageViewController and all view controllers you wish to post this notification from.

    Delegates

    Using a delegate pattern will provide tighter coupling between view controllers and the page view controller than notifications, which may make it more difficult to extend later. The advantage of this is it's easy to track where a page change originated.

    Define a protocol in a file common to your page view controller and child view controllers:

    @protocol PageControlDelegate <NSObject>
    
    - (void)moveToPage:(NSUInteger)pageNumber;
    
    @end
    

    Ensure your page view controller conforms to that protocol:

    @interface PageViewController : UIPageViewController <PageControlDelegate>
    

    (And implement the method in PageViewController.m)

    - (void)moveToPage:(NSUInteger)pageNumber
    {
        // switch to the appropriate page
    }
    

    When you are creating the view controllers for the pages that need the button interaction you will need to supply a (weak) reference to your PageViewController:

    @interface SomeChildViewController : UIViewController
    
    @property (nonatomic, weak) id<PageControlDelegate> pageControlDelegate;
    
    @end
    

    And an example assignment when setting up the next page:

    - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
    {
        if ([viewController isKindOfClass:[NavViewController class]])
            return nil;
    
        SomeChildViewController *childController = [self.storyboard instantiateViewControllerWithIdentifier:@"one"];
    
        childController.pageControlDelegate = self;
    
        return childController;
    }
    

    Then you can directly invoke this method from any child view controllers that have a pageControlDelegate:

    - (void)buttonTapped:(id)sender
    {
        NSUInteger pageNumber = // Decide which page to move to
        [self.pageControlDelegate moveToPage:pageNumber];
    }