Passing swipe touches from UIView to underlying UIScrollView for proper scrolling - uiview

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;
}

Related

UIScrollView over MKMapView

I'm trying to create a specific layout in an iOS App, where the "background" is a MKMapView and the overlay is a UIScrollView. The idea is that there are two screens, one with a map, and another with some additional information on. The user can scroll down to see this general information, and this view covers up the map and has a blurred background to create some depth.
Here's an image showing the layout:
As you can see, the map is fixed in its position. It will always be behind the content, even if you scroll. I want the map to respond to gestures, but only when used directly on the map. When the user "scrolls" over the bottom bar, I want the content to scroll up, revealing the second page which then covers the map.
I'm lost at what to do to achieve this. I tried putting the map on the original UIView and then covering it up with a scrollview, but this causes the map not to respond to gestures. I only want the UIScrollView to respond to gestures when it's on either the bottom bar, or the second page (each is its own separate view). Otherwise I want the Map to respond to the gestures.
I hope I managed to explain it well, if not, please do not hesitate to ask questions. I appreciate all help!
It sounds like you are adding a full screen UIScrollView on top of a MKMapView and the scrollview is picking up all the touches.
What you are asking about is not necessarily standard so there are multiple ways to implement it and you have to decide what works best for your use case.
Here is what I would do:
Use constraints on the scrollview to ensure that it is only covering the bottom bar area.
Make sure you can toggle the constant of the constraint controlling the scroll view's height (hook up an IBOutlet if you're using storyboard).
Add both a swipe gesture recognizer and a tap gesture recognizer that will fire toggleScrollViewFullScreen if recognized. Ensure that these recognizers can only be recognized while the scrollView is the bottom bar.
Have an X-out button display in the top corner while the scrollview is in full screen. This button can call toggleScrollViewFullScreen to dismiss the view back down.
Example toggleScrollViewFullScreen method:
//Toggle size of scroll view
- (void) toggleScrollViewFullScreen {
CGFloat bottomBarHeight = 100;
if (self.scrollViewHeightConstraint.constant > bottomBarHeight){
self.scrollViewHeightConstraint.constant = bottomBarHeight
}else {
self.scrollViewHeightConstraint.constant = self.view.bounds.size.height;
}
//Animate constraint change:
[UIView animateWithDuration:1 animations:^{
[self.view setNeedsUpdateConstraints];
[self.view setNeedsLayout];
}];
}

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

UIViews revert to a previous position after adding a subview

Facing a really strange issue trying to dynamically add a left navigation panel to a View Controller, (should be able to support any view controller in the app, vaguely similar to the Facebook navigation) My idea seemed fairly simple, but I'm really not seeing where it's breaking down. What I've done is created a Category on UIViewController with the following method which I would think would move all the subviews to the right, and then add the new view.
-(void)addLeftView:(UIView *)newView
{
newView.frame=CGRectMake(0, 0, newView.frame.size.width, self.view.frame.size.height);
for(UIView *view in [self.view subviews])
{
view.frame=CGRectMake(view.frame.origin.x + newView.frame.size.width, view.frame.origin.y, view.frame.size.width, view.frame.size.height);
}
[self.view addSubview:newView];
}
What actually happens, though, is that the view is added, but the subviews do not move to the right. However, if you comment the addSubview out, everything actually does move to the right exactly as expected.
To make matters even weirder, if you wrap the view movement in a [UIView animateWithDuration:completionHandler:], where the completion handler adds the subview, the animation actually happens - all the views shift to the right, but when the subview gets added, they jump back to their starting position.
I assumed this was some sort of wacky auto-layout issue, so just to see what happened, I cleared all the constraints out of that view controller, and get the same result.
Found the answer in a similar but un-related thread. Can I disable autolayout for a specific subview at runtime?
Basically it was auto-layout reverting my positioning, so disabling it by setting this user variable on just the UIViews of problem fixed my issues.
You can set the translatesAutoresizingMaskIntoConstraints type Boolean, Value to Yes in the User Defined Runtime Attributes of the UIView you want in the xib/storyboard.

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.

Showing scroll indicators on a UIScrollView when programmatically scrolling

EDIT: The crux of this problem is that scroll indicators do not show during programmatic scrolling, but I would like them to. My original question (provided below) assumed this had something to do with userInteractionEnabled, but it does not. The mention of a master and slave UIScrollView is also possibly distracting from my core problem (the need to show scroll indicators during a programmatic scroll). Apologies to those of you who answered or commented based on my misleading assumptions/info.
Possible Solution: The only way I found to do this was to use the fact that scroll indicators are instances of UIImageView and use a category on it to hack the alpha. This article shows the approach. It was then a case of using tags and scroll view delegate methods to turn the alpha permanently on prior to a programmatic scroll, and permanently off when the scroll is finished. This feels hacky though, so any further suggestions would be welcome!
Everything below this line is the original unedited question to provide context to users' answers and comments
Setting userInteractionEnabled in a UIScrollView object to NO appears to disable the scroll indicators upon programmatic scrolling. This happens even if you have self.showsVerticalScrollIndicator = self.showsHorizontalScrollIndicator = YES;
Is there any way to programmatically scroll the scroll view but still show the indicators?
(To provide some context: I have a slave scrollview that mimics a master scrollview by hooking up the scrollview delegate callbacks and passing the content offset to the slave scrollview. However, I don't want the user to be able to directly manipulate the slave scrollview, but I do want scroll indicators).
Instead of setting userInteractionEnabled to false try setting the UIScrollView's scrollEnabled property to false. The doc. says "When scrolling is disabled, the scroll view does not accept touch events" that should mean that you should still be able to programmatically scroll the UIScrollView. Hope this helps - Did not test it out let me know.
You could try putting a transparent UIView (alpha == 0.0) over your scroll view (but as a sibling in the view hierarchy, not as a subview). Set touchesEnabled to YES on the transparent view, and it will intercept touches heading for the scroll view.

Resources