UIScrollView inside of UIScrollView? - ios

Similar questions have been asked to this, but my predicament is actually the opposite of theirs. I've got a fullscreen, paging UIScrollView, with each page being the size of the screen. Inside of that, I've got some pages that are themselves UIScrollViews, with their width greater than the screen width. The inner scrollviews are within a few layers of other UIViews, they are not direct subviews of the outer scrollview.
What I'd like is that, when I get to the end of one of the inner scrollviews, it starts scrolling the outer scrollview. From other questions I see on here, it looks like that should happen, but for some reason it doesn't. What in my setup could be causing this to stop happening? Where in the touch stack is the hand-off supposed to happen between inner and outer scrolling?
edit: Is there any way to pass touches or pan gesture commands out to the outer scroll view using I sense the inner view is past its bounds via scrollViewDidScroll delegate method?

You can call the super class touchesBegan delegate as follows
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
}
Here is the reference link: How to pass the touch event to superview when userInteractionEnabled = YES?

WWDC 2013 Session 217 Exploring Scroll Views on iOS 7 explains in detail how to nest scroll views.

Related

UIViews outside of View not touchable

I'm working on an iOS 7 app.
My ViewController.view is vertically draggable and snaps between 2 positions.
When I drag it up, the ViewController's center.y property is at around -45.
Obviously, some of the objects inside the ViewController.view are positioned "out of bounds". The problem I have with those, especially one them being a UITableView, is that they don't seem to accept touches. I actually implemented:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
And in the area which is (originally) below the ViewController.view's bounds, this method is not called on touch.
The objects are visible, I just can't interact with them. Everything that's originally located within the ViewController.view's bounds works fine.
I tried setting the ViewController.view.frame.size.height to 1000 within viewWillAppear, but that didn't seem to work. I also tried to remove and add the objects again - that doesn't work.
Do I have to put everything into a separate view under ViewController.view or is there a simpler solution?
I searched around and every example of touchesBegan I found moves a subview not the main view. When you implement touchesBegan on a view controller then it will only be called if it's top view has a touch event. So moving the view will move where the events will be caught.
A good solution would be a redesign of your layout so each subview can be dragged.
Also checkout the pan gesture. This guy has really good tutorials:
http://www.raywenderlich.com/6567/uigesturerecognizer-tutorial-in-ios-5-pinches-pans-and-more

UIScrollView: Only scroll when allowed

I've been trying to only have my UIScrollView scroll in one direction and still get all UITouch events. I'm not fully sure this is possible, at all.
I've tried to override the UIResponder's "touches" events, but as soon as the UIScrollView starts scrolling, the touches methods stop getting called.
I then tried setting the UIScrollView's scrollEnabled property to false until I want the UIScrollView to begin receiving drag events, but I can't seem to find a way to have the UIScrollView to begin receiving touch events mid-drag. Is this possible?
My next attempt was adding "padding" to the scroll view and creating my own "touch events" by playing around with the contentOffset property, but that started calling scrollViewDidScroll: an excessive amount of times and processing like crazy - also some issues with the validity of the responses.
I've played around with hitTest:withEvent:, pointInside:withEvent:, touchesShouldBegin:withEvent:inContentView: and touchesShouldCancelInContentView: without any success.
I've tried a few other things, since, but none have worked. Also, from what I've found, setting delaysContentTouches to false destroys the scroll response.
I would love some real overridden touches methods. Methods that if I don't call super, the UIScrollView doesn't receive the touches.
I need a way to get UITouches (Drags, begins, releases and cancels, not just taps), without affecting the scroll of the UIScrollView, since the UIResponder "touches" events drop off as soon as scrolling starts. Is this possible?
If you just want to detect common touches, try attaching gesture recognizers like UITapGestureRecognizer to your view, they should work even with a UIScrollView.
You can also try placing a transparent UIView on top of the scrollview that detects some touches and passes the rest to the scrollview.
When you start to scroll save off a copy of the offset. Then check against it when you finish scrolling. This code allows scrolling to the left but prevents it from scrolling right
CGPoint oldOffset;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
oldOffset = scrollView.contentOffset;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x < oldOffset.x) {
[scrollView setContentOffset:oldOffset];
}
}

How to scroll UIScrollView only with two finger swipe?

I have a UIView in which I draw with a finger, it all goes fine and dandy, but I need this view to be scrollable. So I made a UIScrollView, and embedded my drawing view in it.
But here comes the problem, I can't draw anymore since every tap goes to UIScrollView and it intercepts them for scrolling purposes.
What I want to do, is to make UIScrollView only intercept two finger swipes and with single touches and swipes just draw regularly.
How do I do that?
I draw by using simpe touchesBegan, touchesMoved methods by using BezierPath, without gesture recognizers.
Check out answer https://stackoverflow.com/a/3173290/1492816 in Scrolling with two fingers with a UIScrollView
People report it as working for the kind of situation you are in.
I have tried this, as suggested in that answer, but to no avail. It says that method recognizePan: is not found. I changed it to nil, and scrolling works with two fingers only as intended, by drawing functionality is missing.
I feel like the touches are not getting to my view. What can be done?
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self.scrollView action:#selector(recognizePan:)];
panGestureRecognizer.maximumNumberOfTouches = 1;
[scrollView addGestureRecognizer:panGestureRecognizer];

Propagate dragging touch to UIScrollView superview

I have been looking to all the other similar topics here, using UIGestureRecognizers, using hitTest:withEvent, pointInside:withEvent: etc. but nothing seems to be ok for what I need to achieve.
Basically I have a main view (self.view of a common UIViewController) and a small rectangular UIScrollView attached onto it at the bottom: the scrollView is filled with some UIImageViews and the user can scroll it as usual.
But the user should also be able to drag one UIImageView (or a copy of it) from the UIScrollView to the main view, and, this is what I am finding really difficult, with the SAME dragging gesture, hence I need a way to:
1) Distinguish between normal horizontal scrolling gesture, which should be handled by the UIScrollView the usual way and a dragging gesture over the image view.
2) Once identified a dragging gesture, should propagate the touch to the superview, which will host a copy of the UIImageView and WITH the SAME dragging gesture continue the dragging over the main view even out of the bounds of the UIScrollView.
Please note that I know that if the UIScrollView has userInteractionEnabled = NO the touch is propagated to the subviews, but 1) I want to propagate it to the superview not the subviews, 2) the userInteractionEnabled property apparently becomes active only once the initial gesture is terminated, while I need to use a single dragging gesture.
Thank you very much for any help.
So, so far I have ended up implementing the touchesShouldBegin:withEvent:inContentView: method of my UIScrollView subclass but with delayContentTouches set to YES (default) instead of NO as #nhahtdh was suggesting.
Strangely enough even only implementing the method was sufficient for my subviews to intercept the dragging, and still my scrollview is scrolling properly, while with delayContentTouches set to NO I was not able to scroll it as all the subviews were starting to move around.
Really the credit for this is #nhahtdh, so man, if you post an answer I will accept it, thank you very much for your help.

Passing swipe touches from UIView to underlying UIScrollView for proper scrolling

I have a situation similar to these two posts (1907297 AND 689684) and to describe my situation most concisely, I present this text/graphical layout (similar to what you'd see in IB, dots used to enforce indent levels)
UIView (MainView: 320x460)
. .UIScrollView (ScrollView: 320x460)
. .UIView (OverlayView: 320x40)
. . . .UIButton (ArbitraryButton1)
. . . .UILabel (ArbitraryLabel1)
. . . .UILabel (ArbitraryLabel2)
The goal here is for the OverlayView to serve as a unified, transparent container to position and display some arbitrary buttons/labels on top of the ScrollView. These buttons/labels should remain stationary while the content in the ScrollView beneath moves with user swipes. The buttons/labels may sometimes be hidden/unhidden/scaled in unison (with animation) which is what makes it handy to have them all grouped in the single OverlayView.
The trouble is that, while taps on the OverlayView seem to transmit right through to the underlying ScrollView just nicely, swiping motions have no effect. I can detect/intercept the swipes by overriding the
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
method in the OverlayView, however I haven't yet found a way to properly pass those along to the ScrollView in a way that makes it scroll. Evidently the touchesMoved method is not what UIScrollView uses to detect/interpret swipes?
All the other similar posts I've researched have either found a different solution that wouldn't work in my case or have just gone unsolved. I've also seen mention of employing touchesShouldBegin / touchesShouldCancel though I don't grasp how that would be implemented. Anyhow, still hopeful that there's some insight from the community that can allow me to come up with an elegant solution for this - any sample code would be fantastic.
Thanks in advance, Joel.
P.S. - I should also mention that I need to make this compatible with iOS 3.0 so I think trying to use UIGestureRecognizers is out.
Here's an easier solutions that worked well for me:
In the OverlayView (the view on top of UIScrollView), make the width and height both 0 (so that the view is no longer technically on top of the scrollview) and set clipsToBounds = NO (so that the contents of the OverlayView still show up on top of the scrollview). It worked like a charm for me.
self.OverlayView.clipsToBounds = NO;
CGRect frame = self.OverlayView.frame;
self.OverlayView.frame = CGRectMake(frame.origin.x, frame.origin.y, 0, 0);
Note that if OverlayView contains interactive controls (like the button above) then they will no longer work. You'll need to move it into it's own view above the UIScrollView.
How about, at runtime in viewDidLoad you take the buttons out of the container view and place them in the view as subviews directly (and get rid of the container view)? Then there's no container view to intercept swipes but you can still use a view to group things in IB.
Also potentially you could put the container view in as a subview of the scroll view instead, and in the scroll view delegate keep re-positioning the view whenever the user scrolls. That would seem to have a high potential for being jittery but may be worth a try.
Also if the containing view is a visual container and you need to see it, you could instead use a CALayer that was placed in the superview on top of the CALayer for rendering the scroll view, since CALayers have nothing to do with input and would not each touches.
You should subclass UIScrollView and override the touchesShouldCancelInContentView: method
-(BOOL)touchesShouldCancelInContentView:(UIView *)view
{
if ([view isKindOfClass:[UIButton class]]) {//or whatever class you want to be able to scroll in
return YES;
}
if ([view isKindOfClass:[UIControl class]]) {
return NO;
}
return YES;
}

Resources