views:

1526

answers:

2

I have a UIView subclass (CustomView for purposes of this question) that has its own handling of all touches events (Began, Moved, Ended, Cancelled). I also have a UIButton that is a sibling of CustomView that overlaps it.

For example, my view hierarchy looks like this:

  • UIView (Controller's view)
    • CustomView with frame (0, 0, 300, 300)
    • UIButton with frame (0, 0, 100, 50)

I would like the CustomView to capture touch events once the user has dragged out of the UIButton. Through debug logging, it appears that the UIControlEventTouchDragExit event is the one to intercept (though it doesn't fire until the touch is around 100 pixels away from the button, but that is not important to this question).

Ideally, when the drag exit event is fired, the button would stop receiving touch move events, the CustomView would get a touchesBegan event (even if I need to fake one myself somehow), and all future touch events (touchesMoved, etc) would be sent to the CustomView.

I have tried the following, and it doesn't have any effect:

-(void)btnTouchDragExit:(id)sender
{
    UIButton * btn = sender;
    [btn resignFirstResponder];
    [customView becomeFirstResponder];
}

I think the underlying UITouch object (which is consistent across touch events) is not being retargeted to point at the CustomView. How can I achieve the desired effect?

+2  A: 

Just use hitTest:withEvent: to figure out what view "should" be receiving the event, then forward on the event to the appropriate view. You might have to do a little bit of dancing around to intercept events before they go to the button, but this is what hitTest:withEvent: was made for.

Dave DeLong
By subclassing UIButton and overriding touchesMoved, I've been able to do this (walk the superview tree calling hitTest until it finds one that has the touch point in it).. However, I have to manually forward (from that point on) the touch events to that view. Isn't there a system call so that Cocoa/UIWindow/UIApplication sends the touches to that new view? In Win32, it's ReleaseCapture/SetCapture(newWnd)..
Jason
Call hitTest:withEvent: on the superview, and it'll tell you which of its subviews would be the first responder to the event. Once you figure that out, just call `touches(Began|Moved|Ended):withEvent:`
Dave DeLong
That works, but it's what I was trying to avoid, namely because this requires subclassing, and by doing that I can only have this behavior on Custom buttons (not Rounded Rect or any others). I've also seen odd things happen when forwarding touch events to things like UIScrollViews and UIWebViews, so it may not be a completely generalized solution. I'll leave the question open for a while to see if there are any other suggestions.
Jason
+1  A: 

you have to override canBecomeFirstResponder to return YES otherwise becomeFirstResponder does nothing. This is already done on UIControls, but not on UIViews

Ed Marty
Adding that to my UIView subclass did not help. Subclassing UIButton and forcibly redirecting touch events was still needed. From a conceptual standpoint, it seems like in order to get this to work, I wouldn't be calling `[customView becomeFirstResponder]` anyway, but should be calling something like `[UIApplication assignFirstResponder:customView]` or some other static/global function.
Jason