Nested UIScrollViews scrolling simultaneously - iphone

I have two nested UIScrollViews: the parent limited to horizontal paging and the child limited to vertical scrolling. The content is one large view that can be freely dragged around, but snaps to one of three horizontal sections. The default behavior of the nested scroll views is to only allow scrolling in one direction at a time, but I wanted to allow simultaneous dragging in both direction to maintain the feeling of manipulating a single large view.
My present solution involved isolating the vertical scroll view's gesture and setting its delegate to my view controller:
for (UIGestureRecognizer *gesture in scrollView.gestureRecognizers)
if ([gesture isKindOfClass:[UIPanGestureRecognizer class]])
gesture.delegate = self;
Then, I implemented the delegate method to allow the gestures of the paging view to recognize simultaneously with the pan gesture of the scroll view:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (gestureRecognizer.view == scrollView && otherGestureRecognizer.view == pageView)
return YES; // allow simultaneous scrolling of pageView and scrollView
return NO;
}
This solution mostly works, but it will occasionally act glitchy when I drag the view around, particularly when I move it around quickly with mouse or drag it around past the view bounds. Specifically, one of the scroll views will temporarily jump back to where it started as if that gesture had been canceled, but then it will jump back if I keep scrolling.
What I want to know is if there is a simpler or more reliable method to achieve scrolling like this that I've overlooked, or if there's anything that I can do to eliminate the glitchy behavior.

The glitchy behavior was happening when the views were dragged out of the content area, released, and then tapped/dragged again before the scroll views bounced back. This could happen, for example, when the view was scrolled by several small swipes. One of the scroll views would get confused and try to decelerate (bounce) while simultaneously being dragged, causing it to jitter back and forth between the origin and where it had been dragged to.
I was able to fix this by reversing the nesting of the scroll views (paging view inside of the vertical scrolling view) and by adding the delegate to the paging view's UIPanGestureRecognizer instead of to the scrolling view's gesture. Now it scrolls naturally as if it was a single scroll view while still conforming to paging only in the horizontal direction. I don't think it was intended for scroll views to be tricked into scrolling simultaneously like this, so I'm not sure if the original glitchy behavior was the result of a bug, or just a consequence of doing something unintended.

Just been struggling with this too and hit a wall with 'NSInvalidArgumentException', reason: 'UIScrollView's built-in pan gesture recognizer must have its scroll view as its delegate.' as pointed out by Rythmic Fistman above.
Found a way around it... first subclass the inner UIScrollView and make it as a
Then all you have to do in the implementation is:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
Seems to work fine.

Related

UIScrollView and content with requires touch move events

I have a view which is using single touch events (single finger) for drawing (lines, cycles, text, and so on).
Now I want to put this view inside of UIScrollView, which will allow zooming and panning. Of course two fingers are required to perform both zooming and panning.
What is the pattern do do that? I've found only examples when contents of UIScrollView accepts only single clicks (it contains only a buttons). Nothing what to do when contents require also touch moves.
Access the panGestureRecognizer property on the UIScrollView, and set its minimumNumberOfTouches property to two:
myScrollView.panGestureRecognizer.minimumNumberOfTouchesRequired = 2;
I'm thinking you should begin with scrolling disabled for scrollview and user interaction enabled for both. Then in touchesBegan: you should check for number of touch points. If it's only one (aka user wants to draw) you do nothing (disabling interaction for scrollview would disable it for all subviews as well). However, if the number of touch points is greater than one, enable scrolling then do
[UIView setUserInteractionEnabled: NO]
This way, no lines should be drawn on the UIView when you pinch or zoom with two fingers on the UIScrollView.
I helped developed an app that required a signature plate with in a scroll view, and a scroll view's subviews are weird to touch events, sometimes you have to hold your finger there for it to pass through the scroll view and reach the inner views, so there was a bit of a lag... but what i ended up doing was subclassing a UIScrollView and overriding the
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {}
method and if the touch landed with in the frame of the signature plate it would
[scrollView setScrollEnabled:NO]; and the drawing happened sooner and more smoother... the only problem is they couldn't scroll the scroll view from that box.. basically i created a dead spot with in the scroll view
but, that's a little wonky, i think a UITableView might work better for you... just make it 1 giant table cell, i think you will get better results....
Don't forget to enable 'Multiple Touch' for these features.

UIScrollView touch down handled by subview

I have a UIScrollView with a child UIView. I've gone ahead and added a UITapGestureRecognizer to the child view like so:
// in child
- (void)setup
{
[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)]];
}
// and the handler
- (void)handleSingleTap:(UITapGestureRecognizer*)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
NSLog(#"tap ended"); // this gets called
else if (sender.state == UIGestureRecognizerStateBegan)
NSLog(#"tap began"); // never get's called
}
This code could be in a view controller but I have reason to handle the touch in the view itself.
As you can see, I'm testing for the state of the tap. However the only state that get's called is when the user removes his finger from the screen, completing the tap. That is, UIGestureRecognizerStateEnded.
When the user taps on a child view (within the scrollView), I understand that the parent scroll view 'intercepts' all touches and decides if they are relevant (for panning, zooming, whatever...) and if not passes the touch(es) down to its subviews. Therefore touchesBegan:withEvent: never gets called on the scrollview's subviews.
WHAT I WOULD LIKE is to detect a touch down event (that is, the instant the user places a finger on the screen), within the child view, regardless of whether the user then pans, scrolls, or taps, and handle that tap within the child. The scrollview's behaviour should be unchanged.
Perhaps there is a better approach sans gesture recognizers such as overriding hitTest:withEvent but my puny human brain can't seem to figure it out!
UPDATE/EXAMPLE:
If you take a look at a UITableView which is a scrollview containing cells. You'll notice that placing your finger on a cell immediately highlights it. Then the act of scrolling 'cancels' the tap. That is generally the idea.
Adding the gesture recognizer to the child view puts you at the mercy of the scroll view, and the events it decides to pass down after it has determined that the user does not intend to scroll. If it fits your needs, one easy fix might be to set:
tableView.delaysContentTouches = NO;
This will cause touch methods to be called immediately.
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIScrollView_Class/Reference/UIScrollView.html
Your other option is to listen for gestures on the scroll view itself, and use hit testing to determine how the user is interacting with child views.

Do gesture recognizers override the scrollview's ability to scroll?

In my app, a top view has pan gesture recognizers to move the view to the left and to the right to reveal two other views underneath it. I'm trying to put a scrollview into the top view, but i can't seem to get it to scroll. I'm using storyboards for the scrollview but the gesture recognizer is implemented programmatically. I have the scroller insets set to exceed the size.
I'm thinking that maybe the gesture recognizers take priority over the scrollview's ability to scroll, but I can't find any way to check.. Unless I'm looking over something stupid, could someone please help with this?
UIScrollView implements a private UISwipeGestureRecognizer that does hold priority over any other gesture. But, it's one many people have overridden (it's fairly easy, just a for-in loop or two) to hook into it's events or provide their own subclass, which is the only way to get other gestures to recognize simultaneously. Actually, that's the name of the delegate method you need to implement, - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer

How To Make Scroll View Scroll Even If A Button Subview Is Pressed

There are countless questions about UIScrollViews, touch events and subviews, but I couldn't find one that solved my dilemma. I have a UIScrollView with delayed and cancellable content touches in which I have added UIButtons. I can scroll the view wherever I touch (including the buttons), but if my finger lingers on a button for too long, it registers as touching the button, as I want and is expected. However, if I then move my finger as if trying to scroll, the scroll view does not scroll - how can I make it do so? The basic effect I am trying to achieve is like a UITableView handles touches for the cells.
Thanks for your help.
Try implementing the following in your UIScrollView subclass:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
return YES;
}

Detecting touches in superview of UITableView when it would bounce if bounces weren't = NO

I've got a UITableView in a UIView subclass in a UIView subclass. I want, instead of the cells "bouncing" down when the the top of the table view is dragged down, the whole UITableView and its containing UIView to be dragged down.
I use the following to stop it from "bouncing" at the top of the UITableView.
-(void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView.contentOffset.y == 0) {
tableView.bounces = NO;
}
}
-(void) scrollViewDidScroll:(UIScrollView *)scrollView {
tableView.bounces = YES;
}
I thought then it might pass touch events for downward swipes up the responder hierarchy. Alas, no. How can I best capture the touch events in my superview, which then moves the UIView containing the UITableView down?
Edit: Just to clarify, here's an example of similar functionality in an existing app: In Twitter's iPad app, if you are zoomed in the browser, with the left edge of the page touching the left edge of the UIWebView, then if you drag from left to right over the web view you end up dragging the web view. That's pretty much what I'm going for, just vertically and with a UITableView.
It sounds as if you are trying to implements scrolling of an entire view that contains a table. If that's the case, may I suggest adding the other portions of the view either as cells in the table, or as a subview in the table.