Someone can help me about the effect scrollview swipe as Yahoo Weather iOS App?
Thanks!
self.backgroundView = [[DKLiveBlurView alloc] initWithFrame: self.view.bounds];
NSString *fileName = [[NSBundle mainBundle] pathForResource:[self.imageArray objectAtIndex:randomId] ofType:#"png"];
UIImage *image = [UIImage imageWithContentsOfFile:fileName];
self.backgroundView.originalImage = image;
self.backgroundView.scrollView = self.tableView;
self.backgroundView.isGlassEffectOn = YES;
self.tableView.backgroundView = self.backgroundView;
You can implement it using UIScrollView with paging. By Setting appropriate contentOffset on didScroll you can achieve the parallax effect like in Yahoo weather. There's a great video on WWDC 2013 about nested scroll views which can be referred to.
I have also created a sample project using scrollView which implements that parallax effect- https://github.com/vin25/SideScrollParallaxEffect-iOS
I believe the Weather App is using UIPageController and the transition effect comes automatically with it. You can find many tutorials about how it works (Take a look to this one for example).
In your app's view controller header file, add a property for the UIPageViewController:
#import <UIKit/UIKit.h>
#interface YourAppViewController : UIViewController <UIPageViewControllerDataSource>
#property (strong, nonatomic) UIPageViewController *pageController;
#end
In your app's view controller .m file, implement these methods:
To increase/decrease the screen index and return the view controller to display):
- (UIViewController *) pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [(YourAppViewController *)viewController index];
if (index == 0) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *) pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [(YourAppViewController *)viewController index];
index++;
if (index == 5) {
return nil;
}
return [self viewControllerAtIndex:index];
}
Finally, to set the number of dots in the page indicator and which dot must be
selected at the beginning.
- (NSInteger) presentationCountForPageViewController:
(UIPageViewController *)pageViewController
{
// The number of items reflected in the page indicator.
return 5;
}
- (NSInteger) presentationIndexForPageViewController:
(UIPageViewController *)pageViewController
{
// The selected item reflected in the page indicator.
return 0;
}
I implemented the parallax effect of Yahoo Weather using a plain UIPageViewController subclass, and autolayout constraints for the parallax effect of the background images.
(I didn't want to recreate a whole scrollView with pages, as I was convinced the UIPageViewController already handles a lot of stuff for free).
Here it is :
https://github.com/frederic-adda/TestParallax/tree/master
Related
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 am having trouble figuring out how to save/pass data between UIViewControllers in UIPageViewController. My setup is like so:
#pragma mark - UIPageViewControllerDataSource
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
//This is nice and avoids having to use a counter
NSString *vcRestorationID = viewController.restorationIdentifier;
NSUInteger index = [self.controllerRestorationIDs indexOfObject:vcRestorationID];
if (index == 0) {
return nil;
}
return [self viewControllerAtIndex:index - 1];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSString *vcRestorationID = viewController.restorationIdentifier;
NSUInteger index = [self.controllerRestorationIDs indexOfObject:vcRestorationID];
//Don't allow it to go forward if there is one at the end
if (index == self.controllerRestorationIDs.count - 1) {
return nil;
}
return [self viewControllerAtIndex:index + 1];
}
#pragma mark - Private Methods
- (UIViewController *)viewControllerAtIndex:(NSUInteger)index
{
// Only process a valid index request.
if (index >= self.controllerRestorationIDs.count) {
return nil;
}
// Create a new view controller.
//Note this is just an extension of UIViewController with a variable inside. All my view controllers in this must be subclassed off BaseContentViewController
BaseContentViewController *contentViewController = (BaseContentViewController *)[self.storyboard instantiateViewControllerWithIdentifier:self.controllerRestorationIDs[index]];
// Set any data needed by the VC here
contentViewController.rootViewController = self;
return contentViewController;
}
This is in my RootViewController.m (the controller that contains UIPageViewController). What I need to be able to do is so save a variable or data in the current displayed controller when a new one is swiped to. Do I need to use a singleton or something for this?
Try to use NSNotificationCenter class in your *ViewController(s)
NSNotificationCenter will probably be your fastest implementation if you only ever have to pass small pieces of information back and forth and need to do so to multiple destinations simultaneously. If, in the far more likely instance, you need to update information frequently and reference it only when necessary then a singleton would be a much more practical solution.
Singletons are surprisingly easy to use and implement. A quick google search had this walkthrough as the first result. That will show you how to set it up, and using it is very similar to using any other property in a view controller.
It may take a couple extra minutes of work to get running over NSNotificationCenter but it's more extensible, readable, and maintainable.
I am new to iPad development. I know how to use images for Page view controller. My problem is i have 3 charts which i have done in three view controllers.. How can i combine all the view controllers in a single page view controller. I have for now kept three view controllers.
I have tried lot of tutorials but none explains me how to use three view controllers
I have done like this now but this is wrong
-(IBAction)handleSwipeLeft:(UISwipeGestureRecognizer*)sender {
LastLearningSessionViewController *last=[[LastLearningSessionViewController alloc]init];
[self presentViewController:last animated:YES completion:nil];
}
From what I can see it appears you may have done this slightly wrong.
First you need to create a controller for the UIPageViewController, that is a datasource and delegate.
Please note all code has been written directly to the answer and has not been tested.
MyUIPageViewController.h
#interface
MyUIPageViewController : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
{
NSNumber *currentIndex //Using NSNumber to handle 32bit/64bit easier.
}
#property (nonatomic, strong) UIPageViewController *pageViewController
#property (nonatomic, strong) NSArray *controllersArray //Used to help navigate between controllers
#end
MyUIPageViewController.m
#import MyUIPageViewController.h
#implementation MyUIPageViewController
- (instancetype)initWithNibName:(NSString *)nibName
bundle:(NSBundle *)nibBundle
{
if(self = [super initWithNibName:nibName bundle:nibBundle])
{
//Create ChartViewController1 (UIViewController *ChartViewController1 = [[ChartViewController1Class alloc] init];)
//Create ChartViewController2
//Create ChartViewController3
//Now we have created all 3 chartViewControllers, create our controllers Array with the controller objects.
self.controllersArray = [[NSArray alloc] initWithObjects:ChartViewController1, ChartViewController2, ChartViewController3];
//Currently setting to 0. A proper way of handling with Multi-tasking is to store the index value from before, but not dealing with that right now.
currentIndex = [NSNumber numberWithInt:0];
//Create our PageViewController. Currently set to PageCurl and all pages will go from left to right.
//These options can be changed, if so desired (Scroll Effect like iBooks Textbooks and a page change from bottom to top like a flip book.
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
//Set ourselves as the datasource and delegate to handle the pages etc.
self.pageViewController.datasource = self;
self.pageViewController.delegate = self;
//We need to set the viewControllers for the PageViewController, because this is the initial load, we will not animate the change.
[self.pageViewController setViewControllers:self.controllersArray direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:^ (BOOL finished) {
//No animation is being done so no need to worry.
}];
//Set our view to be the pagecontroller's view, so we can see it all.
self.view = self.pageViewController.view;
}
return self;
}
//DataSource Methods:
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
//As this method looks for the previous view controller. If our current index is 0, there is no previous VC. But using the objectAtIndex method on the array would throw a outOfRange exception
if([self.currentIndex intValue] <= 0)
{
return nil;
}
else
{
return [self.controllersArray objectAtIndex:([self.currentIndex intValue] - 1)];
}
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
//As this method looks for the next view controller. If our current index is the maximum value the array count and be (2), there isn't a new VC to push. But using the objectAtIndex method on the array would throw a outOfRange exception
if([self.currentIndex intValue] >= self.controllersArray.count)
{
return nil;
}
else
{
return [self.controllersArray objectAtIndex:([self.currentIndex intValue] + 1)];
}
}
//Delegate Methods
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed
{
if(transitionCompleted)
{
//We will update our currentIndex, only if the transition has happened.
switch (previousViewControllers)
{
case 0:
//Something went wrong :S
break;
case 1:
//We are either in Vertical Orientation of the first viewController is only being shown.
if([pageViewController.viewControllers contains:[self.controllersArray objectAtIndex:([currentIndex intValue]+ 1)]])
{
currentIndex = [NSNumber numberWithInt:([currentIndex intValue] + 1)];
}
else
{
if([currentIndex intValue] == 0)
{
//Saftey Net.
}
else
{
currentIndex = [NSNumber numberWithInt:([currentIndex intValue] - 1)];
}
}
break;
case 2:
//We are in horizontal Orientation.
//With 3 View Controllers the only ViewController that will be in both arrays is the ViewController at index 1. We just need to see if the pageViewControllers viewcontrollers array contains the ViewController at index 0 or index 1.
if([pageViewController.viewControllers contains:[self.controllersArray objectAtIndex:([currentIndex intValue]+ 1)]])
{
currentIndex = [NSNumber numberWithInt:([currentIndex intValue] + 1)];
}
else
{
if([currentIndex intValue] == 0)
{
//Saftey Net.
}
else
{
currentIndex = [NSNumber numberWithInt:([currentIndex intValue] - 1)];
}
}
break;
default:
//Should never reach here.
break;
}
}
}
#end
It is also handy to look at the reference documentation:
Class Reference - Apple Docs
I have an UIScrollView that contains 3 UITableView and pagingEnabled = YES.
Users can pan to the left or right to switch between tables (just like the Notification Center of iOS).
I've handled almost all visual bugs (i can help if anyone needed), but the problem is every table have an UISearchBar. Which means in my controller, I've to create 3 UITableView, 3 UISearchBar and 3 UISearchDisplayController.
That will be one of a hell messy controller.
What the best practice in this case ?
It does sound tedious.
Why not create a single UIViewController having a SearchBar and a Tableview. Customize your viewController to be differentiated by tags, add some delegates probably and add it to your UIScrollview? That way, the coding will be structured and clean
I'm just spitballing though.
left or right table will move with your swipe. You have 1 class of your custom UIViewController but different instances.
- (SearchResultViewController *)viewControllerAtIndex:(NSUInteger)index
{
if (([[LeboncoinAgent shareAgent].searchConditions count] == 0) || (index >= [[LeboncoinAgent shareAgent].searchConditions count])) {
return nil;
}
// Create a new view controller and pass suitable data.
SearchResultViewController *pageContentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SearchResultViewControllerId"];
pageContentViewController.pageIndex = index;
pageContentViewController.controller = self;
return pageContentViewController;
}
#pragma mark - Page View Controller Data Source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = ((SearchResultViewController*) viewController).pageIndex;
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
_currentPageIndex = index;
return [self viewControllerAtIndex:index];
}
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