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
Related
I'm trying to extend the current Picker component for ios in react native:
https://github.com/facebook/react-native/blob/master/React/Views/RCTPicker.m
https://github.com/facebook/react-native/blob/master/React/Views/RCTPickerManager.m
I would like to add the possibility to listen for startDragging and endDragging, startDecelerating and endDecelerating events.
I've found there that we can actually know if the user is currently dragging the Picker by checking its subviews dragging and decelerating flags. That's good but an event/delegate like pattern would suit the React native bridging model better.
Could you please suggest me how to listen to the Picker subviews dragging and decelerating values changes?
--- EDIT ---
Here is what I've tried so far:
I edited the Picker Manager file and added this:
// ...
#implementation RCTPickerManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
//return [RCTPicker new];
RCTPicker* picker = [RCTPicker new];
[self setDelegateForScrollViews:picker];
return picker;
}
// ...
RCT_EXPORT_VIEW_PROPERTY(onScrollChange, RCTBubblingEventBlock)
// ...
-(void)setDelegateForScrollViews:(UIView*)view
{
if([view isKindOfClass:[UIScrollView class]]){
UIScrollView* scroll_view = (UIScrollView*) view;
RCTLogInfo(#"DEBUG setting a delegate on SV");
scroll_view.delegate = self;
}
else {
for(UIView *sub_view in [view subviews]){
[self setDelegateForScrollViews:sub_view];
}
}
}
// UIScrollViewDelegate methods
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
RCTLogInfo(#"DEBUG scrollViewWillBeginDragging");
((RCTPicker*)self.view).onScrollChange(#{ #"state": [NSNumber numberWithBool:YES] });
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate {
RCTLogInfo(#"DEBUG scrollViewDidEndDragging");
((RCTPicker*)self.view).onScrollChange(#{ #"state": [NSNumber numberWithBool:NO] });
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
RCTLogInfo(#"DEBUG scrollViewDidEndDecelerating");
// TODO
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
RCTLogInfo(#"DEBUG scrollViewDidEndScrollingAnimation");
}
#end
But the UIScrollView Delegate methods are never raised when I scroll the picker :(...
Anyone any idea?
Try subclassing the picker view and implementing the scrollview delegate methods. Don't forget to call super. I don't see why you're wanting to do this though. These functions are hidden by design. What are you trying to achieve by this? Even if you have a legit reason, you should probably reconsider what you are trying to do. If you really need to know when a user is scrolling to select something, use a UITableView and you can get its scrollview delegate events.
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.
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.
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
i've got a question regarding a UIScrollView with paging enabled that contains many UIView, each managed by an own UIViewController.
Right now there are about 20 to 30 UIViewControllers that COULD be contained in the UIScrollView. It's a catalog app on the iPad, and I started preloading all the views at the beginning, but with the amount of UIViewControllers getting bigger and bigger, that is not an option any more.
I'm looking for the perfect solution in terms of memory usage. It's no problem to reload the UIViewControllers when the ContentOffset of the ScrollView reaches a specific controller. And I think to nil the UIViewControllers when the ContentOffset tells me that the UIViewControllers is not needed any more isn't that hard as well.
What is the correct way to handle this? Is it enough to alloc the UIViewControllers when needed, putting them into a NSMutableDictionary or NSMutableArray and nil them when they are not needed any more? A little bit of help from someone already having done something similar would be great!
Thanks for your help!
I'm sure there are some good infinite scrolling classes out there, but if you were going to "roll your own", here is a minimalist bit of code that demonstrates the process of infinite scrolling, keeping the current, previous, and next pages in memory, but letting go of anything else. This assumes that:
you're doing horizontal scrolling and have turned on paging;
that you're using view controllers for the child views;
your child view controller class has a page property to keep track of what page it's for; and
you've made your view controller the delegate for your scroll view
Thus, it might look like:
- (void)viewDidLoad
{
[super viewDidLoad];
// my underlying model is just an array of strings, which I'll show on my child
// view; your model will be more elaborate, but I just want to illustrate the concept
self.objects = #[#"1", #"2", #"3", #"4", #"5", #"6", #"7", #"8", #"9"];
// set the `contentSize` for the scrollview
CGRect content = self.view.bounds;
content.size.width *= [self.objects count]; // make it wide enough to hold everything
self.scrollView.contentSize = content.size;
// set our current page and load the first pages (the first and the next pages)
self.currentPage = 0;
[self addChildPage:0 toScrollView:self.scrollView];
[self addChildPage:1 toScrollView:self.scrollView];
}
- (void)addChildPage:(NSInteger)page toScrollView:(UIScrollView *)scrollView
{
// create the child controller
ChildViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"child"];
// set whatever properties you need to in order for it to present its information correctly
controller.text = self.objects[page];
controller.page = page;
// now do the stuff to add it to the right place in the scrollview
CGRect frame = self.view.bounds;
frame.origin.x = frame.size.width * page;
controller.view.frame = frame;
[self addChildViewController:controller]; // containment call for adding child view controller
[scrollView addSubview:controller.view];
[controller didMoveToParentViewController:self]; // containment call when done adding child
}
- (ChildViewController *)childControllerForPage:(NSInteger)page
{
for (ChildViewController *controller in self.childViewControllers)
{
if (controller.page == page)
return controller;
}
return nil;
}
- (void)addChildIfNecessary:(NSInteger)page toScrollView:(UIScrollView *)scrollView
{
if (page < 0 || page >= [self.objects count])
return;
ChildViewController *controller = [self childControllerForPage:page];
if (controller == nil)
[self addChildPage:page toScrollView:scrollView];
}
- (void)removeChildController:(UIViewController *)controller
{
[controller willMoveToParentViewController:nil]; // containment call before removing child
[controller.view removeFromSuperview];
[controller removeFromParentViewController]; // containment call to remove child
}
- (void)updateChildrenViewsForPage:(NSInteger)page forScrollView:(UIScrollView *)scrollView
{
if (page == self.currentPage)
return;
// add child pages as necessary
[self addChildIfNecessary:page toScrollView:scrollView];
[self addChildIfNecessary:(page-1) toScrollView:scrollView];
[self addChildIfNecessary:(page+1) toScrollView:scrollView];
// find any pages that need removing
NSMutableArray *pagesToRemove = [NSMutableArray array];
for (ChildViewController *controller in self.childViewControllers)
{
if (controller.page < (page - 1) ||
controller.page > (page + 1))
{
[pagesToRemove addObject:controller];
}
}
// remove them if they need removing
for (UIViewController *controller in pagesToRemove)
{
[self removeChildController:controller];
}
// update our "current page" index
self.currentPage = page;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSInteger page = scrollView.contentOffset.x / scrollView.frame.size.width + 0.5;
[self updateChildrenViewsForPage:page forScrollView:scrollView];
}
This demonstrates the appropriate custom container calls and the handling of the scroll events. I hope this helps.