UITapGestureRecognizer not recognized because view is nested in multiple views - ios

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.

Related

Enable scrolling in a region for nested scroll views

I am working with nested scroll views as described here to create cross-directional scrolling.
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/UIScrollView_pg/NestedScrollViews/NestedScrollViews.html
Is it possible to define a region that the user can touch to scroll while still passing those touches to the nested scroll views?
Here is an example using the image from that documentation link. I only want the red region for UIScrollView A to be scrollable while still passing touches to UIScrollView B if the user scrolled on the right side.
The problem here is if I block touches using a method like -pointInside:withEvent: then the nested scroll views won't get the touch. I want the nested scroll views to accept scrolling in the entire view but the parent to only accept scrolling touches in part of it's view marked in red for this example.
You can overload method hitTest, in your parent view. This method should returns active scroll view.
And also you should write some custom code to manage touchable areas.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
if (result)
{
if ([self scrollChildWithPoint:point])
{
result = self.childScrollView;
}
else if([self scrollParentWithPoint:point])
{
result = self.parentScrollView;
}
else
{
result = nil;
}
}
return result;
}

How to make the subview outside the bounds recognize the touches

I have a view with a subview. When a button in the subview is tapped, the subview expands outside the bounds of a view, presenting couple of other buttons. However, I cannot find a way to interact with them.
I found a code at Apple's site:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// Convert the point to the target view's coordinate system.
// The target view isn't necessarily the immediate subview
CGPoint pointForTargetView = [self.targetView convertPoint:point fromView:self];
if (CGRectContainsPoint(self.targetView.bounds, pointForTargetView)) {
// The target view may have its view hierarchy,
// so call its hitTest method to return the right hit-test view
return [self.targetView hitTest:pointForTargetView withEvent:event];
}
return [super hitTest:point withEvent:event];
}
However, I cannot understand how should I use it, so that my subview will recognize the touches.
Any help would be greately appreciated.
You need to subclass the UIView or which ever class you need and override that method. Then create an object of that subclass and use it. It will then recognize the touches.

Ignoring touch events on UIView except for the Buttons on them.

I have a MainViewController(A) and another DetailedViewController(B) as its subview. In "B" I have a UIView containing some UIControls like a UIButton, UILabel and some UIViews.
I need to ignore all the touch events within this UIView except for Button clicks and I have to remove the subView if clicked outside the UIView.
How can I accomplish this ?
A option might be to set a custom view in the detail view controller.
In that view you can override the hittest function.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *resultView = [super hitTest:point withEvent:event];
if (![resultView isKindClass:[UIButton class]])
{
resultView = nil;
}
return resultView;
}
Touches not on the buttons will than be ignored. But if you have a scroll view under it and you will start dragging on the buttons the scrollview will be ignored.

Can touch events be forwarded to an MKMapView?

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.

How to support touch move across various objects?

I have some labels in my app like this...
What i need to do is, when clicking on a label, just I'm showing label name in the bottom of the screen. It works fine while clicking on each cell separately. But i want to show the changes even the user click on a particular label and move his finger on another label. That is, once he pressed on screen, where ever his finger moves, i want to trace those places and want to show the changes. How can i do this? Please explain briefly.
Thanks in Advance
By default, touch events are only sent to the view they started in. So the easiest way to do what you're trying to do would be to put all of your labels in a container view that intercepts the touch events, and let the container view decide how to handle the events.
First create a UIView subclass for the container and intercept the touch events by overriding hitTest:withEvent::
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// intercept touches
if ([self pointInside:point withEvent:event]) {
return self;
}
return nil;
}
Set that custom class as the class of the container view. Then, implement the various touches*:withEvent: methods on your container view. In your case, something like this should work:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
// determine which view is under the touch
UIView* view = [super hitTest:[[touches anyObject] locationInView:self] withEvent:nil];
// get that label's text and set it on the indicator label
if (view != nil && view != self) {
if ([view respondsToSelector:#selector(text)]) {
// update the text of the indicator label
[[self indicatorLabel] setText:[view text]];
}
}
}

Resources