UIScrollView determine scroll direction on scrollViewDidEndDragging when paging - iphone

Does anyone know how to determine which direction a scroll view will move when the user lifts their finger, i.e. when scrollViewDidEndDragging gets called?
Specifically, when the scroll view is set to paging.
Most of the time, I can just track and check the contentOffset in scrollViewDidScroll, however, there are special cases where the user flicks the screen quickly. In some cases a LEFT-RIGHT-LEFT move will scroll to the next page, and in others, the same pattern will remain on the same page.
I'm guessing it has something to do the with acceleration (difference between the last few points) of the touch.
(I'm on iOS4.3 on an iPad)

You don't use timer, which is very expensive operation. Lock the direction of your scrollView first to move only up/down and left/right [I guess that's what you want]. And then use following code to determine...
CGPoint start, end;
- (void)scrollViewWillBeginDragging:(UIScrollView *)sender {
start = self.scrollView.contentOffset;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)sender willDecelerate:(BOOL)decelerate {
end = self.scrollView.contentOffset;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender {
NSLog(#"%d %d",(int)start.x,(int)start.y);
NSLog(#"%d %d",(int)end.x,(int)end.y);
}

Here is my solution works fine for me hope it helps
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
CGFloat pageWidth = scrollView.frame.size.width;
//Old Solution
//Switch the indicator when more than 30% of the previous/next page is visible
//You can vary the percentage
//int page = floor((scrollView.contentOffset.x - pageWidth * 0.3) / pageWidth) + 1;
//New Solution
_pageControl.currentPage = (int)scrollView.contentOffset.x / (int)pageWidth;
}
I quit the old solution as the new one seems more slick

Implement the following UIScrollViewDelegate Method.
You can compare current content offset and targetContentOffset then get the direction.
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
CGPoint newOffset = CGPointMake(targetContentOffset->x, targetContentOffset->y);
if (newOffset.x > scrollView.contentOffset.x) {
NSLog(#"Scrolling direction is left");
}else{
NSLog(#"Scrolling direction is right");
}
}

This is a tricky one, iOS 5 has the answer, but that no good for you.
Perhaps a hacky workaround is to set a NSTimer for a small time interval (say 0.1 seconds) after scrollViewDidEndDragging finished. Then compare the content offsets.
If you want to know if the scroll view will actually go to the next page, perhaps you could check if the content offset has gone more than 1/2 way. You could read the content offset on scrollViewDidScroll: and do a bit of maths to determine if its more than 1/2 way.

In addition to saurin response.
Her solution work fine but if you are using pagination and you are in the end of scroll content size you will have to test if we really have a scroll to the next 'page' or not:
CGPoint start, end;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
start = scrollView.contentOffset;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
end = scrollView.contentOffset;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
if (start.x > end.x && end.x>0)
{
NSLog(#"on the previous left page");
}
if (start.x < end.x && end.x < scrollView.contentOffset.x)
{
NSLog(#"on the next right page");
}
}

Related

Make All UIImageViews Point to Static UIImageView

I don't know if I'm thinking right or not, but my issue that I have custom cells (that look like as a cars) in UITableView, each cell contain 2 UIImageViews (wheel images), I need to rotate the Wheel Images when UITableView scrolling either downward of upward, so can I point all wheel images to one image, and then rotate this image immediately when UITableView is scrolling?
This is my code
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGPoint currentOffset = scrollView.contentOffset;
if (currentOffset.y > self.lastContentOffset.y)
{
self.wheelImage.transform = CGAffineTransformRotate(self.wheelImage.transform,- M_PI / 4);
}
else
{
self.wheelImage.transform = CGAffineTransformRotate(self.wheelImage.transform, M_PI / 4);
}
self.lastContentOffset = currentOffset;
}
Sorry for my bad english, and thanks in advance.
Here is the best answer I have found till now, and it is work just fine, but I need to make it to more realistic (apply physical formula):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
int fragment = 20;
for(UITableViewCell *cell in [self.showsTable visibleCells]){
if ([cell isKindOfClass:[ShowCell class]]){
CGPoint currentOffset = scrollView.contentOffset;
if (currentOffset.y > self.lastContentOffset.y){
[[(ShowCell*)cell wheel1Image] setTransform:CGAffineTransformRotate([(ShowCell*)cell wheel1Image].transform, M_PI / fragment)];
[[(ShowCell*)cell wheel2Image] setTransform:CGAffineTransformRotate([(ShowCell*)cell wheel2Image].transform, M_PI / fragment)];
}
else{
[[(ShowCell*)cell wheel1Image] setTransform:CGAffineTransformRotate([(ShowCell*)cell wheel1Image].transform,- M_PI / fragment)];
[[(ShowCell*)cell wheel2Image] setTransform:CGAffineTransformRotate([(ShowCell*)cell wheel2Image].transform,- M_PI / fragment)];
}
self.lastContentOffset = currentOffset;
}
}
}
I believe you can't do it such a way. What you need is to call reloadData for your UITableView from scrollViewDidScroll method. Calling reloadData will call cellForRawAtIndexMethod, and there in that method you should update transform of your wheels for every cell accordingly to current offset.

How to make an UICollectionView with infinite paging?

I have a UICollectionView with 6 pages, and paging enabled, and a UIPageControl. What I want is, when I came to the last page, if I drag to right, UICollectionView reloads from first page seamlessly.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)sender
{
// The key is repositioning without animation
if (collectionView.contentOffset.x == 0) {
// user is scrolling to the left from image 1 to image 10.
// reposition offset to show image 10 that is on the right in the scroll view
[collectionView scrollRectToVisible:CGRectMake(collectionView.frame.size.width*(pageControl.currentPage-1),0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
else if (collectionView.contentOffset.x == 1600) {
// user is scrolling to the right from image 10 to image 1.
// reposition offset to show image 1 that is on the left in the scroll view
[collectionView scrollRectToVisible:CGRectMake(0,0,collectionView.frame.size.width,collectionView.frame.size.height) animated:NO];
}
pageControlUsed = NO;
}
It doesn't work like I want. What can I do?
Here's what I ended up with for my UICollectionView (horizontal scrolling like the UIPickerView):
#implementation UIInfiniteCollectionView
- (void) recenterIfNecessary {
CGPoint currentOffset = [self contentOffset];
CGFloat contentWidth = [self contentSize].width;
// don't just snap to center, since this might be done in the middle of a drag and not aligned. Make sure we account for that offset
CGFloat offset = kCenterOffset - currentOffset.x;
int delta = -round(offset / kCellSize);
CGFloat shift = (offset + delta * kCellSize);
offset += shift;
CGFloat distanceFromCenter = fabs(offset);
// don't always recenter, just if we get too far from the center. Eliza recommends a quarter of the content width
if (distanceFromCenter > (contentWidth / 4.0)) {
self.contentOffset = CGPointMake(kCenterOffset, currentOffset.y);
// move subviews back to make it appear to stay still
for (UIView *subview in self.subviews) {
CGPoint center = subview.center;
center.x += offset;
subview.center = center;
}
// add the offset to the index (unless offset is 0, in which case we'll assume this is the first launch and not a mid-scroll)
if (currentOffset.x > 0) {
int delta = -round(offset / kCellSize);
// MODEL UPDATE GOES HERE
}
}
}
- (void) layoutSubviews { // called at every frame of scrolling
[super layoutSubviews];
[self recenterIfNecessary];
}
#end
Hope this helps someone.
I've been using the Street Scroller sample to create an infinite scroller for images. That works fine until I wanted to set pagingEnabled = YES; Tried tweaking around the recenterIfNecessary code and finally realized that it's the contentOffset.x that has to match the frame of the subview that i want visible when paging stops. This really isn't going to work in recenterIfNecessary since you have no way of knowing it will get called from layoutSubviews. If you do get it adjusted right, the subview may pop out from under your finger. I do the adjustment in scrollViewDidEndDecelerating. So far I haven't had problems with scrolling fast. It will work and simulate paging even when pagingEnabled is NO, but it looks more natural with YES.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
[super scrollViewDidEndDecelerating:scrollView];
CGPoint currentOffset = [self contentOffset];
// find the subview that is the closest to the currentOffset.
CGFloat closestOriginX = 999999;
UIView *closestView = nil;
for (UIView *v in self.visibleImageViews) {
CGPoint origin = [self.imageContainerView convertPoint:v.frame.origin toView:self];
CGFloat distanceToCurrentOffset = fabs(currentOffset.x - origin.x);
if (distanceToCurrentOffset <= closestOriginX) {
closestView = v;
closestOriginX = distanceToCurrentOffset;
}
}
// found the closest view, now find the correct offset
CGPoint origin = [self.imageContainerView convertPoint:closestView.frame.origin toView:self];
CGPoint center = [self.imageContainerView convertPoint:closestView.center toView:self];
CGFloat offsetX = currentOffset.x - origin.x;
// adjust the centers of the subviews
[UIView animateWithDuration:0.1 animations:^{
for (UIView *v in self.visibleImageViews) {
v.center = [self convertPoint:CGPointMake(v.center.x+offsetX, center.y) toView:self.imageContainerView];
}
}];
}
I have not used UICollectionView for infinite scrolling, but when doing it with a UIScrollView you first adjust your content offset (instead of using scrollRectToVisible) to the location you want. Then, you loop through each subview in your scroller and adjust their coordinates either to the right or left based on the direction the user was scrolling. Finally, if either end is beyond the bounds you want them to be, move them to the far other end. Their is a very good WWDC video from apple about how to do infinite scrolling you can find here: http://developer.apple.com/videos/wwdc/2012/

ios uiscrollview with pagingenabled between uiimageviews how to detect which one is on the screen/visible

So I have a UIScrollview that scrolls horizontally with paging through a bunch of different UIImageViews containing user avatars. But, I want to only load the avatars from their URL IFF they are actually showing on the screen (4 are showing at a time, but I can scroll through like 30 or more or less). Is there a way of detecting which UIImageView/ which page it is on? Or does anyone have any general recommendations or ideas for how I should go about this problem? Thank you in advance.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
CPoint offset = scrollView.contentOffset;
int index = offset.x / pageWidth;
switch (index) {
case 0:
//first ImageView
break;
case 1:
//second ImageView
break;
default:
break;
}
}
Or If you tag your imageViews (starting at 1) you can retrieve them via:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
CPoint offset = scrollView.contentOffset;
int index = offset.x / pageWidth;
UIImageView *visibileImageView = (id)[scrollView viewWithTag:index+1];
}
You can use scrollViewDidScroll to detect the position at any point if you use the modulus:
int index = (offset.x - offset.x % pageWidth)/pageWidth;

Check direction of scroll in UIScrollView

I am trying to implement the method scrollViewWillBeginDragging.
When this method is called I check that the user has selected one of the buttons that are within the scrollview is in state:selected.
If not then I display a UIAlert to inform the user.
My problem here is I only want to call the button selected (NextQuestion) method if the user scrolls from right to left (pulling the next view from the right).
But if they are scrolling from Left to Right then I want it to scroll as normal.
At present the checker method is called no matter what direction the user scrolls in. How can I only call a method when they scroll from Right To Left?
Here is how i've currently implemented it:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
[self NextQuestion:scrollView];
}
-(IBAction)NextQuestion:(id)sender
{
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth) / pageWidth) + 1;
NSInteger npage = 0;
CGRect frame = scrollView.frame;
// Check to see if the question has been answered. Call method from another class.
if([QuestionControl CheckQuestionWasAnswered:page])
{
pageNumber++;
NSLog(#"Proceed");
if(([loadedQuestionnaire count] - 1) != page)
{
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
}
// update the scroll view to the appropriate page
frame = scrollView.frame;
frame.origin.x = (frame.size.width * page) + frame.size.width;
frame.origin.y = 0;
[self.scrollView scrollRectToVisible:frame animated:YES];
}
// If question has not been answered show a UIAlert with instructions.
else
{
UIAlertView *alertNotAnswered = [[UIAlertView alloc] initWithTitle:#"Question Not Answered"
message:#"You must answer this question to continue the questionnaire."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil, nil];
[alertNotAnswered show];
}
}
Code for Solution 1:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
NSLog(#"%f",scrollView.contentOffset.x);
if (scrollView.contentOffset.x < lastOffset) // has scrolled left..
{
lastOffset = scrollView.contentOffset.x;
[self NextQuestion:scrollView];
}
}
In scrollViewWillBeginDragging the scroll view has not yet moved (or registered the move) and so contentOffset will by 0. As of IOS 5 you can instead look in the scrollview's panGestureRecognizer to determine the direction and magnitude of the user's scrolling gesture.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
CGPoint translation = [scrollView.panGestureRecognizer translationInView:scrollView.superview];
if(translation.x > 0)
{
// react to dragging right
} else
{
// react to dragging left
}
}
Make a CGFloat lastOffset as a member variable in your class.h file..
then set it 0 in viewDidLoad .
then check in
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x < lastOffset) // has scrolled left..
{
lastOffset = scrollView.contentOffset.x;
[self NextQuestion:scrollView];
}
}
You can keep a starting offset as a member of your class which you store when the delegate receives the scrollViewDidBeginDragging: message.
Once you have that value you can compare the x value of the scroll view offset with the one you have stored and see whether the view was dragged left or right.
If changing direction mid-drag is important, you can reset your compared point in the viewDidScroll: delegate method. So a more complete solution would store both the last detected direction and the base offset point and update the state every time a reasonable distance has been dragged.
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
CGFloat distance = lastScrollPoint.x - scrollView.contentOffset.x;
NSInteger direction = distance > 0 ? 1 : -1;
if (abs(distance) > kReasonableDistance && direction != lastDirection) {
lastDirection = direction;
lastScrollPoint = scrollView.contentOffset;
}
}
The 'reasonable distance' is whatever you need to prevent the scrolling direction to flip between left and right to easily but about 10 points should be about enough.

iPhone: Scroll images infinitely

In my iPhone app I'm looking for a solution where I have some images scrolling horizontally, automatically and infinitely around and around. So if I have four images: first image 1 is shown, then it automatically scrolls out, image 2 is shown, then it scrolls out, ... image 4 is shown, then it scrolls out, and then image 1 is shown again and around it goes... (image1->image2->image3->image4->image1...)
I have been looking for a solution where I use a UIScrollView, but I'm not sure how to implement it. Is there a tutorial or anything on how to do this? How would you solve it? Thanks!
You can implement the ScrollViewDelegate's - (void)scrollViewDidScroll:(UIScrollView *)scrollView method and use the setContentOffset of the scroll view once the content offset is larger than some maximum or less than some minimum.
Before doing that you will have to duplicate the contents of the scroll view at least 3 times, like this: .
Once the scroll offset reaches the duplicated content you will have to set the offset to the original content offset...
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.infiniteScrollView) {
CGFloat xOffset = scrollView.contentOffset.x;
CGFloat yOffset = scrollView.contentOffset.y;
if (xOffset > maxOffset) {
xOffset = origOffset + (xOffset - maxOffset);
}
else if (xOffset < minOffset) {
xOffset = origOffset + (xOffset - minOffset);
}
if (xOffset != scrollView.contentOffset.x) {
[scrollView setContentOffset:CGPointMake(xOffset ,yOffset)];
}
}
}