30

I have a UIControl which implements the touches began method like so:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    //More code goes here

This subclass of UIControl is instantiated in a view controller, it is then added as a subview to that view controller. I have a breakpoint at the touches began method of the UIControl, and the method never gets called. I've been doing some reading and it seems that the View Controller has some logic that decides whether to pass on touch events to its subviews. The strange thing is that I have a different subclass of UIControl in the same view controller, and the touch events get passed down to it when the user touches it! Here is the full code:

.h

#import <UIKit/UIKit.h>

@interface CustomSegment : UIView
@property (nonatomic, strong) UIImageView *bgImageView;
@property (nonatomic, assign) NSInteger segments;
@property (nonatomic, strong) NSArray *touchDownImages;
@property (nonatomic, readonly, assign) NSInteger selectedIndex;
@property (nonatomic, weak) id delegate;


- (id)initWithPoint:(CGPoint)point numberOfSegments:(NSInteger)_segments andTouchDownImages:(NSArray *)_touchDownImages;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
@end

.m

#import "CustomSegment.h"

@implementation CustomSegment
@synthesize bgImageView, segments, touchDownImages, selectedIndex, delegate;

- (id)initWithPoint:(CGPoint)point
   numberOfSegments:(NSInteger)_segments
           andTouchDownImages:(NSArray *)_touchDownImages  
{  
    self = [super initWithFrame:CGRectMake(point.x, point.y, [[_touchDownImages     objectAtIndex:0] size].width, [[touchDownImages objectAtIndex:0] size].height)];
if (self)
{
    touchDownImages = _touchDownImages;
    segments = _segments;
    bgImageView = [[UIImageView alloc] initWithImage:[touchDownImages objectAtIndex:0]];
    [self addSubview:bgImageView];
}
return self;
}

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    return YES;  
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //[super touchesBegan:touches withEvent:event];
    UITouch *touch = [touches anyObject];
    float widthOfSegment = [self frame].size.width / segments;
    float bottomPoint = 0;
    float topPoint = widthOfSegment;
    for (int i = 0; i < segments; i++)
    {
        if ([touch locationInView:self].x > bottomPoint && [touch locationInView:self].x < topPoint)
        {
            [bgImageView setImage:[touchDownImages objectAtIndex:i]];
            selectedIndex = i;
            return;
        }
        else
        {
            bottomPoint = topPoint;
            topPoint += topPoint;
        }
    }
}
@end
beryllium
  • 29,669
  • 15
  • 106
  • 125
Kyle Rosenbluth
  • 1,672
  • 4
  • 22
  • 38
  • How do you add the two UIControll sunbviews? Can you show some code? – sch Feb 19 '12 at 14:38
  • Solution is in a duplicate thread here, by 'jhabbot' http://stackoverflow.com/questions/1245248/how-to-correctly-subclass-uicontrol – Tom Susel Apr 20 '13 at 22:42

9 Answers9

119

tl;dr Set all subviews of the UIControl to setUserInteractionEnabled:NO. UIImageViews have it set to NO by default.

Original Post

One thing I found recently is that it helps if the top-most subview of the UIControl has setUserInteractionEnabled:NO. I arrived at this because I had a UIControl subclass with a UIImageView as it's only subview and it worked fine. UIImageView has userInteractionEnabled set to NO by default.

I also had another UIControl with a UIView as it's top most subview (technically the same UIControl in a different state). I believe UIView defaults to userInteractionEnabled == YES, which precluded the events being handled by the UIControl. Settings the UIView's userInteractionEnabled to NO solved my issue.

I don't know if it's the same issue here, but maybe that will help?

-- Edit: When I say topmost view... probably set all subviews of the UIControl to setUserInteractionEnabled:NO

btomw
  • 2,512
  • 2
  • 20
  • 25
3

Xcode 12 and Latter.

  • As mention in the accepted answer, make sure all the subviews of the UIControl view get "User Interaction Enabled" unchecked.
  • Select your UIControl view and switch to the "Connection Inspector" and make sure it has been connected to "Touch Up Inside" event. Sometimes Xcode uses "Value Changed" event so make sure to change to "Touch Up Inside" event
Ofcourse
  • 617
  • 1
  • 7
  • 19
3

Check frames of all parent views. The rule is that if sub-view (or its part) of the view is outside the view bounds, it doesn't receive touch events.

ivanzoid
  • 5,952
  • 2
  • 34
  • 43
  • Thanks ivanzoid, The view controller's(parent of the UIControl) frame is the whole screen, so this shouldn't be an issue. Especially because the UIControl is quite small. THanks though – Kyle Rosenbluth Feb 19 '12 at 15:28
1

It is possible that another view is covering your UIControl, and preventing it from receiving the touch events. Another possibility is that userInteractionEnabled is set to NO somewhere by mistake.

EDIT: I see that you added more code above. Did you verify that your view's frame has a width and height greater than zero? It looks to me like you are calling "size" directly on an object from NSArray (which is 'id'). I don't know how you are doing this without a cast (perhaps the parenthesis didn't come through above?) but if you are somehow pulling it off I wouldn't be surprised if it was an invalid value.

borrrden
  • 33,256
  • 8
  • 74
  • 109
1

First, why do you call [super touchesBegan:touches withEvent:event];? This call is forwarding the event to the next responder. Usually you do it only when you don't want to handle the event. Are you sure you know what are you doing there?

An idea why it doesn't work - can it be that you have a gesture recognizer which handles the event first?

Anyway, do you really need to override touchesBegan? UIControl is made to track the touch events by itself and call your handlers in response. And the UIControl docs say HOW to subclass it.

Subclassing Notes

You may want to extend a UIControl subclass for either of two reasons:

  • To observe or modify the dispatch of action messages to targets for particular events

    To do this, override sendAction:to:forEvent:, evaluate the passed-in selector, target object, or UIControlEvents bit mask, and proceed as required.

  • To provide custom tracking behavior (for example, to change the highlight appearance)

    To do this, override one or all of the following methods: beginTrackingWithTouch:withEvent:, continueTrackingWithTouch:withEvent:, endTrackingWithTouch:withEvent:.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 1
    Thanks Sulthan, I agree that I shouldn't be calling super. There is not gesture recognizer and I tried using begintrackingtouch instead of touches began only to get the same result. What I find really weird about this is the fact that I have a different UIControl that works just fine and I made sure I handled the touches in the same exact way. Yet, it still does not work. – Kyle Rosenbluth Feb 19 '12 at 22:02
  • Another idea - it's inside a `UIScrollView` which cancels the touches for its content? There are so many possibilities. – Sulthan Feb 19 '12 at 22:51
  • To make things simpler I created a blank application with one empty vc and the only thing I put in it was the control. Still same problem. This is very frustrating, but thanks for all your help. – Kyle Rosenbluth Feb 19 '12 at 22:55
  • At this point I might as well, editing original post with added code above – Kyle Rosenbluth Feb 20 '12 at 00:10
  • You should always call 'Super' on touch handling methods in a UIControl subclass, otherwise its touch tracking methods will not fire. – Ash Jul 08 '12 at 10:46
  • @Ash If you handle the action in `touchesBegan`, usually you don't call `super`. That's why `UIControl` is not commonly overriden this way. If you read by answer, you'll understand it. – Sulthan Jul 08 '12 at 10:59
0

Fair enough. Recently re-investigated this (UIControl is very poorly documented) and realised that tracking touches is a replacement for touchesBegan/Ended, not an additional, so sorry about that. I'd reverse the vote but it wouldn't let me :(

Ash
  • 9,064
  • 3
  • 48
  • 59
0

You might need to override intrinsicContentSize inside your UIControl subclass.

override var intrinsicContentSize: CGSize {
    return CGSize(width: 150, height: 100)
}

I don't quite understand how it fixes it or why, but it works. It doesn't even need to be the exact size as your control.

Chad Parker
  • 382
  • 3
  • 6
0

Possibilities:

  • You might have forgot to set the delegate for the UIControl.
  • The other UIControl which receives the touch is obscuring/covering over the UIControl.
Shameem
  • 14,199
  • 13
  • 40
  • 43
  • Thanks, I didn't think I needed a delegate for the UIControl. Maybe I wasn't clear but the touches began is in my subclass of UIControl. I thought about something covering it earlier but this is not the case either, thanks though – Kyle Rosenbluth Feb 19 '12 at 15:16
-2

UIView instances are unable to respond to events so there is no way, up to your current configuration, to trigger your action method ?

Try to change the class of the view to be an instance of the UIControl class !

LAOMUSIC ARTS
  • 642
  • 2
  • 10
  • 15