I have a page controller that show a day planning with a UITableView inside
The main controller shows the day , and the page content shows the planning of that day. To change the page content it uses the "book effect" animation.
The problem is that, If you start the animation It pass throught
viewControllerBeforeViewController or viewControllerAfterViewController and viewControllerAtIndex, but if the user decide to stay in the same day( the same content page) instead of finish the animation, The day label already changed since I changed it on viewControllerAtIndex
How can I know the real index , or How can I track the real index.
If you need further explanation ask me for them please
Just declare an int on your class and add
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers{
self.nextPageIndex = contenido.pageIndex;
}
And then
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
if(completed){
self.pageIndex = self.nextPageIndex;
}
}
Related
I'm implementing a page based app.
Basically the pageViewController will be used to show pages of data.
Same format, different data.
Created a ViewController to hold the pageViewController and another viewController which is going to present the data.
I only need one different viewController for the data, I saw this can be a problem.
Normal pagers, like a Android ViewPager, will ask you to return the view you want to show given an index, PageViewController doesn't work like that, it just ask you for the next and the previous viewcontrollers.
I was hoping that storing the index in a property of the content ViewController will do the job... but it wasn't.
It is partially working but..
here is my relevant code:
- (void)viewDidLoad {
[super viewDidLoad];
page = [[UIPageViewController alloc]initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
[page setDelegate:self];
[page setDataSource:self];
index = 0;
CampaignContent * content = [[CampaignContent alloc]initWithNibName:#"CampaignContent" bundle:nil];
[content setTitlestr:#"Index 0"];
[content setDesc:#"desc1"];
[content.view setTag:1];
[page setViewControllers:#[content] direction:UIPageViewControllerNavigationDirectionForward animated:TRUE completion:nil];
[page willMoveToParentViewController:self];
[self addChildViewController:page];
[self.view addSubview:page.view];
[page didMoveToParentViewController:self];
}
-(UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
last=false;
NSLog(#"move right index %i",index);
CampaignContent * content = [[CampaignContent alloc]initWithNibName:#"CampaignContent" bundle:nil];
[content setTitlestr:[NSString stringWithFormat:#"Index %i",index+1]];
[content.view setTag:viewController.view.tag+1];
return content;
}
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if(completed)
{
if([[previousViewControllers objectAtIndex:0] view].tag<index){
NSLog(#"Left");
index = index-1;
}else{
NSLog(#"Right");
index = index+1;
}
NSLog(#"didFinishAnimating view tag %i",[[previousViewControllers objectAtIndex:0] view].tag,index);
}
}
-(UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
last= true;
NSLog(#"move left index %i",index);
CampaignContent * content = [[CampaignContent alloc]initWithNibName:#"CampaignContent" bundle:nil];
[content setTitlestr:[NSString stringWithFormat:#"Index %i",index-1]];
[content.view setTag:viewController.view.tag-1];
return content;
}
Just wanting to see the index properly working but instead...
Starting on 0, I swipe to the next page and I see the following in the log
move right index 0
move left index 0
Right
didFinishAnimating view tag 1
move right index 1
Wow such a lot of calls... but index displayed still OK
Again swipe to the next page
Right
didFinishAnimating view tag 2
move right index 2
To this point the index displayed its correct, the problem starts when changing direction.
Swipe to the previous now I see this:
Right
didFinishAnimating view tag 3
move left index 3
Looks like it has failed on didFinishAnimating when determining swipe direction but still index displayed is OK
Again Swipe left
Left
didFinishAnimating view tag 2
Here seems that viewControllerBeforeViewController are not getting called, we have gone from 0->1->2->1->0 right? but index displayed has done 0->1->2->1->2
And if I swipe left again I can see it goes to ->1->0->-1 just like it should.
The same applies going on the other way.
So, I'm thinking that viewControllerBeforeViewController and viewControllerAfterViewController are not reliable and didFinishAnimating can't determine swipe direction alone.
Is there any way I can do this properly or I'm simply doing something bad?
Sorry for my large post, hope someone can help me.
What you can do is use ViewControllerAtIndex method. Inside that method set the contentViewControllers property accordingly based on index.
Then in the viewDidAppear method of contentView Controller you will get the values that you set in the ViewControllerAtIndex method which you can use according to your requirement.
I've been struggling all weekend with this problem and have spent a long time googling to find the solution without any success.
My use case is pretty simple and I can't believe how difficult it is to make such a trivial behaviour work correctly.
My app is a simple paginated flow where users swipe left or right to see the next or previous page. I have a UIPageViewController and each page contains a UITableView. I have had problems trying to keep track of the page index inside the viewControllerAfterViewController and viewControllerBeforeViewController functions for the reasons explained here: PageViewController delegate functions called twice
I've tried following all the suggested workarounds for this problem (keeping track of the index inside willTransitionToViewControllers and didFinishAnimating) but this doesn't solve my problem as the viewController*ViewController functions must still return a viewController and since they are initially called twice, the first returned viewController seems to be the one that gets used and the second pass through doesn't seem to have any affect.
Although I've seen many questions and blogs about this problem, I haven't seen a single example that shows how to consistently return the correct viewController from the viewController*ViewController functions and would be massively grateful for an example. The main issue I can't see a solution to is how to determine the next index inside willTransitionToViewControllers if I only have a single viewController whose content is dynamically updated on page load. It seems like a chicken and egg problem to me; I need to figure out what content to update the page with, but to do that I need to know what the index of the page is (which is part of the content).
Edit
Here is a distilled version of the affected code:
- (void)viewDidLoad {
[super viewDidLoad];
_pageViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"PageViewController"];
_pageViewController.dataSource = self;
_pageViewController.delegate = self;
[self.view addSubview:_pageViewController.view];
PageContentViewController *startingPage = [self viewControllerAtIndex:0];
NSArray *viewControllers = #[startingPage];
[_pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
// Don't do any index calculations as the result is inconsistent
return [self viewControllerAtIndex:nextIndex];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
// Don't do any index calculations as the result is inconsistent
return [self viewControllerAtIndex:nextIndex];
}
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers {
// This value never changes because the global _pageViewContentController hasn't been updated yet
nextIndex = [((PageContentViewController *)pendingViewControllers[0]) pageIndex];
}
- (PageContentViewController *)viewControllerAtIndex:(NSUInteger)index {
_pageContentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"PageContentViewController"];
_pageContentViewController.pageIndex = index;
_pageContentViewController.title = index;
return _pageContentViewController;
}
I've debugged the order in which these functions get called and would expect the global _pageViewContentController to be populated with the correct data after the page has transitioned.
Edit 2
To give some more detail on my problem; each of my pages contains a title and a table of web links. Clicking on a link opens a WebViewController to display the selected web page. With Yunus' solution below, everything displays correctly (the title and links appear as expected) but the problem comes when clicking on a link as this loads a link from the next page. It seems like the rendering phase of the page happens at the point the data is correct, but it then reinitialises the page content controller with incorrect data after rendering has finished which is why the actual link data is wrong (even though the rendered data is good).
Let's keep all the related contentViewControllers in an array
self.contentViewControllers = [NSMutableArray new];
for(int i=0; i< MAX_INDEX; i++) {
[self.contentViewControllers addObject:[self viewControllerAtIndex:0]];
}
Then what we need to do is to decide which view controller we should show on after and before methods
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
for(int i=0;i<self.contentViewControllers.count ;i++)
{
if(viewController == [self.contentViewControllers objectAtIndex:i])
{
if(i-1 >= 0)
{
return [self.contentViewControllers objectAtIndex:i-1];
}
}
}
return nil;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
for(int i=0;i<self.contentViewControllers.count ;i++)
{
if(viewController == [self.contentViewControllers objectAtIndex:i])
{
if(i+1 < self.contentViewControllers.count)
{
return [self.contentViewControllers objectAtIndex:i+1];
}
}
}
return nil;
}
Then in the transition method
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray *)pendingViewControllers
{
UIViewController* viewController = [pendingViewControllers objectAtIndex:0];
NSInteger nextIndex = [self.contentViewControllers indexOfObject:viewController];
[self.pageControl setCurrentPage:nextIndex];
}
I'm trying to implement a moving background picture behind a UIPageView in a panoramic way. (similar to the concept of an android home screen)
The PageView has the transistion style "Scroll".
All I found until now was using the delegate "didFinishAnimation" method:
(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
With this method the background moves just after the new page has been reached. I would like it to happen simultaneously.
Is there any way to synchronize the position of this imageview with the current dragging position of the page view?
Granted it's kind of hacky but in iOS 7.1, the page view controller's scroll view has no delegate by default, and you can become its delegate without accessing any private API. Subclass UIPageViewController, and in your subclass call
- (void)viewDidLoad {
[super viewDidLoad];
// other code
NSInteger scrollViewIdx = [self.view.subviews indexOfObjectPassingTest:^BOOL(UIView *subview, __unused NSUInteger idx, __unused BOOL *stop) {
return [subview isKindOfClass:UIScrollView.class];
}];
NSAssert(scrollViewIdx != NSNotFound, #"Failed to find page view controller scroll view");
UIScrollView *scrollView = self.view.subviews[scrollViewIdx];
scrollView.delegate = self; // or whatever
}
then you can implement
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
Before closing this question as a duplicate just see I'm facing a serious issue for the past two days.
I'm following UIPageViewController with storyboards tutorial.
I tried to develop my UIPageviewController. I've to show a number of user photos. I don't want to show apagecontrol in bottom so I created a custom page control instead. When I try to set the index to the UIPageControl it's not working properly - getting some delay some times reaching 5th page only its updating so the counts are not proper
This is my datasource:
-(UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = ((BMEnlargePhotoPageView*) viewController).pageIndex;
[self.enlargePhotoPageControl setCurrentPage:index];
if((index == 0 ) || (index==NSNotFound)){
return nil;
}
index--;
return [self viewControllerATindex:index];
}
-(UIViewController*)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = ((BMEnlargePhotoPageView*) viewController).pageIndex;
[self.enlargePhotoPageControl setCurrentPage:index];
if(index==NSNotFound){
return nil;
}
index++;
if(index==[self.userImages count])
{
return nil;
}
return [self viewControllerATindex:index];
}
- (BMEnlargePhotoPageView*)viewControllerATindex:(NSUInteger)index
{
if(([self.userImages count]==0) || (index >= [self.userImages count])){
}
BMEnlargePhotoPageView *PageContentViewControl = [self.storyboard instantiateViewControllerWithIdentifier:#"EnlargePhoto"];
PageContentViewControl.imageFIle =self.userImages[index];
PageContentViewControl.pageIndex = index;
return PageContentViewControl;
}
My other issue is that I want to find the current visible photos's index ID so that too I can't take properly - it's getting delayed value and is not correct.
Note: when I use animation page scroll everything works well but in scroll animation it's getting delayed but I want to show it as scroll only i tried many SO ansers nothing getting my problem some one guide or am i doing any wrong.
After long frustration week finally found which one good with iOS7 and don mind answering my question bcz no one answered so i supposed to fill the blank space.
In UIPageViewController there is delegate named didFinishAnimating: so in this there is a BOOL function which can identify your page turned completely or partially so using that BOOL you can check that and when you using even animation scroll it will be useful. Your code goes like this
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
if(completed){
COntentViewController *enlarge =[self.pageViewController.viewControllers lastObject];
self.enlargePhotoPageControl.currentPage = enlarge.pageIndex;
}
}
in this last object hold your last transmitted content if you partially scroll page the BOOL will not be get true it will indicate if the page scrolled completely this will help you those who wants to use custom page control. its working for me well how i expected set your pagecontrol index when the page scrolled completely unless the page indicator will not be set. It remains as last page count thx if i did any mistakes pls correct myself or even some good approach.
I have a UIPageViewController and I just can not figure out how to know to what direction the user turned the page so i can set the page count appropriately.
Thanks
Shani
As Hejazi said
After a gesture-driven transition completes this delegate method
is called:
pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
The part that should be clarified is that completed will be YES if the page was fully turned and will be NO if the page did not actually turn. The NO case happens, for example, when the user just pulls up the corner of the page and then puts it back down without flipping the page.
This is the concept you will want to implement:
- (void)pageViewController:(UIPageViewController *)pvc didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
// If the page did not turn
if (!completed)
{
// You do nothing because whatever page you thought
// the book was on before the gesture started is still the correct page
return;
}
// This is where you would know the page number changed and handle it appropriately
// [self sendPageChangeNotification:YES];
}
After a gesture-driven transition completes this delegate method is called:
pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
So by comparing the previousViewControllers parameter and pageViewController.viewControllers you can know the direction.
The 'Page-Based Application' template provide these 2 methods :
- (NSUInteger)indexOfViewController:(DataViewController *)viewController;
a method for finding index given a view controller
- (DataViewController *)viewControllerAtIndex:(NSUInteger)index
a method for instantiating a view controller given an index.
For making the correct animation, you need to know the index of your current view controller. The page-based template methods are perfect fit for that. Then, you simply compare your 'jump to' index and your 'current' index.
Here's some code to get the idea :
- (void)jumpToPage:(NSInteger)page {
// find current index
DataViewController *currentViewController = (DataViewController *)[self.pageViewController.viewControllers lastObject];
NSUInteger index = [self indexOfViewController:currentViewController];
// choosing the correct direction
// if the 'current' is smaller than the 'jump to' page, then choose forward
// vice versa
UIPageViewControllerNavigationDirection direction;
if (index < page) {
direction = UIPageViewControllerNavigationDirectionForward;
} else {
direction = UIPageViewControllerNavigationDirectionReverse;
}
// choose view controllers according to the orientation
NSArray *viewControllers;
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) {
DataViewController *rightViewController = [self viewControllerAtIndex:page];
viewControllers = [NSArray arrayWithObject:rightViewController];
} else {
DataViewController *rightViewController = [self viewControllerAtIndex:page];
DataViewController *leftViewController = [self viewControllerAtIndex:page-1];
viewControllers = [NSArray arrayWithObjects:leftViewController, rightViewController, nil];
}
// fire the method which actually trigger the animation
[self.pageViewController setViewControllers:viewControllers
direction:direction
animated:YES
completion:nil];
}
You could add a "pageIndex" property to your view controllers that serve as the pages. IOW, when you create the view controllers for viewControllerBeforeViewController & viewControllerAfterViewController (or when you call setViewControllers), store a pageIndex in those view controllers that you can then reference whenever you need to know the index.
great answers for page controller handling. I found that the view controller that was added as a page will call viewWillAppear as the user slides the page into view and will also call viewDidAppear upon completion.
When the user turns a page the UIPageViewController's setViewControllers: method will be called. This method receives an argument of type UIPageViewControllerNavigationDirection that will give you the information you need.