UIView hitTest blocks click in child views - ios

I have the following structure of the views
UIView(1)
|--> UIScrollView
|-----------> UIView(2)
|------> UIButton
hitTest in UIView(1) has been overridden with the following:
- (UIView *)hitTest:(CGPoint) point withEvent:(UIEvent *)event
{
/* this view should have only view only: the scroll view */
UIView * vv = [[self subviews] objectAtIndex:0];
if ([vv pointInside:point withEvent:event])
{
UIView *rv = [vv hitTest:point withEvent:event];
return rv;
}
if ([self pointInside:point withEvent:event])
{
return vv;
}
return nil;
}
This is required in order to catch UIScrollView dragging outside of the scroll view itself.
The issue is that when the UIButton is clicked, the event attached to it does not fire. The view returned from the hitTest is UIView(2).
How can I make the UIButton to respond to clicking event?
Edit
After suggestion of Mundi, I exchanged hitTest with the following and it works. Of course, I forgot to call [super hitTest:].
- (UIView *)hitTest:(CGPoint) point withEvent:(UIEvent *)event
{
/* this view should have only view only: the scroll view */
UIView * scrv = [self findScrollView];
UIView *superView = [super hitTest:point withEvent:event];
if (superView == self)
{
return scrv;
}
return superView;
}

How about calling [super hitTest:point withEvent:event] at the appropriate point?

Related

UIButton above two UIView is not pressed

I have two UIViews. On second "UIView" I have one UIButton, but it must be over the both views.
When I click lower middle UIButton - event is fire, but when I click above the middle - nothing works.
How to fix it?
example image
move the button outside of both views and center it vertically
You should create a subclass of UIView for UIView2 and override hitTest like this:
- (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;
}
}
}
return nil;
}

ios How to know which UIView when I click from xib?

Because some reasons, I create a UIView xib file to reuse.
How to know which UIView when I click from xib?
I create a xib file extends UIView(named with XibView).
Then I drag two UIView(leftView,rightView) in the storyboard, and set the custom class "XibView" in XCode inspector window.
When I compile the code, It will get correct result that show two UIViews.
XibView.m file part code below:
-(id) initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if( self )
{
UIView *containerView = [[[UINib nibWithNibName:#"XibView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0];
CGRect newFrame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
containerView.frame = newFrame;
[self addSubview:containerView];
}
return self;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"tap in xib");
......
}
But How can I know which UIView is my click?
// I add some detail description.
In the xib custom class, I will using post the notify to the uiviewcontroller and take some data when user click in xib(touchesBegan is in the xib custom class).
I have two uiview in the storyboard , these uiview will using the xib file.
so I want to know when I click which uiview, I can know which one user click.
// ------------- answer . please refer #Boyi Li answer. ---------
In the XibView.m had add the
[super touchesBegan:touches withEvent:event];
to override the touches begain method.
and add the XibView header file in the viewcontroller.
It can drag the refer to the viewcontroller.
As your said, you could assign different tag for these two views. Inside XibView, you could check self.tag == <tag>.
If you want to get specific view in parent view or controller, use viewWithTag: method. It will check hierarchy whose tag property matches the value in the tag parameter.
Update:
Create IBOutlet Reference in ViewController for left and right Views. In your controller, you have:
#property (nonatomic, weak) IBOutlet XibView *leftView;
#property (nonatomic, weak) IBOutlet XibView *rightView;`
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
CGPoint leftTouchPoint = [touch locationInView:leftView];
CGPoint rightTouchPoint = [touch locationInView:rightView];
if ([self.leftView pointInside:leftTouchPoint withEvent:event]) {
// Do something for leftView
} else if ([self.rightView pointInside:rightTouchPoint withEvent:event]) {
// Do something for Right View
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
UITouch *touch = [touches anyObject];
if ([touch.view isKindOfClass: UIView.class])
{
UIView *view=touch.view;
if (view==YourView1)
{
//start editing
}else
if (view==YourView2)
{
//start editing
}
}
else
{
}
}

Custom Container using UIScrollView with two fingers

I'm creating a container view controller where the child view controllers are shown like a paged scrollView. In this container controller I want to change between pages scrolling horizontally using two fingers. So I used a UIScrollView and I have set it like that:
self.scrollView.showsHorizontalScrollIndicator = NO;
self.scrollView.showsVerticalScrollIndicator = NO;
self.scrollView.delegate = self;
[self.scrollView setContentOffset:[self rectForPage:1].origin];
for (UIGestureRecognizer *gestureRecognizer in self.scrollView.gestureRecognizers) {
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer;
panGR.minimumNumberOfTouches = 2;
}
}
I also have implemented the - (void)scrollViewDidScroll:(UIScrollView *)sender method to avoid the vertical scrolling
- (void)scrollViewDidScroll:(UIScrollView *)sender {
CGFloat pageWidth = self.scrollView.frame.size.width;
int page = floor((self.scrollView.contentOffset.x +pageWidth/2) / pageWidth);
self.pageControl.currentPage = page;
[self.scrollView setContentOffset: CGPointMake(self.scrollView.contentOffset.x, 0)];
}
Now the problem is that the childViewControllers subviews can't receive the touches from the user. I want the single touch to be passed throw the view hierarchy. I have read about subclassing UIScrollView to overwrite the method - (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view but it's only called if the subviews are UIControl. How I can do it?
The purpose is that this ChildViewControllers have UIButtons inside and ScrollViews that should be used with singleTouches
maybe not exactly, but this might help you. Create a scroll view subclass and implement this;
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// UIView will be "transparent" for touch events if we return NO
for (id subview in self.subviews) {
if ([subview isKindOfClass:[YourViewController class]]) {
YourViewController *VC = (YourViewController*) subview;
if (CGRectContainsPoint(YourViewController.frame, point)) {
return YES;
}
}
}
return NO;
}

Dismiss a view when a user taps anywhere outside the view

I have a very complex app with lots of stacked views which contain lots of buttons ,drawing areas and other custom touch handling . I am displaying a draggable helper view which can be on top of all the other views . I need to dismiss this view if the user taps anywhere outside the helper view . I have tried using multiple UIWindows and adding Gesture recognizers to the UIWindow .
A easy is to add a transparent button which bounds is equal the superview's bounds. And the superview insert the transparent button below your helper view.
The transparent button add a click event which can dismiss the helper view and the transparent button it self.
For example :
UIButton *transparencyButton = [[UIButton alloc] initWithFrame:superview.bounds];
transparencyButton.backgroundColor = [UIColor clearColor];
[superview insertSubview:transparencyButton belowSubview:helperView];
[transparencyButton addTarget:self action:#selector(dismissHelper:) forControlEvents:UIControlEventTouchUpInside];
and the dismissHelper: method can do this :
- (void)dismissHelper:(UIButton *)sender
{
[helperView dismiss];
sender.hidden = YES;
// or [sender removeFromSuperview]
}
You can check the view being touched by going through the touches and looking at the .view property of the touch. It reflects the view from where the view actually originates.
Assuming that you keep a reference to your view (either via an IBOutlet or otherwise) to your view called "myView" the below works.
In your .m file. The touchesBegan: function is triggered every time the user touches a finger down. We loop through the touches to see if the originating view of the touch is NOT equal to "myView". This comparison could also be done via checking the class, tag or any other property you use to identify your view. Examples given below.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"touches began");
UITouch *touch = [touches anyObject];
if(touch.view!=myView){
myView.hidden = YES;
}
}
Or in case of using a tag:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"touches began");
UITouch *touch = [touches anyObject];
if(touch.view.tag!=23){
myView.hidden = YES;
}
}
create an hittestintercept view. this can make the view below handle events like before.
#protocol HitTestInterceptViewDelegate <NSObject>
- (BOOL)interceptHitTest:(CGPoint)point withEvent:(UIEvent *)event;
#end
#interface HitTestInterceptView : UIView
#property(nonatomic, weak) id<HitTestInterceptViewDelegate> hitTestInterceptDelegate;
#end
HtiTestInterceptView.m
#import "HitTestInterceptView.h"
#implementation HitTestInterceptView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if ([_hitTestInterceptDelegate interceptHitTest:point withEvent:event] == NO) {
return [super hitTest:point withEvent:event];
}
return nil;
}
#end
then use it in the toast view
- (void)dismissIfTouchOutsideInView:(UIView *)view {
if (_interceptView != nil) return;
_interceptView = [[HitTestInterceptView alloc] initWithFrame:view.bounds];
_interceptView.backgroundColor = [UIColor clearColor];
_interceptView.hitTestInterceptDelegate = self;
[view insertSubview:_interceptView belowSubview:self];
}
- (BOOL)interceptHitTest:(CGPoint)point withEvent:(UIEvent *)event {
dispatch_async(dispatch_get_main_queue(), ^{
// [self dismiss];
});
return YES;
}

Forwarding touch events only to the views being touched

I have a UIWindow with a text field, a button, and a table.
I would like to be able to track all the touches on the screen and then forward them to the element being touched.
I have read about overriding sendEvent in the Apple documentation but I still do not understand:
How to use hitTest to retrieve the element being touched
How to forward touches
This is what I have so far.
- (void) sendEvent:(UIEvent *)event
{
for (UITouch *touch in [event allTouches])
{
/* Get coordinates of touch */
CGPoint point = [touch locationInView:self];
/* Get subview being touched */
/* something like this???
UIView *receiver = [self hitTest:point withEvent:event];
*/
/* Forward touch event to right view */
/* how??? */
}
[super sendEvent:(UIEvent *)event];
}
Thank you.
I am not sure this is the best solution but I am following what has been posted here.
Basically I have a subclass of UIView covering the entire space. Such class contains a reference to ALL the elements that can be touched. (I wish there was a way to avoid that)
This is the code in the header
#interface SubclassUIView : UIView {
UITextField *text;
UITableView *table;
UIButton *button;
UIToolbar *toolbar;
}
And this is the implementation:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
CGPoint tableHit = [table convertPoint:point fromView:self];
CGPoint buttonHit = [button convertPoint:point fromView:self];
CGPoint toolbarHit = [toolbar convertPoint:point fromView:self];
CGPoint messageHit = [text convertPoint:point fromView:self];
if ([table pointInside:tViewHit withEvent:event]) return table;
else if ([button pointInside:buttonHit withEvent:event]) return button;
else if ([toolbar pointInside:toolbarHit withEvent:event]) return toolbar;
else if ([text pointInside:messageHit withEvent:event]) return text;
return [super hitTest:point withEvent:event];
}

Resources