A reliable way to get UIPageViewController current index - ios

I'm looking for a reliable way to keep track of UIPageViewController current index.
The problem is well known; Although viewControllers are being presented properly, it hard to keep track of the current index.
I thought it will be good to refresh this topic across SO community since it remains unsolved for some reason
I've browsed through many threads here, but most answers are outdated or marked as unreliable(The result depends on if user did a full swipe or just half swiped etc)
I've visited this thread, but it doesn't provide any explicitly correct answer.
I've tried:
1) Keeping track of viewController's view tag - link - always returns 0
2) Looking at index variable in both UIPageViewController methods, viewControllerBeforeViewController and viewControllerAfterViewControlle
its results is unpredictable, sometimes it skips over one index etc.
Have anybody come up with a good way, reliable way to keep track of UIPageCiewController index to make use of it(for example print current index)?
I'd appreciate both obj-c and swift implementation, but swift is the one I'm looking for.

This is for ObjC
ParentViewController
#import "PagesViewController.h"
// Delegate: PageViewDelegate
// Declared inside `PagesViewController`
//
#interface ParentViewController () <UIPageViewControllerDataSource, UIPageViewControllerDelegate, PageViewDelegate>
#property (nonatomic) UIPageViewController *pageViewController;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.dataSource = self;
self.pageViewController.view.frame = self.view.frame;
// im setting page 3 as the default page
//
[self.pageViewController setViewControllers:[NSArray arrayWithObject:[self viewControllerAtIndex:3]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [(PagesViewController *)viewController index];
if (index == 0) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
NSUInteger index = [(PagesViewController *)viewController index];
index++;
if (index == 5) {
return nil;
}
return [self viewControllerAtIndex:index];
}
- (PagesViewController *)viewControllerAtIndex:(NSInteger)index
{
PagesViewController *vc = [[PagesViewController alloc] init];
// set delegate here..
//
vc.delegate = self;
// other data
//
vc.index = index;
vc.titleLabel = [NSString stringWithFormat:#"Screen :%ld", (long)index];
return vc;
}
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{ // The number of items reflected in the page indicator. return x; }
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{ // The selected item reflected in the page indicator. return x; }
// This is what you need
//
- (void)viewController:(id)VC didShowWithIndex:(long)index
{
NSLog(#"didShowWithIndex: %ld", index);
}
PagesViewController.h
#protocol PageViewDelegate <NSObject>
- (void)viewController:(id)VC didShowWithIndex:(long)index;
#end
#interface PagesViewController : UIViewController
#property (weak) id <PageViewDelegate> delegate;
#property (nonatomic) NSInteger index;
#property (nonatomic) NSString *titleLabel;
#end
PagesViewController.m
#implementation PagesViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
UILabel *titleLabel = [[UILabel alloc] initWithFrame:self.view.frame];
titleLabel.textAlignment = NSTextAlignmentCenter;
titleLabel.textColor = [UIColor whiteColor];
titleLabel.text = self.titleLabel;
[self.view addSubview:titleLabel];
}
// Trigger delegate here
//
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.delegate viewController:self didShowWithIndex:self.index];
}
#end

Like #0yeoj suggested, delegation design patter is the solution here. It requires a bit of thinking outside the box.
Let's add protocol IndexDelegate and modify a bit PageContentViewController
protocol IndexDelegate {
func showIndex(index:Int)
}
import UIKit
class PageContentViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
var delegate:IndexDelegate?
var pageIndex:Int = 0
var imageFile:String!
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.image = UIImage(named: self.imageFile)
}
override func viewDidAppear(animated: Bool) {
self.delegate!.showIndex(self.pageIndex)
}
now in our parent view controller we conform to protocol
func showIndex(index: Int) {
print(index)
}
don't forget to set yourPageContentViewControllerInstance.delegate = self
and inherit protocol YourParentViewController:UIViewController, UIPageViewControllerDataSourceDelegate, IndexDelegate {}
That's it! Works perfectly and reliably and doesn't lag at all!

Related

Why isn't my UILabel being set?

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

Adding a UIPageViewController to a subview, subview only shows edge of pageContentview

I'm trying to display an entire pageContentView within a SubView on a rootViewController. I'm able to see the pageView but it doesn't auto-resize correctly to fit the SubView, it only shows a cutoff portion of the pageContentView
Here's what it looks like
Here's what it should look like
I can't figure out how to resize the pageView so it display correctly in a subView.
RootViewController.h
#import <UIKit/UIKit.h>
#import "PageContentViewController.h"
#class PageContentViewController;
#interface CLDViewController : UIViewController <UIPageViewControllerDataSource>
#property (weak, nonatomic) IBOutlet UIView *subViewRoot;
- (IBAction)startWalkthrough:(id)sender;
#property (strong, nonatomic) UIPageViewController *pageViewController;
#property (strong, nonatomic) NSArray *pageTitles;
#property (strong, nonatomic) NSArray *pageImages;
#property (strong,nonatomic) PageContentViewController *bounds;
#end
ViewDidLoad and required methods for UIPageViewController in RootController
- (void)viewDidLoad
{
[super viewDidLoad];
_pageTitles = #[#"Over 200 Tips and Tricks", #"Discover Hidden Features", #"Bookmark Favorite Tip", #"Free Regular Update"];
_pageImages = #[#"page1.png", #"page2.png", #"page3.png", #"page4.png"];
// Create page view controller
self.pageViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"PageViewController"];
self.pageViewController.dataSource = self;
PageContentViewController *startingViewController = [self viewControllerAtIndex:0];
NSArray *viewControllers = #[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
//Trying to setup the pageViewContoller view to be inside the subview (subViewRoot)
self.pageViewController.view.frame = self.subViewRoot.bounds;
[self addChildViewController:_pageViewController];
[self.subViewRoot addSubview:_pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
}
- (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;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Page View Controller Data Source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = ((PageContentViewController*) viewController).pageIndex;
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
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;
}
PageContentViewController.m
-viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
self.backgroundImageView.image = [UIImage imageNamed:self.imageFile];
self.titleLabl.text = self.titleText;
}
Has anyone ran into this situation before? What did you do?
Thanks for the help!
Since you are setting the frame of your UIPageViewController to the frame of that 'subview' placeholder view, you need to make sure that the frame for the placeholder will be accurate by when the view is loaded. ViewControllers loaded from xibs don't generally have a useful frame until their view is added somewhere, and the same goes for their subviews. viewWillAppear is usually the soonest you can be sure your viewController's view to be set correctly.
Disabling auto-layout will work, but auto layout can be a nice feature once you learn how to use it. In this case, you can add constraints from subViewRoot to its parent view - Leading, Trailing, Top and Bottom. Control dragging from the subViewRoot to the parent is an easy shortcut.
I was able to get my view laid out as I wanted using constraints on the PageContentViewController and also adding constraints to the SubView on the RootViewController. (As Acey suggested)
I did this by selecting "resolve auto-layout issues" --> Reset to suggested auto layout constraints.
Then also ctr-clking inside the view and setting width and height constraints. I can even change the orientation and the layout is what I expect.
for anyone struggling with this feel free to send a pm or comment here.

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

Need help for pushing a view in UIScrollView / UIPageControl on storyboard

I'm trying to implement UIScrollView with UIPageControl on storyboard to display multiple views. All the examples, tutorials or informations I found are using xib and not storyboard. I tried to adapt Apple's sample code but I'm missing something. As you can see, it is quite simple.
However, either I get an error in ContentController.m -[NSNull view]: unrecognized selector sent to instance 0x129acd8 when I test if (controller.view.superview == nil) where I push the view (see below after the first half of code).
Or only the UIScrollView and UIPageControl are working and I am not able to push the view in the UIScrollView:
I know it's a simple question, however I spent many hours on this and any help will be appreciated.
I post the full code as it may help someone when resolved like a kind of tutorial. I will correct the code if/when needed according to contributions.
Thanks in advance
AppDelegate.h
// Nothing special as I don't want to manage it through application delegate
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#end
AppDelegate.m
// Nothing special as I don't want to manage it through application delegate
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application { }
- (void)applicationDidEnterBackground:(UIApplication *)application { }
- (void)applicationWillEnterForeground:(UIApplication *)application { }
- (void)applicationDidBecomeActive:(UIApplication *)application { }
- (void)applicationWillTerminate:(UIApplication *)application { }
#end
ContentController.h
// From Apple's PageControl sample code
#import <UIKit/UIKit.h>
#interface ContentController : UIViewController <UIScrollViewDelegate> {
UIScrollView *scrollView;
UIPageControl *pageControl;
NSMutableArray *viewControllers;
BOOL pageControlUsed;
}
#property (nonatomic, retain) IBOutlet UIScrollView *scrollView;
#property (nonatomic, retain) IBOutlet UIPageControl *pageControl;
#property (nonatomic, retain) NSMutableArray *viewControllers;
- (IBAction)changePage:(id)sender;
#end
ContentController.m
// From Apple's PageControl sample code
#import "AppDelegate.h"
#import "ContentController.h"
#import "MyViewController.h"
// Private methods
#interface ContentController (PrivateMethods)
- (void)loadScrollViewWithPage:(int)page;
- (void)scrollViewDidScroll:(UIScrollView *)sender;
#end
#implementation ContentController
#synthesize scrollView, pageControl, viewControllers;
static NSUInteger kNumberOfPages = 6;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.navigationController.navigationBar.hidden=YES;
// view controllers are created lazily in the meantime, load the array with
// placeholders which will be replaced on demand
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++) {
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
// a page is the width of the scroll view
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;
pageControl.numberOfPages = kNumberOfPages;
pageControl.currentPage = 0;
// pages are created on demand
// load the visible page
// load the page on either side to avoid flashes when the user starts scrolling
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.pageControl=nil;
self.pageControl = nil;
self.viewControllers = nil;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)loadScrollViewWithPage:(int)page {
if (page < 0)
return;
if (page >= kNumberOfPages)
return;
// replace the placeholder if necessary
MyViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) // <== *** HERE SEEMS TO BE THE ERROR ***
{
controller = [[MyViewController alloc] initWithPageNumber:page
initWithNibName:nil
bundle:nil];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
// add the controller's view to the scroll view
if (controller.view.superview == nil)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
- (void)scrollViewDidScroll:(UIScrollView *)sender {
// We don't want a "feedback loop" between the UIPageControl and the scroll delegate in
// which a scroll event generated from the user hitting the page control triggers updates from
// the delegate method. We use a boolean to disable the delegate logic when the page control is used.
if (pageControlUsed) {
// do nothing - the scroll was initiated from the page control, not the user dragging
return;
}
// Switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = page;
// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
// A possible optimization would be to unload the views+controllers which are no longer visible
}
// At the begin of scroll dragging, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
pageControlUsed = NO;
}
- (IBAction)changePage:(id)sender {
int page = pageControl.currentPage;
// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
// update the scroll view to the appropriate page
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
[scrollView scrollRectToVisible:frame animated:YES];
// Set the boolean used when scrolls originate from the UIPageControl. See scrollViewDidScroll: above.
pageControlUsed = YES;
}
#end
MyViewController.h
#import <UIKit/UIKit.h>
#interface MyViewController : UIViewController {
UILabel *pageNumberLabel;
int pageNumber;
}
#property (nonatomic, retain) IBOutlet UILabel *pageNumberLabel;
- (id)initWithPageNumber:(int)page initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;
#end
MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
#synthesize pageNumberLabel;
- (void)loadView {
}
- (void)viewDidLoad {
[super viewDidLoad];
// Set the label and background color when the view has finished loading
pageNumberLabel.text = [NSString stringWithFormat:#"Page %d", pageNumber + 1];
}
- (void)viewDidUnload {
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
// Load the view nib and initialize the pageNumber ivar
// classic initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
// class adapted to init the page number
// /!\ be careful, I'm not sure it's working properly
- (id)initWithPageNumber:(int)page initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
pageNumber = page;
NSLog(#"pageNumber = %i", pageNumber);
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#end
The unrecognized selector error is happening because you are redeclaring your local variable "controller" within the if statement in loadScrollViewWithPage: Because you are doing so, the new controller variable only exists within the scope of the if statement, and when you exit the if statement the controller variable outside that scope is still NSNull.
You can fix by removing the variable definition inside the if statement:
if ((NSNull *)controller == [NSNull null])
{
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"MVC"];
[controller initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
So I've been having exactly the same problem you were having but I just did something that might have worked just fine.
Short answer is an addition to Jonkroll's answer about initiating the controller inside the if statement. For some reason I can't explain the empty placeholder is not null so the if statement doesn't execute as you pointed out in your code. So what I did was modify the if and add an else to that same if and load the ViewController from the storyboard.
if ((NSNull *)controller == [NSNull null]) {
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"MVC"];
[viewControllers replaceObjectAtIndex:page withObject:[controller initWithPageNumber:page]];
}
else {
controller = [self.storyboard instantiateViewControllerWithIdentifier:#"MVC"];
[viewControllers replaceObjectAtIndex:page withObject:[controller initWithPageNumber:page]];
}
The rest of my code is exactly as yours. I know is not the best code ever and I might be ok by just initiating the ViewController without an if/else statement all together but I don't have the answer as to why this is needed or worst why is not actually working as I expect. If anyone knows please share.
First of all, thanx for this thread, it indeed served as a kind of a tutorial or a reference to me.
I did everything almost exactly like you did, except in content controller i put following code (based on #jonkroll's answer):
if ((NSNull *)controller == [NSNull null]){
controller = [[self.storyboard instantiateViewControllerWithIdentifier:#"MyViewController"] initWithPageNumber:page initWithNibName:nil bundle:nil];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
MyViewController (has different name in my case, but it's of no relevance) inherits from UITableViewController, because it was kind of stuff i needed.
My initWithPageNumber method looks like this:
- (id)initWithPageNumber:(int)page initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
pageNumber = page;
NSLog(#"PageNumber = %i", pageNumber);
return self;
}
Added return.self, notice that.
One, last thing is, i embedded ContentController in Navigation controller, via editor->embed in->navigation controller.
That's pretty much everything. Later, i even managed to get navigation from MyViewController to another view and everything works fine.
If you need further help, you are free to ask here.
Regards.
I had the same problem with you when I tried to follow the sample code of UIPageController from Apple Documents. For I did my MyViewController programmatically, so I directly use the following code
if ((NSNull *)controller == [NSNull null])
{
MyViewController * controller = [[MyViewController alloc]init];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
And I changed nothing in MyViewController.

Resources