I was wondering how can I implement buttons which will affect the UIPageViewController’s model. For example I would like my UIPageViewController model array to load with a single object in it and when a button is tapped a new view controller will be added and will flip automatically the page or something like that (like in the Notes app). Same for deleting the current object that the user is seeing.
So far I tried by implementing some IBActions to my root view controller but no luck so far.
Here is how I have implement my ViewController class:
@implementation ViewController
@synthesize modelArray = _modelArray;
@synthesize pageVC = _pageVC;
#pragma mark - UIPageViewControllerDataSource Methods
// Returns the view controller before the given view controller. (required)
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentVC *)viewController labelContents]];
if(currentIndex==0)
return nil;
ContentVC *cVC = [[ContentVC alloc] init];
cVC.labelContents = [self.modelArray objectAtIndex:currentIndex-1];
return cVC;
}
// Returns the view controller after the given view controller. (required)
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentVC *)viewController labelContents]];
if(currentIndex==self.modelArray.count-1)
return nil;
ContentVC *cVC = [[ContentVC alloc] init];
cVC.labelContents = [self.modelArray objectAtIndex:currentIndex+1];
return cVC;
}
#pragma mark - UIPageViewControllerDelegate Methods
// Returns the spine location for the given orientation.
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
if(UIInterfaceOrientationIsPortrait(orientation)){
// Set the array with only 1 view controller
UIViewController *currentVC = [self.pageVC.viewControllers objectAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObject:currentVC];
[self.pageVC setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
// Set the doubleSided property to NO
self.pageVC.doubleSided = NO;
// Return the spine location
return UIPageViewControllerSpineLocationMin;
} else { // if landscape
NSArray *viewControllers = nil;
ContentVC *currentVC = [self.pageVC.viewControllers objectAtIndex:0];
NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentVC *)viewControllers labelContents]];
if(currentIndex==0 || currentIndex %2 == 0){
UIViewController *nextViewController = [self pageViewController:self.pageVC viewControllerAfterViewController:currentVC];
viewControllers = [NSArray arrayWithObjects:currentVC, nextViewController, nil];
} else {
UIViewController *previousVC = [self pageViewController:self.pageVC viewControllerBeforeViewController:currentVC];
viewControllers = [NSArray arrayWithObjects:previousVC, currentVC, nil];
}
// Set the view controllers as property of the UIPageViewController
[self.pageVC setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
return UIPageViewControllerSpineLocationMid;
}
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Instantiate the model array
self.modelArray = [[NSMutableArray alloc] init];
[self.modelArray addObject:@"Page One"];
NSLog(@"%@", self.modelArray);
// Instantiate the UIPageViewController
self.pageVC = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
// Assign the delegate and datasource as self
self.pageVC.delegate = self;
self.pageVC.dataSource = self;
// Set the initial view controllers
ContentVC *cVC = [[ContentVC alloc] initWithNibName:@"ContentVC" bundle:nil];
cVC.labelContents = [self.modelArray objectAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObject:cVC];
[self.pageVC setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
// Add the pageViewController as the childViewController
[self addChildViewController:self.pageVC];
// Add the view of pageViewController to the currentView
[self.view addSubview:self.pageVC.view];
// Call didMoveToParentViewController: of the childViewController, the UIPageViewController instance in our case.
[self.pageVC didMoveToParentViewController:self];
// Assign the gestureRecognizers property of the pageViewController to the view's gestureRecognizers property
self.view.gestureRecognizers = self.pageVC.gestureRecognizers;
}
Thank you very much!
The
UIPageViewControlleruses a datasource to obtain the next or previous view controller. So the solution is to add your new view controller to the datasource once the button is touched.Say your datasource is implemented as an
NSArray(see the XCode’s Page-based Application template for an example of this) you just add your new vc to that array and set the new vc by calling[UIPageViewController setViewControllers:direction:animated:completion:].Since you didn’t provide any details on how you implemented your hierarchy I can’t be more specific.
Edit:
Now that I have some code, here’s what I’d do:
First I wouldn’t save the
labelContentsbut rather the view controller itself in themodelArray. Besides other things, your current design creates a newContentVCevery time you change pages. That’s a lot of unnecessary overhead. Here’s how I would implement that:(btw, you should think of a more descriptive name than
labelContents. Right now it might be fine if there’s just one label, but what if you add more labels in the future?)The code in your comment:
doesn’t work because you didn’t set the
labelContentsof theContentVC. So you basically end up calling[self.modelObject indexOfObject:nil]or[self.modelObject indexOfObject:@""]depending on your implementation oflabelContents. Since neither of them is in the array, the call returnsNSNotFoundwhich on 64bit systems is translated toNSIntegerMaxand that is2147483647. Later you try to access yourmodelArrayat indexNSIntegerMax - 1and that raises anNSRangeException.So can fix that by either setting the
labelContentsin yournewButtonTapped:method or if you follow my suggestion to redesign your code: