Control UIScrollView outside bounds? - iphone

I'm wondering how i can allow the user to scroll outside the bounds of a UIScrollView?

You can try forwarding touch events from the various UIView methods of the superview to the scrollview, and see if that will work. E.g:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[scrollView touchesBegan:touches withEvent:event];
}
// etc
Or you might try using a UIPanGestureRecognizer on the superview and explicitly set the scroll view offset when you get the pan events. E.g:
- (void)handlePan:(UIPanGestureRecognizer *)pan
{
scrollView.contentOffset = [pan translationInView:scrollView];
}
// Or something like that.

Try subclassing the UIScrollView and overriding hitTest:withEvent: so that the UIScrollView picks up touches outside its bounds. Something like this:
#interface MagicScrollView : UIScrollView
#end
#implementation MagicScrollView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// Intercept touches 100pt outside this view's bounds on all sides
if (CGRectContainsPoint(CGRectInset(self.bounds, -100, -100), point)) {
return self;
}
return nil;
}
#end
You may also need to override pointInside:withEvent: on the UIScrollView's superview, depending on your layout.
See the following question for more info: interaction beyond bounds of uiview

Related

UIScrollView sending touches to subviews

Note: I already read some questions about the UIScrollView sending touches to the subviews (this included and although I have up voted, it's not working as I intended anymore).
What I have: I have a UIScrollView with a Custom UIView (let's call it A) inside which covers the entire UIScrollView. I am also allowed to put other custom UIViews inside the A.
On the code I am doing this:
[scrollView setDelaysContentTouches:YES];
scrollView.canCancelContentTouches = NO;
What is happening: At the moment my only issue is that, if I want to move a subview inside A, I have to touch it, wait, and then move it. Exactly as stated here:
Now, the behaviour changes depending on the "length in time" of the
first touch on the UIView. If it's short, then the relative dragging
is managed as it was a scroll for the UIScrollView. If it's long, then
I'm getting the touchesMoved: events inside my UIView.
What I want: The subviews inside A should always receive priority and I shouldn't have to touch and wait. If I touch A and not a subview of it, I want the UIScrollView to receive the touches, like panning and moving around (the contentSize is bigger than the frame).
Edit 1.0
The only reason for me to have this A view inside a generic UIScrollView, is because I want to be able to zoom in/out on the A view. So I am doing the following:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return customView; // this is the A view
}
In the beginning I didn't had the A view inside the UIScrollView and the only thing I did was adding the A as a subView of my UIViewController's root view and everything went well. If there is another way to enable zoom in/out I will gladly accept the answer.
Note: Thank you all for your contributions, specially to Aaron Hayman.
I was able to figure it out by doing the following on the UIScrollView sub-class I had:
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
CGPoint pointOfContact = [gestureRecognizer locationInView:self];
// The view with a tag of 200 is my A view.
return (![[self hitTest:pointOfContact withEvent:nil] isEqual:[self viewWithTag:200]]);
}
I haven't tested this, but I believe how you are handling the touch events in View A (or it's subviews) will determine how touch events are passed on. Specifically, if you're trying to use the methods: touchesBegan, touchesMoves, touchesEnded, etc instead of a UIGestureRecognizer you won't receive the touches in the way you want. Apple design the UIGestureRecognizer to handle problems like the one you're facing. Specifically, the UIScrollView uses UIPanGestureRecognizer to handle the scrolling. If you add a UIPanGestureRecognizer to each of the subviews of View A any "panning" that occurs on one of those subviews should be sent to that subview instead of the UIScrollView. However, if you're simply using the "raw" touches methods, the UIPanGestureRecognizer in UIScrollView will never be cancelled.
In general, it's almost always best to use a UIGestureRecognizer instead of processing the touches directly in the view. If you need touches processed in a way that no standard UIGestureRecognizer can provide, subclass UIGestureRecognizer and process the touches there. That way you get all the the functionality of a UIGestureRecognizer along with your own custom touch processing. I really think Apple intended for UIGestureRecognizer to replace most (if not all) of the custom touch processing code that developers use on UIView. It allows for code-reuse and it's a lot easier to deal with when mitigating what code processes what touch event.
Jacky, I needed a similar thing: Within a building plan (your A, in my case a subclass of UIScrollView), let the user place and resize objects (call them Bs). Here's a sketch of what it took me to get at this behavior:
In the superview's (A) initWithFrame: method, set these two:
self.canCancelContentTouches = YES;
self.delaysContentTouches = NO;
This will ensure taps on B are immediately routed to the Bs.
In the embedded B, stop the superview A from cancelling taps, so it does not interfere with a gesture started on the B.
In the touchesBegan: method, search the view hierarchy upwards (using superview property of the views) until you find a UIScrollView, and set its canCancelContentTouches to NO. Remember the superview you changed, and restore this property in the touchesEnded and touchesCancelled methods of B.
I'd be interested whether this works for you as well. Good Luck!
nobi
I think you had better use "touchesBegan,touchesMoved,touchesEnded" to pass the event.
you can do like this:
you should make a mainView . It has 2 property. One is yourScrollView A , and One is yourCustomView.
`[yourScrollView addSubviews:yourCustomView];
[mainView addSubviews:yourScrollView];`
and then write your touches method in the mainView.m like this (ignor the scrollView statment)
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
if ([[touches allObjects] isKindOfClass:[yourCustomView class]])
{
//do whatever you want
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch=[[touches allObjects] objectAtIndex:0];
if ([[touches allObjects] isKindOfClass:[yourCustomView class]])
{
//do whatever you want
}
}
The last step: pass the event to the subview of the scrollView(your A).
#import "yourScrollView.h"
#implementation yourScrollView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesBegan:touches withEvent:event];
if(!self.dragging)
[[self nextResponder] touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesMoved:touches withEvent:event];
if(!self.dragging)
[[self nextResponder] touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
[super touchesEnded:touches withEvent:event];
if(!self.dragging)
[[self nextResponder] touchesEnded:touches withEvent:event];
}
- (void)dealloc {
[super dealloc];
}
#end
wish to help you

How to resize child view of a scroll view when zooming in iphone sdk

I have a scroll view with an image. User can touch the scrollview and place rectangle views on it. I want that when user pinch zoom a scroll view then the child subviews should automatically resize according to the ratio. Also user should be able to move or resize the child views with touch events. This is working fine when scroll view is not zoomed but when scroll view is zoomed then the child view cant be resized or dragged as the whole scroll view moves. I tried to change the frame size of child view according to the scroll view size but it is not giving proper results .
Is there any way to recieve touch events when scroll view is zoomed
Thanks in advance
#interface UIZoomingViewController : UIViewController <UIScrollViewDelegate>
#property (strong, nonatomic) UIView* substrate;
#end
#implementation UIZoomingViewController
-(id)init /*your init method*/
{
if(self = [super init])
{
UIScrollView* scrollView = ((UIScrollView*)self.view); /*controller view - UIScrollView*/
[scrollView setDelegate:self];
/*set contentsize, min max zoom scale*/
_substrate = [UIView alloc] initWithFrame:scrollView.bounds];
[scrollView addSubview:_substrate];
/*
if you need zoom view, add to substrate
[_substrate addSubview:...];
*/
}
return self;
}
#pragma mark - UIScrollView delegate implementation
-(UIView*)viewForZoomingInScrollView:(UIScrollView*)scrollView
{
return _substrate;
}
#end
and little trick, if you need to receive touches after UIScrollView, create subclass...
#interface UINewScrollView : UIScrollView
#end
#implementation UINewScrollView
-(BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
{
return YES;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self nextResponder] touchesBegan:touches withEvent:event];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self nextResponder] touchesMoved:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[[self nextResponder] touchesEnded:touches withEvent:event];
}
#end

UIScrollView prevents touchesBegan, touchesMoved, touchesEnded on view controller

I am handling touches for a couple of my UI components in my view controller (custom subclass of UIViewController). It has methods touchesBegan:withEvent:, touchesMoved:withEvent:, and touchesEnded:withEvent:. It was working fine. Then I added a scroll view (UIScrollView) as the top view in the hierarchy.
Now my touch handlers on the view controller don't work. They don't get called. The interesting thing is, I have various other UI components within the scroll view that do work. Some are buttons, some are custom views that define their own touchesBegan:withEvent:, etc. The only thing that doesn't work is the touch handlers on the view controller.
I thought maybe it's because the scroll view is intercepting those touches for its own purposes, but I subclassed UIScrollView and just to see if I could get it to work I am returning YES always from touchesShouldBegin:withEvent:inContentView: and NO always from touchesShouldCancelInContentView:. Still doesn't work.
If it makes a difference my view controller is within a tab bar controller, but I don't think it's relevant.
Has anyone had this problem and have a ready solution? My guess is the scroll view monkeys up the responder chain. Can I monkey it back? I guess if I can't figure anything else out I'll make the top level view under my scroll view be a custom view and forward the messages on to the view controller, but seems kludgy.
create a subclass of UIScrollView class and override the touchesBegan: and other touch methods as follows:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// If not dragging, send event to next responder
if (!self.dragging){
[self.nextResponder touchesBegan: touches withEvent:event];
}
else{
[super touchesBegan: touches withEvent: event];
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
// If not dragging, send event to next responder
if (!self.dragging){
[self.nextResponder touchesMoved: touches withEvent:event];
}
else{
[super touchesMoved: touches withEvent: event];
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
// If not dragging, send event to next responder
if (!self.dragging){
[self.nextResponder touchesEnded: touches withEvent:event];
}
else{
[super touchesEnded: touches withEvent: event];
}
}
Well this worked, but I'm not sure I can "get away with it", since nextResponder is not one of the UIView methods you're "encouraged" to override in a subclass.
#interface ResponderRedirectingView : UIView {
IBOutlet UIResponder *newNextResponder;
}
#property (nonatomic, assign) IBOutlet UIResponder *newNextResponder;
#end
#implementation ResponderRedirectingView
#synthesize newNextResponder;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (UIResponder *)nextResponder {
return self.newNextResponder;
}
- (void)dealloc
{
[super dealloc];
}
#end
Then in Interface Builder I made the direct subview of the scroll view one of these, and hooked up its newNextResponder to skip the scrollview and point directly to the view controller.
This works too, replacing the override of nextResponder with these overrides:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.newNextResponder touchesBegan:touches withEvent:event];
}
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[self.newNextResponder touchesMoved:touches withEvent:event];
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self.newNextResponder touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self.newNextResponder touchesCancelled:touches withEvent:event];
}
"It was working fine. Then I added a scroll view (UIScrollView) as the top view in the hierarchy."
is your scrollview on top of your contentview that contains items?
all your components should be in the scrollview and not the view behind the scrollview
user1085093's answer worked for me. Once you move the touch more than a small amount, however, it then gets interpreted as a Pan Gesture.
I overcame this by altering the behaviour of the Pan Gesture recogniser so it requires two fingers:
-(void)awakeFromNib
{
NSArray *gestureRecognizers = self.gestureRecognizers;
UIGestureRecognizer *gestureRecognizer;
for (gestureRecognizer in gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *pgRecognizer = (UIPanGestureRecognizer*)gestureRecognizer;
pgRecognizer.minimumNumberOfTouches = 2;
}
}
}
The touchesBegan: etc methods will NEVER be called in a UIScrollView because it is a subclass of UIView and overrides these methods. check out the different UIScrollView methods available here. The work-around will depend on what you want to implement.

Detect touch on uiview under the UIButton when button is pressed

i am using a uiview parent view....overidded touch began and touch ended methods...
when i added a UIButton as a subview in the view and touch on the button touchdown event is detected and associated method is called ...but touchbegan method that was over ridden is not called...
what i want is when i touch the button the method associated with the touchdown event and touchBegan method of uiview both be called simultaneously...UIbutton is not passing the touch to its superview i.e. uiview.....?
Any idea how to do that ?
Thanks
I'm not sure exactly how to call two touchesBegan events simultaneously on two different views, but you probably want to override the hitTest:withEvent: method of UIView. This will allow you to catch a touch event on the view underneath the UIButton before it gets to the button (hitTests work from the window upwards to the foremost view).
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (passToSubviews)
{
return [super hitTest: point withEvent: event];
}
// otherwise stop the subview receiving touches
if ([super hitTest: point withEvent: event])
{
return self;
}
return nil;
}
Maybe this can help...
EDIT:
Just a guess but you could try:
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* hitSubview = [super hitTest: point withEvent: event];
if (hitSubview)
{
[self doTouchedInStuff];
return hitSubview;
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self doTouchedInStuff];
[super touchesBegan:touches withEvent:event];
}

Passing scroll gesture to UIScrollView from another view

I do have a view that has a UIScrollView and over it there is a view that display some text.
When the user swipes over this view that contains text the UIScrollView won't scroll. How to make this view transparent in a way it relays the swipe gesture to UIScrollView.
Thanks
You can just set
myTextView.userInteractionEnabled = NO;
Or, if you're creating your view with Interface Builder, there's a check box there called 'User interaction enabled', just uncheck that.
Check out the UIView hitTest Method
Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
Inside the -touchesXXXX:withEvent: methods of your custom view, call their super methods to forward touch events.
For example:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// forward touchesBegan to super class
[super touchesBegan:touches withEvent:event];
...
// process touchesBegan for this view
}
Do the same things for touchesMoved, touchesEnded, and touchesCancelled.