How can I prevent the UIScrollView to send touches to it's subviews?
your subview.userInteractionEnabled = NO;
Inherit from UIScrollView and define your own hitTest in following way:
#implementation MyScrollView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [self pointInside:point withEvent:event] ? self : nil;
}
#end
So, touches will not be forwarded into internal subviews and will stay on a level of UIScrollView.
Related
I've got an UIImageView on top of (and not an element of) a UIScrollView. Well, I've subclassed the UIImageView to check for touch events and have overridden the touchesBegan:withEvent: method. Inside that method I basically send the subclassed UIImageView behind the UIScrollView element. (I use [self.super bringSubviewToFront: scrollView] to achieve this).
What I want is to be able to send the current touch event of the UIImageView to the UIScrollView element, so that the user doesn't have to lift his finger and touch again to scroll. I already tried to call touchesBegan:withEvent: on the UIScrollView element, but it did nothing.
Does anyone have an idea of what should be done so I could simulate the touch of the UIImageView to the UIScrollView element?
Here is the overridden method in the subclassed UIImageView:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan: touches withEvent: event];
ViewController* controller = [Singleton getSingleton].controller;
[super bringSubviewToFront: controller.scrollView]; //Now scroll view is in the front
[controller.scrollView touchesBegan: touches withEvent: event]; //Does nothing
}
I need to override hitTest:withEvent: in the UIImageView subclass.
(UIView*) hitTest: (CGPoint)point withEvent: (UIEvent*)event
{
if([self pointInside:point withEvent:event]){
ViewController* controller = [Singleton getSingleton].controller;
[controller.view bringSubviewToFront: controller.scrollView];
return controller.scrollView;
}else{
return [super hitTest:point withEvent: event];
}
}
This way, when a touch on the UIImageView is issued, it would first call this method, which would bring the UIScrollView to front and then return the UIScrollView as the object which should handle the event.
I have a UIView containing a UIScrollView.
This UIView is supposed to respond to some touch events but it is not responding because of the scrollView it contains.
This scrollView's contentView is set to a MoviePlayer inside the OuterView.
Now whenever I click in the area where the MoviePlayer is, touch events of my OuterView are not responding.
I have even set the movie player's userInteraction to NO.
but now scrollView is interfering it seems.
I have referred to this post on SO itself.
How can a superview interecept a touch sequence before any of its subviews?
But the solution there, asks me to set the userInteractionEnabled to NO for the scrollView
But if I do so, my pinch zoom doesn't take place either!
What to do?
An UIScrollView intercepts all touch events because it has to decide if/when the user has movd their finger in order to scroll the scroll view.
You may want to subclass UIView, then in the
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)evt;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)evt;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)evt;
methods, check for the neccessary touches, then forward all these methods to your UIScrollViewInstance. For example:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface MyView: UIView {
UIScrollView *scrollView;
}
#end
#implementation MyView
/* don't forget to alloc init your ScrollView init -init
and release it in -dealloc! Then add it as a subview of self. */
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)evt
{
/* check for a gesture */
[scrollView touchesBegan:touches withEvent:evt];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)evt;
{
/* check for a gesture */
[scrollView touchesMoved:touches withEvent:evt];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)evt;
{
/* check for a gesture */
[scrollView touchesEnded:touches withEvent:evt];
}
#end
I have multiple custom UIWebView's as subviews within a UIScrollView. I have implemented the touch methods within the UIViewController for the UIWebView's, but these methods are not being called.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { }
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { }
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { }
I have read elsewhere and it says that the UIScrollView will consume the events. This makes sense, but should the UIWebView's not receive the touch event before the UIScrollView itself? What is probably happening, is that the UIWebBrowserView (a subview of the UIWebView) is consuming the events. This class can't be subclassed however, and so I can't send the events up the superview chain to the UIScrollView. I have read on the Apple iOS Documentation that one shouldn't ever place a UIWebView inside a UIScrollView, but I need to do so for my purposes.
So my question is: how can my custom UIWebView's (that are subviews of a UIScrollView) receive touch events?
My controllers and views are set up as follows:
HomeViewController.h /.m /.xib
HomeView.h /.m (is a UIScrollView)
DashboardViewController.h /.m /.xib (custom UIWebView's)
HomeViewController is loaded from a Nib file that contains a custom view: HomeView.
DashboardViewController is loaded from a Nib file. It contains a few labels, sliders, etc. (note that there is no custom view here - I am not subclassing UIView here).
HomeViewController dynamically loads several DashboardViewControllers and adds its view to the HomeView.
DashboardViewController *dashboardViewController = [[DashboardViewController alloc] initWithNibName:#"DashboardViewController" bundle:[NSBundle mainBundle];
[homeView addSubview:dashboardViewController.view];
You are probably looking for canCancelContentTouches.
scrollView.canCancelContentTouches = NO;
The User Interaction is standard enabled.
Did you connect your views delegate to the File's Owner?
And did you bind your View to the Controller? And make sure you connect the corresponding actions to the correct methods. (CTRL Click on the View and drag the plus sign onto the corresponding method in your Class.h file.
#interface MyScrollView : UIViewController {
UIView *myFirstView;
UIView *mySecondView;
UIView *myThirdView;
}
#property (nonatomic, retain) IBOutlet UIView *myView;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
#end
Have you set userInteractionEnabled = YES for all the subviews?
UIViewController gets the touches* messages before the UIView. The UIViewController doesn't pass the message along to the UIView (by calling [super touchesBegan]) by default. So you need to move or forward the touches* functions if your UIView has a controller.
Personally I put the various touches* methods on the view subclass, not the view controller subclass.
You can try this, when you get the touches methods being called for UIScrollView in turn you can call the same touches method in subview from scrollview.
I've subclassed UITableView (as KRTableView) and implemented the four touch-based methods (touchesBegan, touchesEnded, touchesMoved, and touchesCancelled) so that I can detect when a touch-based event is being handled on a UITableView. Essentially what I need to detect is when the UITableView is scrolling up or down.
However, subclassing UITableView and creating the above methods only detects when scrolling or finger movement is occuring within a UITableViewCell, not on the entire UITableView.
As soon as my finger is moved onto the next cell, the touch events don't do anything.
This is how I'm subclassing UITableView:
#import "KRTableView.h"
#implementation KRTableView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
NSLog(#"touches began...");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesMoved:touches withEvent:event];
NSLog(#"touchesMoved occured");
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
NSLog(#"touchesCancelled occured");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
NSLog(#"A tap was detected on KRTableView");
}
#end
How can I detect when the UITableView is scrolling up or down?
You don't need to intercept the event methods. Check the docs for the UIScrollViewDelegate protocol, and implement the -scrollViewDidScroll: or -scrollViewWillBeginDragging: methods as appropriate to your situation.
I would like to add the following:
If you are working with a UITableView it is likely that you are already implementing the UITableViewDelegate to fill the table with data.
The protocol UITableViewDelegate conforms to UIScrollViewDelegate, so all you need to do is to implement the methods -scrollViewWillBeginDragging and -scrollViewDidScroll directly in your UITableViewDelegate implementation and they will be called automatically if the implementation class is set as delegate to your UITableView.
If you also want to intercept clicks in your table and not only dragging and scrolling, you can extend and implement your own UITableViewCell and use the touchesBegan: methods in there. By combining these two methods you should be able to do most of the things you need when the user interacts with the UITableView.
It was my mistake, I tried to call the method before reloading the tableview. The following code helped me solve this issue.
[mytableview reloadData];
[mytableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:btn.tag-1] atScrollPosition:UITableViewScrollPositionTop animated:YES];
I have a UIPickerView that gets faded out to 20% alpha when not in use. I want the user to be able to touch the picker and have it fade back in.
I can get it to work if I put a touchesBegan method on the main View, but this only works when the user touches the View. I tried sub-classing UIPickerView and having a touchesBegan in there, but it didn't work.
I'm guessing it's something to do with the Responder chain, but can't seem to work it out.
I've been searching for a solution to this problem for over a week. I'm answering you even if you're question is over a year old hoping this helps others.
Sorry if my language is not very technical, but I'm pretty new to Objective-C and iPhone development.
Subclassing UIpickerView is the right way to do it. But you've to override the - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event method. This is the method called whenever you touch the screen and it returns the view that will react to the touch. In other words the view whose touchesBegan:withEvent: method will be called.
The UIPickerView has 9 subviews! In the UIPickerView class implementation - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event won't return self (this means the touchesBegan:withEvent: you write in the subclass won't be called) but will return a subview, exactly the view at index 4 (an undocumented subclass called UIPickerTable).
The trick is to make the - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event method to return self so you have control over the touchesBegan:withEvent:, touchesMoved:withEvent: and touchesEnded:withEvent: methods.
In these methods, in order to keep the standard functionalities of the UIPickerView, you MUST remember to call them again but on the UIPickerTable subview.
I hope this makes sense. I can't write code now, as soon as I'm at home I will edit this answer and add some code.
Here is some code that does what you want:
#interface TouchDetectionView : UIPickerView {
}
- (UIView *)getNextResponderView:(NSSet *)touches withEvent:(UIEvent *)event;
#end
#implementation TouchDetectionView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
UIView * hitTestView = [self getNextResponderView:touches withEvent:event];
[hitTestView touchesCancelled:touches withEvent:event];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
return self;
}
- (UIView *)getNextResponderView:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch * touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
UIView * hitTestView = [super hitTest:point withEvent:event];
return ( hitTestView == self ) ? nil : hitTestView;
}
Both of the above answers were very helpful, but I have a UIPickerView nested within a UIScrollView. I'm also doing continual rendering elsewhere on-screen while the GUI is present. The problem is that the UIPickerView doesn't update fully when: a non-selected row is tapped, the picker is moved so that two rows straddle the selection area, or a row is dragged but the finger slides outside of the UIPickerView. Then it's not until the UIScrollView is moved that the picker instantly updates. This result is ugly.
The problem's cause: my continual rendering was keeping the UIPickerView's animation from getting the CPU cycles it needed to finish, hence to show the correct, current selection. My solution --which works-- was this: in the UIPickerView's touchesEnded:withEvent:, execute something to pause my rendering for a short while. Here's the code:
#import "SubUIPickerView.h"
#implementation SubUIPickerView
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
[pickerTable touchesBegan:touches withEvent:event];
}
- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
[pickerTable touchesMoved:touches withEvent:event];
}
- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
[singleton set_secondsPauseRendering:0.5f]; // <-- my code to pause rendering
[pickerTable touchesEnded:touches withEvent:event];
}
- (void) touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
[pickerTable touchesCancelled:touches withEvent:event];
}
- (UIView*) hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
if (CGRectContainsPoint(self.bounds, point))
{
if (pickerTable == nil)
{
int nSubviews = self.subviews.count;
for (int i = 0; i < nSubviews; ++i)
{
UIView* view = (UIView*) [self.subviews objectAtIndex:i];
if ([view isKindOfClass:NSClassFromString(#"UIPickerTable")])
{
pickerTable = (UIPickerTable*) view;
break;
}
}
}
return self; // i.e., *WE* will respond to the hit and pass it to UIPickerTable, above.
}
return [super hitTest:point withEvent:event];
}
#end
and then the header, SubUIPickerView.h:
#class UIPickerTable;
#interface SubUIPickerView : UIPickerView
{
UIPickerTable* pickerTable;
}
#end
Like I said, this works. Rendering pauses for an additional 1/2 second (it already pauses when you slide the UIScrollView) allowing the UIPickerView animation to finish. Using NSClassFromString() means you're not using any undocumented APIs. Messing with the Responder chain was not necessary. Thanks to checcco and Tylerc230 for helping me come up with my own solution!
Set canCancelContentTouches and delaysContentTouches of parent view to NO, that worked for me
Couldn't find a recent, easier solution to this so I rigged something to solve my problem. Created an invisible button that stretches over the picker view. Connected that button with a "Touch Down" recognizer to my parent UIView. Now any functionality can be added, I happened to need a random selection timer to be invalidated when someone touches the picker view. Not elegant but it works.