tags:

views:

2351

answers:

3

I don't want UIButton or anything like that. I want to subclass UIControl directly and make my own, very special control.

But for some reason, none of any methods I override get ever called. The target-action stuff works, and the targets receive appropriate action messages. However, inside my UIControl subclass I have to catch touch coordinates, and the only way to do so seems to be overriding these guys:

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    NSLog(@"begin touch track");
    return YES;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event {
    NSLog(@"continue touch track");
    return YES;
}

They get never called, even though the UIControl is instantiated with the designates initializer from UIView, initWithFrame:.

All examples I can find alyways use a UIButton or UISlider as base for subclassing, but I want to go closer to UIControl since that's the source for what I want: Fast and undelayed Touch coordinates.

Any idea would be great!

+1  A: 

The simplest approach that I often use is to extend UIControl, but to make use of the inherited addTarget method to receive callbacks for the various events. The key is to listen for both the sender and the event so that you can find out more information about the actual event (such as the location of where the event occurred).

So, just simply subclass UIControl and then in the init method (make sure your initWithCoder is also setup if you are using nibs) , add the following:

[self addTarget:self action:@selector(buttonPressed:forEvent:) forControlEvents:UIControlEventTouchUpInside];

Of course, you can choose any of the standard control events including UIControlEventAllTouchEvents. Notice that the selector will get passed two objects. The first is the control. The second is the info about the event. Here's an example of using the touch event to toggle a button depending on if the user pressed on the left and right.

- (IBAction)buttonPressed:(id)sender forEvent:(UIEvent *)event
{
    if (sender == self.someControl)
    {        
        UITouch* touch = [[event allTouches] anyObject];
        CGPoint p = [touch locationInView:self.someControl];
        if (p.x < self. someControl.frame.size.width / 2.0)
        {
            // left side touch
        }
        else
        {
            // right side touch
        }
    }
}

Granted, this is for pretty simplistic controls and you may reach a point where this will not give you enough functionality, but this has worked for all my purposes for custom controls to this point and is really easy to use since I typically care about the same control events that the UIControl already supports (touch up, drag, etc...)

Code sample of custom control here: Custom UISwitch (note: this does not register with the buttonPressed:forEvent: selector, but you can figure that out from the code above)

Duane Homick
+3  A: 

I think that you forgot to add [super] calls to touchesBegan/touchesEnded/touchesMoved. Methods like

(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event    
(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event

aren't working if you overriding touchesBegan / touchesEnded like this :

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Touches Began");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Touches Moved");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
   NSLog(@"Touches Ended");
}

But! All works fine if methods will be like :

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
   [super touchesBegan:touches withEvent:event];
   NSLog(@"Touches Began");
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
   [super touchesMoved:touches withEvent:event];
     NSLog(@"Touches Moved");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
   [super touchesEnded:touches withEvent:event];
   NSLog(@"Touches Ended");
}
tt.Kilew
I have found that regardless ofa) not implementing touchesBegan and touchesMoved at all; andb) impementing them and calling super.either way, beginTrackingWithTouch and continueTrackingWithTouch don't ever get called.
jhabbott
A: 

I have looked long and hard for a solution to this problem and I don't think there is one. However, on closer inspection of the documentation I think it might be a misunderstanding that begintrackingWithTouch:withEvent: and continueTrackingWithTouch:withEvent: are supposed to be called at all...

UIControl documentation says:

You may want to extend a UIControl subclass for two basic 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 “Note” 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:.

The critical part of this, which is not very clear in my view, is that it says you may want to extend a UIControl subclass - NOT you may want to extend UIControl directly. It's possible that beginTrackingWithTouch:withEvent: and continuetrackingWithTouch:withEvent: are not supposed to get called in response to touches and that UIControl direct subclasses are supposed to call them so that their subclasses can monitor tracking.

So my solution to this is to override touchesBegan:withEvent: and touchesMoved:withEvent: and call them from there as follows. Note that multi-touch is not enabled for this control and that I don't care about touches ended and touches canceled events, but if you want to be complete/thorough you should probably implement those too.

- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    // Get the only touch (multipleTouchEnabled is NO)
    UITouch* touch = [touches anyObject];
    // Track the touch
    [self beginTrackingWithTouch:touch withEvent:event];
}

- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
    // Get the only touch (multipleTouchEnabled is NO)
    UITouch* touch = [touches anyObject];
    // Track the touch
    [self continueTrackingWithTouch:touch withEvent:event];
}

Note that you should also send any UIControlEvent* messages that are relevant for your control using sendActionsForControlEvents: - these may be called from the super methods, I haven't tested it.

jhabbott