How to make the subview outside the bounds recognize the touches - ios

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.

Related

UITapGestureRecognizer not recognized because view is nested in multiple views

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.

forwarding UISwipeGestureRecognizer to underlying UIScrollView

in my viewController I have an imageSlider which is above a UITableView.
I want to forward vertical swipeGesture from imageSlider to the underlying tableview. so when I swipe vertically on imageSlider, underlying tableView begins to scrolling. (tableview can scroll below imageSlider)
what I did:
I have created a custom view for my viewController View and have override hitTest:WithEvent: method:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *targetView = self.flexibleHeader;
// Convert the point to the target view's coordinate system.
// The target view isn't necessarily the immediate subview
CGPoint pointForTargetView = [targetView convertPoint:point fromView:self];
if (CGRectContainsPoint(targetView.bounds, pointForTargetView)) {
// The target view may have its view hierarchy,
// so call its hitTest method to return the right hit-test view
UIView *finalView = [targetView hitTest:pointForTargetView withEvent:event];
//final view should be UIButton or other UIControl element in RestaurantFlexibleHeader view
if (![finalView isKindOfClass:[targetView class]]) {
return finalView;
}
}
return [super hitTest:point withEvent:event];
}
but the problem is that I can't detect swipe gesture from UIEvent. because allTouches property is empty!
I can put imageSlider below tableView, so When I start to scroll vertically on imageSlider tableView begins to start scrolling. but how can I forward horizontal swipe and tap event to imageSlider?
so whats your idea? how do you handle this dilemma?

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.

Touch event handled by multiple views

I have a subclass of UIView on top of a UITableView. I am using the UITableView to display some data and, at the same time, I would like to overlay an animation that follows the finger (for instance, leaving a trail).
If I get it right, I need the touch events to be handled both by the UIView subclass and the UITableView. How can I do that?
Is it possible to have, ie, touchesMoved being triggered on the UIView subclass and then on UITableView?
Thank you so much for any help.
The way I have solved this problem is in a way that is not that clean, but it works. Please let me know if there's a better way to do this.
I have overridden hitTest for my custom UIView so that it directs touches to the UITableView underneath. Then in the UITableView I am handling the gestures through touchesBegan, touchesMoved, etc. There I am also calling touchesBegan on the UIView.
In this way touches are handled by two views.
The reason why I am not doing the other way around (having UIView's touchesBegan calling UITableView's touchesBegan) is that gestures recognizers on the UITableView would not work.
UIView subclass' hitTest
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
// tview is the UITableView subclass instance
CGPoint tViewHit = [tView convertPoint:point fromView:self];
if ([tView pointInside:tViewHit withEvent:event]) return tView;
return [super hitTest:point withEvent:event];
}
UITableView subclass's touchesBegan
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:touch.view];
// ....
// view is the UIView's subclass instance
[view touchesBegan:touches withEvent:event];
}
No, you cann't do it implicity. Event Delivery chapter says
The window object uses hit-testing and the responder chain to find the
view to receive the touch event. In hit-testing, a window calls
hitTest:withEvent: on the top-most view of the view hierarchy; this
method proceeds by recursively calling pointInside:withEvent: on each
view in the view hierarchy that returns YES, proceeding down the
hierarchy until it finds the subview within whose bounds the touch
took place. That view becomes the hit-test view.
So, when window finds touched view it returns YES. Only one view can handle touches at the current moment.
But if you need to handle event for UITableView then handle it for UIView! You can convert touched point to required coordinates with – convertPoint, – convertRect functions, add subview to UITableView and move it depends on coordinate, and a lot of another things.
UITableView relays unhandled touch events to UIView. (Google "responder chain")
UITableView Documentation
So, you can handle your touch events in UIView only. So. In your UIView
touchesstart - do initialization stuff
touchesmove - draw tail on UIView (Use timers/delayedresponse to desable points so that it would look like a trail)
touchesend - do remaining stuff
Hope this helps.

Detecting clicks outside of UIScrollView

I've implemented a paged scroll according to this technique
( iOS develop. How to extend UIScrollView's scroll event responding area? ) and it works just as intended.
The view that I'm scrolling is containing a couple of buttons and I want to be able to click not only those that are centered/paged into the scrollview but also those to the left and to the right of it. I cannot find any way to solve this but I'm not really an iOS-Jedi yet, hoping one of you are though :)
So as you can see from the screenie the UIScrollView is about a third of the width of the window, the contentsize of the UIScrollView is much larger: about 1500px and contains a lot of buttons added programmatically. The cool thing with this solution, and the part that actually works, is that the buttons:
1) are paged into the scrollview
2) are visible outside the scrollview (since "clip subviews" is unchecked for the scrollview) 3) the buttons are clickable when visible inside the uiscrollview.
BUT what doesn't work is simply this:
- the buttons currently being outside of the window does not receive "their" clicks when clicking on them, the events are instead forwarded to the underlaying (the white part of the window) view.
So,
I finally managed to solve this puzzle and the solution is divided into two
parts. The problem was, as you way recall, that the click events did not travel to the
buttons that were(visible) outside the UIScrollView. It turned out that the clicks were captured by the underlying view and that it is possible to manipulate their way to finding their target by bending the rules a bit regarding who got hit and thereby tricking the events to get passed where you want them. Not really sure if this is how it should be done but it solved my problem.. . :)
1) First one must override the following method in the bottom view
so that it returns the scrollview instead of itself when appropriate.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
if (view == self)
return [self scrollView];
return view;
}
2) The scrollView must override TWO methods to hand over the clicks to its contained objects.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
// Always return us.
return view ;
}
and
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
// We want EVERYTHING!
return YES;
}
Thanks a lot for you comments and willingness to help.
I ho
Inspired by the answer #tommys mentioned, it turns out that by overriding the hinTest method of a UIView and return the scrollView instead, you actually can detach the swiping of this UIView to the scrollView.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *view = [super hitTest:point withEvent:event];
// Doing this make you detached the swiping to the scrollView
if (view == self)
return [self scrollView];
return view;
}
So this UIView is acting like an extension scroll area of the scrollView, the idea is here. If you make the UIView mask over the scrollView and same size of the window, then swiping anywhere inside the window makes the scrollView scroll.
Here is the example, ExtensionScrollArea
Here's my version:
hit test in container
- (UIView *) hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if ( CGRectContainsPoint( self.frame, point ) && ! self.hidden ) // <-- *
{
if ( ! CGRectContainsPoint( scrollView.frame, point ) )
return scrollView;
}
return [super hitTest:point withEvent:event];
}
(*) This marked line is important if you are moving about or otherwise hiding your view, for instance if you have multiple views, each with their own scrollviews. If you don't have this line, you may be directing all your touches to an off-screen scrollview!
override in scrollview
- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
return YES;
}
(in the hitTest of the container, you can exclude additional frames within the if statement for default behaviour) :)

Resources