Why isn't my UILabel being set? - ios

I have 2 view controllers. viewController1 has a UILabel called nameLabel which I want to set in a different class, viewController2.
I try calling this code from viewController2.
Content1ViewController *viewController1 = [Content1ViewController new];
viewController1.nameLabel.text = #"HELLO";
NSLog(#"%#",viewController1.nameLabel);
However, the viewController1 nameLabel doesn't change when I call the code? Also the NSLog returns "null"?? Can someone tell me why this is happening and also how I can change the nameLabel from a different class? Thanks!

Controls are not initialized when you manually create the instance. This is done on a later stage of view controller life cycle.
If I'm not mistaken, first event where you will see controls initialized is viewDidLoad
Something you can do is adding a NSString property named (let's say) nameLabelText and do nameLabel.text = nameLabelText; on viewDidLoad

You need to take the same instance of viewController which was on the window. If you are pushing viewController2 into navigation controller stack then get instance of viewController1 from navigation stack and then try to set the label.
If you are using present modal then use presentingViewController instance of viewController2 in order to access instance of viewController1 like
viewController1 *controller = [viewController2 presentingViewController];
[controller.nameLabel setText:#"WhatEver"];

Where have you set the property for namelabel?
Did you use Interface Builder (XIB or Storyboard) or have you done it all in code?
If the you did it in InterfaceBuilder than
[Content1ViewController new]
is the wrong approach.
For XIB-Files you should use
initWithNibName:(NSString *)nibName
bundle:(NSBundle *)nibBundle
and if your ViewController has been defined in a storyboard you should use
instantiateViewControllerWithIdentifier:(NSString *)identifier
from UIStoryboard.
But as Claudio Redi already said the earliest point where you will be able to access the UILabel will be in viewDidLoad
If everything in code, you should put
[self setNameLabel:[[UILabel alloc] initWithFrame:CGRectMake(50.0f,50.0f,100.0f,100.0f)];
in the init-Method of your ViewController.

//
// ViewController.m
// UIPageViewControllerDemo
//
// Created by YunYi1118 on 15/5/20.
// Copyright (c) 2015年 Xiaoyi. All rights reserved.
//
#import "ViewController.h"
#import "MoreViewController.h"
#interface ViewController () <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
#property (strong, nonatomic) UIPageViewController *pageController;
#property (strong, nonatomic) NSArray *pageContent;
#end
#implementation ViewController
#synthesize pageContent = _pageContent;
#synthesize pageController = _pageController;
- (void)viewDidLoad {
[super viewDidLoad];
[self createContentPages];
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:UIPageViewControllerSpineLocationMin] forKey:UIPageViewControllerOptionSpineLocationKey];
self.pageController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:options];
_pageController.dataSource = self;
_pageController.view.frame = self.view.bounds;
MoreViewController *initialViewController = [self viewControllerAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObject:initialViewController];
[self.pageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
[self addChildViewController:_pageController];
[self.view addSubview:_pageController.view];
}
- (MoreViewController *)viewControllerAtIndex:(NSUInteger)index
{
if (self.pageContent.count == 0 || index >= self.pageContent.count) {
return nil;
}
MoreViewController *dataViewController = [[MoreViewController alloc] init];
dataViewController.dataObject = [self.pageContent objectAtIndex:index];
return dataViewController;
}
- (void)createContentPages
{
NSMutableArray *pageStrings = [NSMutableArray array];
for (int i = 1; i < 11; i++) {
NSString *contentString = [[NSString alloc] initWithFormat:#"Chapter%d, This is the page %d of content displayed using UIPageViewController ",i,i];
[pageStrings addObject:contentString];
}
self.pageContent = [NSArray arrayWithArray:pageStrings];
}
#pragma mark - delegate
- (NSUInteger)indexOfViewController:(MoreViewController *)viewController
{
return [self.pageContent indexOfObject:viewController.dataObject];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(MoreViewController *)viewController];
if (index == 0 || index == NSNotFound) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(MoreViewController *)viewController];
if (index == NSNotFound) {
return nil;
}
index++;
if (index == self.pageContent.count) {
return nil;
}
return [self viewControllerAtIndex:index];
}
#end

Related

Add multiple custom UIViewControllers to UIPageViewController

I'm trying to add UIPageViewController to my RootViewController in code. I removed storyboard file according to
Xcode 5 without Storyboard and ARC
and adding “regular” ViewController works perfectly. I adoped methods according to UIPageViewControllerDataSource
Also I tried to simplify logic (instead of implementing ModelController from starting PageView project I add showVCWithIndex: method to pick 1 of 3 VC from vcArray and write it to Mutable Array with 1 VC. Hope you can tell me if I was right or wrong about that.
.m
#import "RootPageViewController.h"
#import "ViewController0.h"
#import "ViewController1.h"
#import "ViewController2.h"
#interface RootPageViewController ()
#property (strong, nonatomic) ViewController0 *vController0;
#property (strong, nonatomic) ViewController1 *vController1;
#property (strong, nonatomic) ViewController2 *vController2;
#property (strong, nonatomic) UIPageViewController *pageViewController;
#property (strong, nonatomic) NSArray *vcArray;
#property (strong, nonatomic) NSMutableArray *viewController;
#end
#implementation RootPageViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.dataSource = self;
_vController0 = [[ViewController0 alloc] init];
_vController1 = [[ViewController1 alloc] init];
_vController2 = [[ViewController2 alloc] init];
NSUInteger index = 0;
NSMutableArray *viewController = [NSMutableArray arrayWithObjects:[self showVCWithIndex:index], nil];
[self.pageViewController setViewControllers:viewController
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
CGRect pageViewRect = self.view.bounds;
self.pageViewController.view.frame = pageViewRect;
[self.pageViewController didMoveToParentViewController:self];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [self.vcArray indexOfObject:viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self showVCWithIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [self.vcArray indexOfObject:viewController];
if (index == 2 || index == NSNotFound) {
return nil;
}
index++;
return [self showVCWithIndex:index];
}
- (UIViewController *)showVCWithIndex: (NSUInteger)index
{
self.vcArray = [NSArray arrayWithObjects: _vController0, _vController1, _vController2, nil];
UIViewController *currentVC = [self.vcArray objectAtIndex:index];
return currentVC;
}
#end
EDIT
got the solution (this code works, but I'm not sure if I am 100% right)
When you call...
[self.pageViewController setViewControllers:[vcArray objectAtIndex:0]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
... you are setting a nil view controller to the pagination view controller.
[vcArray objectAtIndex:0] points to _vController0 which is, in that moment, nil (you haven't instantiated it yet).
Before set you view controllers into the pagination, you must get it from the storyboard using [self.storyboard instatiateViewControllerWithIdentifier:#"YourViewControllerIdentifier"] or simply calling a alloc init, if you are using Xibs.
When you set the view controllers in your UIPageViewController it has to be an array with only one view controller. The method -setViewControllers:direction:animated:completion: is so misleading. I had the same problem as you when I created a page view controller for the first time.
To allow your page view controller to display other view controllers you return them in the UIPageViewControllerDataSource methods -pageViewController:viewControllerBeforeViewController: and -pageViewController:viewControllerAfterViewController:.

UIPageViewController with array of programatically generated views

So i need to create a UIPageViewController that shows a few views (1-25). Basically i have a quiz app, and for every wrong question i save the question number (1-25) in a NSMutableArray. After the quiz is done i want to show the user which quiz answer was wrong. I have the methods that sets the view of the wrong answers (same methods i use to set the views in the quiz).
This keeps giving me 'Thread1:' errors.
PVCPagesViewController.h
#import <UIKit/UIKit.h>
#import "PVCContentViewController.h"
#interface PVCPagesViewController : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
#property (weak, nonatomic) IBOutlet UIPageControl *pageControl;
#end
.
PVCPagesViewController.m
#import "PVCPagesViewController.h"
#interface PVCPagesViewController () {
NSArray *pages;
}
#property (retain, nonatomic) NSArray *pages;
#property (strong, nonatomic) UIPageViewController *pageController;
#end
#implementation PVCPagesViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//I think my problem is around here?
PVCContentViewController *page1 = [[PVCContentViewController alloc] init];
[page1 example1];
PVCContentViewController *page2 = [[PVCContentViewController alloc] init];
[page1 example2];
PVCContentViewController *page3 = [[PVCContentViewController alloc] init];
[page1 example3];
PVCContentViewController *page4 = [[PVCContentViewController alloc] init];
[page1 example2];
// load the view controllers in our pages array
self.pages = [[NSArray alloc] initWithObjects:page1, page2, page3, page4, nil];
self.pageController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
[self.pageController setDelegate:self];
[self.pageController setDataSource:self];
[[self.pageController view] setFrame:[[self view] bounds]];
NSArray *viewControllers = [NSArray arrayWithObject:[self.pages objectAtIndex:0]];
[self.pageControl setCurrentPage:0];
[self.pageControl addTarget:self action:#selector(changePage:) forControlEvents:UIControlEventValueChanged];
[self.pageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
[self addChildViewController:self.pageController];
[self.view addSubview:self.pageControl];
[[self view] addSubview:[self.pageController view]];
[self.pageController didMoveToParentViewController:self];
[self.view sendSubviewToBack:[self.pageController view]];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSUInteger currentIndex = [self.pages indexOfObject:viewController]; // get the index of the current view controller on display
[self.pageControl setCurrentPage:self.pageControl.currentPage+1]; // move the pageControl indicator to the next page
// check if we are at the end and decide if we need to present the next viewcontroller
if ( currentIndex < [self.pages count]-1) {
return [self.pages objectAtIndex:currentIndex+1]; // return the next view controller
} else {
return nil; // do nothing
}
}
- (UIViewController *) pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
NSUInteger currentIndex = [self.pages indexOfObject:viewController]; // get the index of the current view controller on display
[self.pageControl setCurrentPage:self.pageControl.currentPage-1]; // move the pageControl indicator to the next page
// check if we are at the beginning and decide if we need to present the previous viewcontroller
if ( currentIndex > 0) {
return [self.pages objectAtIndex:currentIndex-1]; // return the previous viewcontroller
} else {
return nil; // do nothing
}
}
- (void)changePage:(id)sender {
UIViewController *visibleViewController = self.pageController.viewControllers[0];
NSUInteger currentIndex = [self.pages indexOfObject:visibleViewController];
NSArray *viewControllers = [NSArray arrayWithObject:[self.pages objectAtIndex:self.pageControl.currentPage]];
if (self.pageControl.currentPage > currentIndex) {
[self.pageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
} else {
[self.pageController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
}
}
#end
.
PVCContentViewController.h
#import <UIKit/UIKit.h>
#import "PVCPagesViewController.h"
#interface PVCContentViewController : UIPageViewController
#property (weak, nonatomic) IBOutlet UILabel *label1;
#property (weak, nonatomic) IBOutlet UILabel *label2;
#property (weak, nonatomic) IBOutlet UILabel *label3;
-(void)example1;
-(void)example2;
-(void)example3;
#end
And lastly:
PVCContentViewController.m
#import "PVCContentViewController.h"
#interface PVCContentViewController ()
#end
#implementation PVCContentViewController
#synthesize label3,label2,label1;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
-(void)example1{
label1.text = #"View 1";
label2.text = #"View 1";
label3.text = #"view 1";
}
-(void)example2{
label1.text = #"View 2";
label2.text = #"View 2";
label3.text = #"view 2";
}
-(void)example3{
label1.text = #"View 3";
label2.text = #"View 3";
label3.text = #"view 3";
}
#end
I have borrowed the start source code from here:
https://github.com/hackin247/UIPageViewController
My source code:
https://github.com/4FunAndProfit/UIPageViewControllerHelp
Please let me know if you need anymore info!
Have you tried setting the pageController with the self.pages array directly?
[self.pageController setViewControllers:pages direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
I don't see the point of creating yet another array(viewControllers). Maybe this is causing issues when trying to get the next page since the pageController only has a single viewController ([self.pages objectAtIndex:0]) but the "data source" says it should have more based on the self.pages.count.

Changing UIPageViewController's page programmatically doesn't update the UIPageControl

In my custom UIPageViewController class:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.model = [[BSTCMWelcomingPageViewModel alloc] init];
self.dataSource = self.model;
self.delegate = self;
self.pageControl = [UIPageControl appearance];
self.pageControl.pageIndicatorTintColor = [UIColor lightGrayColor];
self.pageControl.currentPageIndicatorTintColor = [UIColor blackColor];
}
return self;
}
Then I programmatically set the current ViewController when a button is hit:
- (void)scrollToNext
{
UIViewController *current = self.viewControllers[0];
NSInteger currentIndex = [self.model indexForViewController:current];
UIViewController *nextController = [self.model viewControllerForIndex:++currentIndex];
if (nextController) {
NSArray *viewControllers = #[nextController];
// This changes the View Controller, but PageControl doesn't update
[self setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
//Nothing happens!
[self.pageControl setCurrentPage:currentIndex];
//Error: _installAppearanceSwizzlesForSetter: Not a setter!
[self.pageControl updateCurrentPageDisplay];
}
}
If I can't do this with the UIPageControl that "belongs" to my UIPageViewController I will just try to make my own. But it would be nice if this was possible tho!
to update your UIPageControl indicator, you need to implement one data source method of UIPageViewController (the UIPageViewControllerDataSource method) :
-(NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
This method is responsible for updating the page control indicator (when you use UIPageViewController). You only need to return the currentpage value in this method. The Method gets called by default when you use/make a call for setViewControllers on your custom UIPageViewController.
So the chunk of code that you need to write is:
- (void)scrollToNext
{
UIViewController *current = self.viewControllers[0];
NSInteger currentIndex = [self.model indexForViewController:current];
UIViewController *nextController = [self.model viewControllerForIndex:++currentIndex];
if (nextController) {
NSArray *viewControllers = #[nextController];
// This changes the View Controller and calls the presentationIndexForPageViewController datasource method
[self setViewControllers:viewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
return currentIndex;
}
Hope this solves your problem. :)
As mentioned in accepted answer, you need to implement
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
But for me it was enough to use it like this:
Objective-C:
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController {
// The selected item reflected in the page indicator.
return [self.controllers indexOfObject:[pageViewController.viewControllers firstObject]];
}
Swift 3+:
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let index = viewControllers?.index(of: (pageViewController.viewControllers?.first)!) else { return 0 }
return index
}
Without the need to remember the current index. Where self.controllers is an NSArray of UIViewControllers displayed in given UIPageViewController. I'm not sure how exactly your BSTCMWelcomingPageViewModel works, but it should be easy to adjust.
Xamarin/C# Solution
I had this problem in Xamarin, here is my version of #micromanc3r's solution:
public class PageViewControllerDataSource : UIPageViewControllerDataSource
{
UIViewController[] pages;
public PageViewControllerDataSource(UIViewController[] pages)
{
this.pages = pages;
}
...
public override nint GetPresentationIndex(UIPageViewController pageViewController)
{
return Array.IndexOf(pages, pageViewController.ViewControllers[0]);
}
}
SWIFT 4.2
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let currentController = pageViewController.viewControllers?.first else {
return 0 }
guard let index = viewControllerList.index(of: currentController) else { return 0 }
return index
}
A page indicator will be visible if both methods are implemented, transition style is UIPageViewControllerTransitionStyleScroll and navigation orientation is UIPageViewControllerNavigationOrientationHorizontal. Both methods are called in response to a setViewControllers:... call, but the presentation index is updated automatically in the case of gesture-driven navigation.
(NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController NS_AVAILABLE_IOS(6_0);: The number of items reflected in the page indicator.
(NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController NS_AVAILABLE_IOS(6_0);: The selected item reflected in the page indicator.
The answer above, given by aansul, works.
Note : Don't forget to set the pageViewController's Transition style to Scroll instead of Page Curl. Otherwise it won't work.
// ViewController.h File
#import <UIKit/UIKit.h>
#import "PageContentViewController.h"
#interface ViewScreen : UIViewController<UIPageViewControllerDataSource,UIPageViewControllerDelegate>
- (IBAction)Startwalkthrough:(id)sender;
#property(strong, nonatomic)UIPageViewController *pageViewController;
#property(strong, nonatomic)NSArray * pageTitles;
#property(strong,nonatomic)NSArray * pageImages;
#end
// ViewController.m File
#import "ViewScreen.h"
#interface ViewScreen ()
#end
#implementation ViewScreen
#synthesize pageTitles,pageImages;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
pageTitles =#[#"dianna",#"images",#"image",#"i1",#"hulk1",#"assasins"];
pageImages =#[#"dianna",#"images",#"image",#"i1",#"hulk1",#"assasins"];
self.pageViewController =[self.storyboard instantiateViewControllerWithIdentifier:#"PageViewController"];
self.pageViewController.dataSource=self;
PageContentViewController *startingViewController = [self viewControllerAtIndex:0];
NSArray * viewController =#[startingViewController];
[self.pageViewController setViewControllers:viewController direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
[self addChildViewController:_pageViewController];
[self.view addSubview:_pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
}
-(IBAction)Startwalkthrough:(id)sender
{
PageContentViewController * startingViewController =[self viewControllerAtIndex:0];
NSArray * viewControllers =#[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionReverse animated:NO completion:nil];
}
- (PageContentViewController *)viewControllerAtIndex:(NSUInteger)index
{
if (([self.pageTitles count] == 0) || (index >= [self.pageTitles count])) {
return nil;
}
// Create a new view controller and pass suitable data.
PageContentViewController *pageContentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"PageContentViewController"];
pageContentViewController.imageFile = self.pageImages[index];
pageContentViewController.titletext = self.pageTitles[index];
pageContentViewController.pageIndex = index;
return pageContentViewController;
}
#pragma mark - Page View Controller Data Source
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index =((PageContentViewController *) viewController).pageIndex;
if (index == NSNotFound) {
return nil;
}
index--;
if (index ==[self.pageTitles count]) {
return nil;
}
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = ((PageContentViewController*) viewController).pageIndex;
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [self.pageTitles count]) {
return nil;
}
return [self viewControllerAtIndex:index];
}
-(NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController{
return [self.pageTitles count];
}
-(NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
return 0;
}
#end
// Second File
PageViewController.h File
#import <UIKit/UIKit.h>
#interface PageContentViewController : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *txtLabel;
#property (strong, nonatomic) IBOutlet UIImageView *backgroundImageView;
#property NSUInteger pageIndex;
#property NSString *titletext;
#property NSString * imageFile;
#end
// SecondFile.M File
#import "PageContentViewController.h"
#interface PageContentViewController ()
#end
#implementation PageContentViewController
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self =[super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
//
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.backgroundImageView.image =[UIImage imageNamed:self.imageFile];
self.txtLabel.text =self.titletext;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#end
Make sure to set the currentIndex BEFORE you call setViewControllers

UIPageViewController doesn't change page

I have a view controller implementing UIPageViewControllerDataSource delegate, and it contains a UIPageViewController.
My issue is that, after 3 hours of tutorials and reading, I still don't understand why the UIPageController reacts to swipe by moving it's content, but it doesn't change the page if the scroll is enough. It's always stuck on the first page.
So this is my .h file
#import <UIKit/UIKit.h>
#interface BreathePageViewController : UIViewController <UIPageViewControllerDataSource>
#property (strong, nonatomic) UIPageViewController *pageController;
#end
And this is my .m file
#import "BreathePageViewController.h"
#import "FirstViewController.h"
#import "SecondViewController.h.h"
#interface BreathePageViewController () {
NSArray *pageViewControllerScreens;
FirstViewController *firstViewController;
SecondViewController *secondViewController;
int pageIndex;
}
#end
#implementation BreathePageViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
pageIndex = 0;
self.pageController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageController.dataSource = self;
[[self.pageController view] setFrame:[[self view] bounds]];
[self.pageController setViewControllers:#[firstViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
[self.view addSubview:self.pageController.view];
[self addChildViewController:self.pageController];
[[self view] addSubview:[self.pageController view]];
[self.pageController didMoveToParentViewController:self];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
if (pageIndex == 0) {
return nil;
}
pageIndex--;
return [self viewControllerAtIndex:pageIndex];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
if (pageIndex == 2) {
return nil;
}
pageIndex++;
return [self viewControllerAtIndex:pageIndex];
}
- (UIViewController *)viewControllerAtIndex:(NSUInteger)index {
UIViewController *vc;
if (pageIndex == 0 ) {
vc = [[FirstViewController alloc] init];
}
else if (pageIndex == 1) {
vc = [[SecondViewController alloc] init];
}
return vc;
}
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController {
return 2;
}
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController {
return 0;
}
#end
Now the strange stuff is that the pageViewController:viewControllerAfterViewController is never called.
Can someone help me out?
Thank you
After a night of sleep, I am going to to reply my own question.
I don't know why (any comment appreciated), but the problem was not on that view controller, but it was in the one I was creating it in.
In the parent view controller I was building the UIPageViewController like that:
BreathePageViewController *pageController = [[BreathePageViewController alloc] init];
[pageController.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[self.view addSubview:pageController.view];
And now I tried to move the declaration of pageViewController as an iVar. And it works.
Hope to help someone else

How to add a new view controller in UIPageViewController after UIButton was tapped?

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 UIPageViewController uses 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 labelContents but rather the view controller itself in the modelArray. Besides other things, your current design creates a new ContentVC every 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?)
- (ContentVC *)contentViewControllerWithLabelContents:(NSString *)labelContents
{
ContentVC *vc = [[ContentVC alloc] initWithNibName:#"ContentVC" bundle:nil];
vc.labelContents = labelContents;
return vc;
}
- (void)viewDidLoad
{
NSMutableArray *vcs = [NSMutableArray array];
[vcs addObject:[self contentViewControllerWithLabelContents:#"Page One"]];
[vcs addObject:[self contentViewControllerWithLabelContents:#"Page Two"]];
//...
self.modelArray = vcs;
}
#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:viewController];
if(currentIndex == 0)
return nil;
ContentVC *cVC = [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:viewController];
if(currentIndex == self.modelArray.count - 1)
return nil;
ContentVC *cVC = [self.modelArray objectAtIndex:currentIndex + 1];
return cVC;
}
The code in your comment:
- (IBAction)newButtonTapped:(id)sender
{
if(sender){
[self.modelArray addObject:#"Page Two"];
ContentVC *newVC = [[ContentVC alloc] initWithNibName:#"ContentVC" bundle:nil];
NSArray *arr = [NSArray arrayWithObject:newVC];
[self.pageVC setViewControllers:arr direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
NSLog(#"%#", self.modelArray);
}
}
doesn't work because you didn't set the labelContents of the ContentVC. So you basically end up calling [self.modelObject indexOfObject:nil] or [self.modelObject indexOfObject:#""] depending on your implementation of labelContents. Since neither of them is in the array, the call returns NSNotFound which on 64bit systems is translated to NSIntegerMax and that is 2147483647. Later you try to access your modelArray at index NSIntegerMax - 1 and that raises an NSRangeException.
So can fix that by either setting the labelContents in your newButtonTapped: method or if you follow my suggestion to redesign your code:
- (IBAction)newButtonTapped:(id)sender
{
if(sender){
ContentVC *newVC = [self contentViewControllerWithLabelContents:#"Page Two"];
[self.modelArray addObject:newVC];
NSArray *newVCs = nil;
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation) || self.modelArray.count == 1)
newVCs = #[newVC];
else
{
NSUInteger secondLastIndex = self.modelArray.count - 2;
id previousVC = [self.modelArray objectAtIndex:secondLastIndex];
newVCs = #[previousVC, newVC];
}
[self.pageVC setViewControllers:newVCs direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
}

Resources