I've got several cells containing views in this structure:
Main view
backview
frontview
The cells partly overlap. This means that the main view of cell A will partly cover the frontview of cell B. Like this:
B main view
B backview
B frontview
A main view
A backview
A frontview
I want to intercept touches on frontviews and backviews, but I want main views to ignore them.
(I've tried disabling user interaction on main views, but that also disables front and back views).
Any tips?
I found an answer here: http://vectorvector.tumblr.com/post/2130331861/ignore-touches-to-uiview-subclass-but-not-to-its
Basically, I'm making the main view a subclass of UIView, and overriding hitTest with this:
-(id)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
id hitView = [super hitTest:point withEvent:event];
if (hitView == self) return nil;
else return hitView;
}
(Note that confusingly you must set UserInteractionEnabled to true, ticked, yes for the UIView in question!)
Related
I have a custom view that I present by tapping on either a button on the view controller or by tapping on a button on tableview cell (table view is a child of the view controller)
To dismiss the custom view I want the user to be able to tap anywhere on the screen to dismiss it. However due to the many hierarchies of the view in view controllers. A simple UITapGuestureRecognizer isn't working. Is there any workaround for a case like this?
Create a subclass of UIView, call it MyTapView. Assign this class your parent view, which holds all your subviews.
Override in your class the following to intercept any touches made to your view instance.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (!self.clipsToBounds && !self.hidden && self.alpha > 0) {
for (UIView *subview in self.subviews.reverseObjectEnumerator) {
CGPoint subPoint = [subview convertPoint:point fromView:self];
UIView *result = [subview hitTest:subPoint withEvent:event];
if (result != nil) {
return result;
}
}
}
// use this to pass the 'touch' onward in case no subviews trigger the touch
return [super hitTest:point withEvent:event];
}
This method ignores view objects that are hidden, that have disabled user interactions, or have an alpha level less than 0.01. This method does not take the view’s content into account when determining a hit (feel free to modify this). Thus, a view can still be returned even if the specified point is in a transparent portion of that view’s content and now, after it has been overridden, receives touches outside the bounds.
I have a UITableView with custom TableViewCell inside that custom UITableViewCell there is a UIView(myView). I want to disable didSelectRowAtIndexPath when I tap on myView,didSelectRowAtIndexPathshould not be called when I tap on myView.
it's not same as this question because in that question it's not clear what the user want to achieve and also answer is different.
You can override the pointInside of UITableViewCell, try this:
// MyCell.m
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
BOOL pointInside = [super pointInside:point withEvent:event];
if (pointInside && ![self.myView pointInside:[self convertPoint:point toView:self.myView] withEvent:event]) {
return YES;
}
return NO;
}
didSelectRowAtIndexPathshould not be called when u tap on myView
Accodding to the Responder Chain say
iOS uses hit-testing to find the view that is under a touch. Hit-testing involves checking whether a touch is within the bounds of any relevant view objects. If it is, it recursively checks all of that view’s subviews. The lowest view in the view hierarchy that contains the touch point becomes the hit-test view. After iOS determines the hit-test view, it passes the touch event to that view for handling.
In the image blow :
(Figure 2-1 Hit-testing returns the subview that was touched)
(source: apple.com)
To illustrate, suppose that the user touches view E in Figure 2-1. iOS finds the hit-test view by checking the subviews in this order:
The touch is within the bounds of view A, so it checks subviews B
and C.
The touch is not within the bounds of view B, but it’s within the
bounds of view C, so it checks subviews D and E.
The touch is not within the bounds of view D, but it’s within the
bounds of view E. View E is the lowest view in the view hierarchy
that contains the touch, so it becomes the hit-test view.
As most app do,there is no need to disable the execution of didSelectRowAtIndexPath function.What you can do here ,is to add a UIButton or add a UITapGesture on your myView and perform the desired task,when you touch in the area of the UITapGesture, didSelectRowAtIndexPath will not execute.
Just like this:
I want to display a map view as a permanent background while other views are displayed on top of it (I'm going to set the alpha of the top view to something like 0.9 so the map is just faintly visible underneath) and at some points the map get revealed.
I have a container view which is layered on top of the map view and I would like to know if touch events that occur within the bounds of the container view can be passed to the map view so that it can be scrolled etc. Here's a sketch project showing an example of the architecture.
(The Container view is on top of the bottom half of the map view, the container view and contained View Controller's view's alphas are both 0, so to the user the map is visible on the entire screen).
Its easy to forward the touch events occurring within the Contained View Controller's views or child view controllers to the Map Background View Controller.
If I do something like pass the touch event to the map view like this
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
MapBackgroundViewController *parent = (MapBackgroundViewController *) self.parentViewController;
[parent.mapView touchesBegan:touches withEvent:event];
}
then nothing happens.
Is there a way of passing the touch events to the map view such that it will scroll etc.?
Yes, you can do this.
What I do is subclass UIView and override hitTest:withEvent: such that touches are passed through unless a subview is touched. Something like this:
#implementation PassthroughView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
return view == self ? nil : view;
}
#end
Then I assign this class to my container view and the contained view controller's main view in IB. So you can still interact with the content of the contained view controller, but touches on the container itself get passed through to the map.
You can pack your overlay views all into one container view, in which you then override -pointInside:withEvent: to return NO:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return NO;
}
This will make the view effectively "untappable".
Alternatively you can also override -hitTest:withEvent: to return nil.
Simply setting userInteractionEnabled to NO won't work unfortunately, since the taps will still arrive in the view and be swallowed.
I'm building an iOS app that has a custom UIView upon a UIScrollView which in turn has a subview.
Here's the layout structure:
Note that the custom UIView(called "Detected Object Hint View") is not a subview of ScrollView, it's a sibling view of UIScrollView. And I want to respond to tap gesture on the custom UIView, so I've added UITapGestureRecognizer to the UIView, and it works for tap, but the UIScrollView will never get any touch events (not responding to scroll or zoom gesture).
I've googled a while, and a lot of people pointed out that in order for other view to respond to the touch events, I should implement the following method:
- (id)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self){
return nil;
}
else {
return hitView;
}
}
But once I've added this method to my custom UIView, it will not respond to tap gesture either (of course).
So I'm wondering how can I handle the tap gesture on my custom UIView and pass the touch events to UIScrollView as well?
Big thanks!
I am subclassing a view which is the same size as my main ViewController (1024x768). This subview has a transparent background and contains buttons that are sized 50w X 50h and are positioned dynamically.
My issue is that I need to interact with content and buttons that exist beneath this view but this subview is blocking that interaction.
I've seen some posts address a similar problem, but I am unclear of the actual usage.
-pointInside:withEvent: is how iOS asks if a touch is within a particular view. If a view returns YES, iOS calls -hitTest:withEvent: to determine the particular subview of that view that was touched. That method will return self if there are no subviews at the location of that touch. So you can pass any touches that aren't on subviews back to views behind this one by implementing -pointInside:withEvent: like this:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
return ([self hitTest:point withEvent:event] != self);
}
If you need to catch some touches that aren't on subviews, your implementation will be more complicated, but this method is still the right place to tell iOS where your view is and accepts touch events.
Did you try to set userInteractionEnabled to YES or NO?
If all else fails you can bring those subviews to the front programmatically using
[self.view bringSubviewToFront:buttonToClick];