How to find out if scrollView is about to be scrolled up or down - objective-c

I would like to find out if a scrollView is scrolled up or down. Ideally, I'd like to have only one call if the scrollView is scrolled up or down. I tried this but it will obviously not tell me anything about the direction:
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
NSLog(#"%.2f", scrollView.contentOffset.y);
}
contentOffset will always be 0 - it doesn't matter whether I scrolled up or down. Now I could simply check in -(void)scrollViewDidScroll: if the offset is positive or negative, but this is called constantly. scrollViewWillBeginDragging has the advantage of being called only once and this is what I need. Is there something like scrollViewDidBeginDragging? I didn't find anything in the docs. Any smart workaround?

Store the initial content offset in scrollViewWillBeginDragging:
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
self.initialContentOffset = scrollView.contentOffset.y;
self.previousContentDelta = 0.f;
}
And check it on each scrollViewDidScroll:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat prevDelta = self.previousContentDelta;
CGFloat delta = scrollView.contentOffset.y - self.initialContentOffset;
if (delta > 0.f && prevDelta <= 0.f) {
// started scrolling positively
} else if (delta < 0.f && prevDelta >= 0.f) {
// started scrolling negatively
}
self.previousContentDelta = delta;
}

It IS possible to perform this check in scrollViewWillBeginDragging before any scrolling is registered. (IOS 5+). By examining the scroll view's built-in pan gesture recognizer you can check gesture direction.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
CGPoint translation = [scrollView.panGestureRecognizer translationInView:scrollView.superview];
if(translation.y > 0)
{
// react to dragging down
} else
{
// react to dragging up
}
}
I found it very useful in canceling out of a scroll at the very first drag move when the user is dragging in a forbidden direction.

Create a declared property to let us know that the tableview is starting to scroll. Let's use a BOOL called scrollViewJustStartedScrolling.
In scrollViewWillBeginDragging set it to true:
self.scrollViewJustStartedScrolling = YES;
In scrollViewDidScroll do something like:
if (self.scrollViewJustStartedScrolling) {
// check contentOffset and do what you need to do.
self.scrollViewJustStartedScrolling = NO;
}

-(void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
if (velocity.y > 0){
NSLog(#"up");
} else {
NSLog(#"down");
}
}

Related

How do I avoid the jerking when I do an Infinite Horizontal Scroll?

Platform: iOS 10+
I'm able to make a crude infinite scrolling loop via watching WWDC11.
However I can't figure out the algorithm to smooth out the jerking effect.
Any ideas would be appreciated.
Here's the collection view, to be scrolled horizontally:
Here's the UICollectionView where I'm attempting to create an infinite horizontal scroll effect (allowing for the initial scroll to trigger it):
#implementation PhotoCollectionView
- (void)recenterIfNecessary {
CGPoint currentOffset = self.contentOffset;
CGFloat contentWidth = self.contentSize.width;
CGFloat centerOffsetX = (contentWidth - self.bounds.size.width) / 2.0;
CGFloat distanceFromCenter = fabs(currentOffset.x - centerOffsetX);
if (contentWidth > 0 && (distanceFromCenter > (contentWidth / 2.5))) {
self.contentOffset = CGPointMake(centerOffsetX, currentOffset.y);
// Move content by the same amount so it appears to stay still.
// Note: Need to determine correct algorithm.
// Ran out-of-time on this one.
}
}
- (void)layoutSubviews {
[super layoutSubviews];
[self recenterIfNecessary];
}
#end
How do I provide a SMOOTH scroll vs the cell-replenish jerk?
...I want to also use this within Swift 4+ as well as Objective-C.

Zoom simultaneously in two UIImageView

I need to have two UIImageView, both with zoom enabled, (I can use the typical UISCrollView technique) and whenever I zoom in in one of them will result to zoom in the other as well, in the same point and with the same zoom.
Here's an explicative video: https://youtu.be/Ft-Dt3fx3z8?t=29s
Do you know any solution?
Thanks
I will answer my own question, first solution zooming async without scrolling
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
if (scrollView == _scrollView1)
return self.imageView1;
else
return self.imageView2;
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale
{
if (scrollView == _scrollView1){
[_scrollView2 setZoomScale:scale animated:YES];
[_scrollView2 setContentOffset:_scrollView1.contentOffset animated:YES];
}
else if (scrollView == _scrollView2){
[_scrollView1 setZoomScale:scale animated:YES];
[_scrollView1 setContentOffset:_scrollView2.contentOffset animated:YES];
}
}
Second, better solution with real time zooming and scrolling (viewForZoomingInScrollView is still needed)
- (void)matchScrollView:(UIScrollView *)first toScrollView:(UIScrollView *)second {
CGPoint offset = first.contentOffset;
offset.x = second.contentOffset.x;
offset.y = second.contentOffset.y;
[first setContentOffset:offset];
[first setZoomScale:second.zoomScale];
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if([scrollView isEqual:_scrollView1]) {
[self matchScrollView:_scrollView2 toScrollView:_scrollView1];
} else {
[self matchScrollView:_scrollView1 toScrollView:_scrollView2];
}
}

scrollViewDidScroll not called -- scrollViewDidEndDecelerating is called

I have a custom view replacing the keyboard and I'm trying to get it to scroll offscreen when the user scrolls down.
My original scrollViewDelegate methods worked EXCEPT there was a delay between user scrolling and view animation because I was using scrollViewDidEndDecelerating and it took about 1 second for this to be called after the user started scrolling.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
self.isUserScrolling = YES;
// Save to tell if scrolling up or down
initialContentOffset = scrollView.contentOffset.y;
previousContentDelta = 0.f;
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
self.isUserScrolling = NO;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
self.isUserScrolling = NO;
// If scrolled down
CGFloat prevDelta = previousContentDelta;
CGFloat delta = scrollView.contentOffset.y - initialContentOffset;
if (delta > 0.f && prevDelta <= 0.f) {
} else if (delta < 0.f && prevDelta >= 0.f) {
[self animateKeyBoardSpace:[self rectForKeyboardSpace:NO] curve:UIViewAnimationCurveEaseInOut duration:0.25];
}
previousContentDelta = delta;
}
So I am trying to now check for downward scrolling in scrollViewDidScroll and call animateKeyBoardSpace there like so:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
// ScrollDirection scrollDirection;
if (lastContentOffset > scrollView.contentOffset.y) {
[self animateKeyBoardSpace:[self rectForKeyboardSpace:NO] curve:UIViewAnimationCurveEaseInOut duration:0.25];
}
else if (lastContentOffset < scrollView.contentOffset.y) {
}
lastContentOffset = scrollView.contentOffset.x;
}
HOWEVER, scrollViewDidScroll isn't even getting called. It's in the same tableViewController, the delegate is set, and the other delegate methods are getting called.
I was working in the superclass and I had a stub for scrollViewDidScroll in the subclass from some time ago. This was capturing the delegate call.
I just had to delete the subclass stub.

Creating a hidden UISegmentedControl in a UITableView

Like the iBooks app, when you pull down the tableview, a search bar and segmented control appear, to allow you to search and switch between two types of views.
It sticks in that position when you pull down far enough, and alternatively, gets hidden when you pull the tableview up enough.
I am trying to implement the same thing with a UISegmentedControl.
So far I have added a segmented control successfully as a subview to the table. (It has a negative Y frame so make it stick above the tableview).
I have also implemented this code:
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
float yOffset = scrollView.contentOffset.y;
if (yOffset < -70) {
[scrollView setContentOffset:CGPointMake(0.0f, -70.0f) animated:YES];
} else if (yOffset > -10) {
[scrollView setContentOffset:CGPointMake(0.0f, -11.0f) animated:YES];
}
}
This works great, until I try using the segmented control. Where the table will just act like it is scrolling, ignoring the segmented control altogether (i.e. if I tap on a segment, it doesn't even get selected, instead the table scrolls up, hiding the segmented control.
I did use the scrollViewDidScroll method but this made it buggy and the scrolling jumpy.
I also tried to make the segmented control's exclusiveTouch = YES, but this had no effect whatsoever.
I would be thankful for all help! thanks in advance!
Here is my code which works:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//
// Table view
//
if ([scrollView isKindOfClass:[myTableView class]]) {
//
// Discover top
//
CGFloat topY = scrollView.contentOffset.y + scrollView.contentInset.top;
if (topY <= self.tableHeaderHeightConstraint.constant) {
[self setIsScrolledToTop:YES];
} else {
[self setIsScrolledToTop:NO];
}
}
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
//
// Table view
//
if ([scrollView isKindOfClass:[myTableView class]]) {
//
// Toggle favourite category
//
if ([self isScrolledToTop]) {
//
// Show
//
} else {
//
// Hide
//
}
}
}
Edited the above code to make it a bit more generic, but syntactically its correct

iPhone OS3 changes to UIScrollView subclasses

I have a subclass of UIScrollView that overrides
touchesBegan:withEvent:
touchesMoved:withEvent:
touchesEnded:withEvent:
Overriding these three seems to be a technique that is widely used (based on my observations in forums). However, as soon as I compiled this code on OS3, these methods are no longer being called. Has anyone else seen this problem? Is there a known fix that doesn't use undocumented methods?
My first attempt at a solution was to move all the touchesBegan/Moved/Ended methods down into my content view and set
delaysContentTouches = NO;
canCancelContentTouches = NO;
This worked partially, but left me unable to pan when I have zoomed. My second attempt only set canCancelContentTouches = NO when there were two touches (thus passing the pinch gesture through to the content). This method was sketchy and didn't work very well.
Any ideas? My requirement is that the scroll view must handle the pan touches, and I must handle the zoom touches.
My solution is not pretty. Basically there is a scroll view who contains a content view. The scroll view does not implement touchesBegan,Moved,Ended at all. The content view maintains a pointer to his parent (called "parentScrollView" in this example). The content view handles the logic and uses [parentScrollView setCanCancelContentTouches:...] to determine whether or not to let the parent view cancel a touch event (and thus perform a scroll event). The tap count logic is there because users rarely place both fingers onscreen at exactly the same time so the first touch must be ignored if it is very quickly followed by a second.
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
if(parentViewIsUIScrollView)
{
UIScrollView * parentScrollView = (UIScrollView*)self.superview;
if([touches count] == 1)
{
if([[touches anyObject] tapCount] == 1)
{
if(numberOfTouches > 0)
{
[parentScrollView setCanCancelContentTouches:NO];
//NSLog(#"cancel NO - touchesBegan - second touch");
numberOfTouches = 2;
}
else
{
[parentScrollView setCanCancelContentTouches:YES];
//NSLog(#"cancel YES - touchesBegan - first touch");
numberOfTouches = 1;
}
}
else
{
numberOfTouches = 1;
[parentScrollView setCanCancelContentTouches:NO];
//NSLog(#"cancel NO - touchesBegan - doubletap");
}
}
else
{
[parentScrollView setCanCancelContentTouches:NO];
//NSLog(#"cancel NO - touchesBegan");
numberOfTouches = 2;
}
//NSLog(#"numberOfTouches_touchesBegan = %i",numberOfTouches);
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if(touchesCrossed)
return;
if(parentViewIsUIScrollView)
{
UIScrollView * parentScrollView = (UIScrollView*)self.superview;
NSArray * thoseTouches = [[event touchesForView:self] allObjects];
if([thoseTouches count] != 2)
return;
numberOfTouches = 2;
/* compute and perform pinch event */
[self setNeedsDisplay];
[parentScrollView setContentSize:self.frame.size];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
touchesCrossed = NO;
if(parentViewIsUIScrollView)
{
numberOfTouches = MAX(numberOfTouches-[touches count],0);
[(UIScrollView*)self.superview setCanCancelContentTouches:YES];
//NSLog(#"cancel YES - touchesEnded");
//NSLog(#"numberOfTouches_touchesEnded = %i",numberOfTouches);
}
}