Http://www.cocoachina.com/ios/20150611/12082.html
When I recently encountered a problem with UIButton, I wanted to get a callback when my finger dragged the button and left the button boundary, so I listened to the Uicontroleventtouchdragexit event, as described in the document:
1 |
An event where a finger is dragged from within a control to outside its bounds. |
The event was exactly what I needed, but in the end it was found that when the finger left the button boundary, the event did not trigger, but instead received the callback when it was nearly 70 pixels away from the button.
Directory
To better illustrate the problem, I made an example, see. The expected behavior is that when the finger leaves the button boundary, the button's contents are changed to leave and entered. In addition, the position of the finger gives the pixel difference from the top of the button.
However, when the finger leaves the button boundary, the button's content does not change. When the finger is 70 pixels from the top of the button, it becomes left. As you can see, the Uicontroleventtouchdragexit event is not triggered immediately when you leave the button boundary, but only 70 pixels from the top of the button.
Here I just demonstrated that the finger upward movement, in fact, in the other three directions, there will be the same effect, interested students can try some of their own.
And not just the Uicontroleventtouchdragexit event, all the border-related events have this problem:
Uicontroleventtouchdraginside
Uicontroleventtouchdragoutside
Uicontroleventtouchdragenter
Uicontroleventtouchdragexit
UIControlEventTouchUpInside
Uicontroleventtouchupoutside
I do not know why Apple has to set this, has not found the relevant information. Speculation may be that the Apple feels the finger is thicker, and the screen contact area is relatively large, positioning does not need to be so precise, so set up a large external area.
But in many cases, if we need more precise control, the 70-pixel expansion is not going to happen. So is there any way to jump out of the button's palm faster?
The answer from StackOverflow
After some finding, an answer was found above StackOverflow, which is covered by the Uicontrol Continuetrackingwithtouch:withevent method, since UIButton is derived from Uicontrol, This method is therefore inherited. Let's take a look at its statement:
123456789101112 |
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
/*
Description
Sent continuously to the control as it tracks a touch related to the given event within the control’s bounds.
Parameters
touch
A UITouch object that represents a touch on the receiving control during tracking.
event
An event object encapsulating the information specific to the user event
Returns
YES if touch tracking should continue; otherwise NO.
*/
|
This method determines whether to keep track of current touch events. This is where you determine if you are in the range of the button, and then send the corresponding event. The corresponding code is:
1234567891011121314151617181920 |
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
CGFloat boundsExtension = 25.0f;
CGRect outerBounds = CGRectInset(self.bounds, -1 * boundsExtension, -1 * boundsExtension);
BOOL touchOutside = !CGRectContainsPoint(outerBounds, [touch locationInView:self]);
if
(touchOutside) {
BOOL previousTouchInside = CGRectContainsPoint(outerBounds, [touch previousLocationInView:self]);
if
(previousTouchInside) {
NSLog(@
"Sending UIControlEventTouchDragExit"
);
[self sendActionsForControlEvents:UIControlEventTouchDragExit];
}
else
{
NSLog(@
"Sending UIControlEventTouchDragOutside"
);
[self sendActionsForControlEvents:UIControlEventTouchDragOutside];
}
}
return
[
super
continueTrackingWithTouch:touch withEvent:event];
}
|
In the code, Boundsextension is set to 25, which corresponds to the 70, the button "palm" in the previous discussion. Of course we can set it to any other value.
Test results
This method looks very good and is adopted as the correct answer to the original question. But after trying, I found that it had two serious problems:
Uicontroleventtouchdragexit will respond two times, respectively:
Triggered when a finger leaves the button boundary 25 pixels
The second time is still 70 pixels when triggered, which is the default behavior of UIButton
The second problem is the callback function in the event:
-(void) Callback: (UIButton *) sender withevent: (Uievent *) event
, the position computed by the Uievent parameter is always (0, 0) and it is not initialized correctly
It is understandable to think carefully that the corresponding event is triggered after we have judged in the overridden function, but this does not cancel the event that the original Uicontrol was supposed to trigger, which resulted in two responses, and in our handling, only the event was triggered, This does not involve the initialization of uievent, so the final position is definitely wrong.
For a recurring response question, one might guess whether the last line above would have an effect on calling the parent class method:
1 |
1 return [ super continueTrackingWithTouch:touch withEvent:event]; |
I've also tried to return yes directly at the end, and the problem is still there, and that's not the reason.
A different idea.
Because of the above two problems, this answer is not advisable. Is there any other way?
Let's take a closer look at the previous method and use the first half of the code to easily determine if the current position is within the button. So can we not process the underlying, but in the upper-level callback function to judge? Based on this idea, I have made this attempt:
Register callback
123 |
// to get the drag event [btn addTarget:self action:@selector(btnDragged:withEvent:) forControlEvents:UIControlEventTouchDragInside]; [btn addTarget:self action:@selector(btnDragged:withEvent:) forControlEvents:UIControlEventTouchDragOutside]; |
The first step is still to register the callback function, but notice that the two events here are registered with the same callback function Btndragged:withevent:. And did not register Uicontroleventtouchdragexit and Uicontroleventtouchdragenter, Instead of Uicontroleventtouchdraginside and uicontroleventtouchdragoutside, why? Please look down.
callback function
The callback function uses the method of judgement in the previous answer, which can be used to determine whether the button is inside the buttons according to the current and previous positions. You can then determine which event to belong to at this point, as shown in the following note. At this point, we can do the corresponding processing in each branch.
123456789101112131415161718192021 |
- (void)btnDragged:(UIButton *)sender withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGFloat boundsExtension = 25.0f;
CGRect outerBounds = CGRectInset(sender.bounds, -1 * boundsExtension, -1 * boundsExtension);
BOOL touchOutside = !CGRectContainsPoint(outerBounds, [touch locationInView:sender]);
if
(touchOutside) {
BOOL previewTouchInside = CGRectContainsPoint(outerBounds, [touch previousLocationInView:sender]);
if
(previewTouchInside) {
// UIControlEventTouchDragExit
}
else
{
// UIControlEventTouchDragOutside
}
}
else
{
BOOL previewTouchOutside = !CGRectContainsPoint(outerBounds, [touch previousLocationInView:sender]);
if
(previewTouchOutside) {
// UIControlEventTouchDragEnter
}
else {
// UIControlEventTouchDragInside
}
}
}
|
Note that here we only register two events, but achieve the equivalent of four events effect. The final effect is as follows, where the boundsextension is still set to 25, and of course you can set it to whatever value you want.
Handling Touchup Events
At the beginning of this article we mentioned that all events that need to be judged within a button have this problem, such as UIControlEventTouchUpInside and Uicontroleventtouchupoutside, Of course, the same approach can be used to deal with:
Register the same callback function for two events first:
123 |
// to get the touch up event [btn addTarget:self action:@selector(btnTouchUp:withEvent:) forControlEvents:UIControlEventTouchUpInside]; [btn addTarget:self action:@selector(btnTouchUp:withEvent:) forControlEvents:UIControlEventTouchUpOutside]; |
Then handle the callback function:
1234567891011 |
- (void)btnTouchUp:(UIButton *)sender withEvent:(UIEvent *)event {
UITouch *touch = [[event allTouches] anyObject];
CGFloat boundsExtension = 25.0f;
CGRect outerBounds = CGRectInset(sender.bounds, -1 * boundsExtension, -1 * boundsExtension);
BOOL touchOutside = !CGRectContainsPoint(outerBounds, [touch locationInView:sender]);
if
(touchOutside) {
// UIControlEventTouchUpOutside
}
else
{
// UIControlEventTouchUpInside
}
}
|
End
Because the AddTarget:action:forControlEvents method of UIButton is inherited from Uicontrol, the above approach applies equally to all Uicontrol subclasses, such as Uiswitch,uislider and so on.
I also added to the original question of StackOverflow. If you have a better idea, or if you know why Apple is dealing with it, please leave a message or answer it on the original question.
(End of full text)
Feihu
2015.05.21 at Shenzhen
This article is from the submission of South Gardenia Cold (Pinterest), translated from Apple Swift blog, original: Memory safety:ensuring Values is Defined before use
You are welcome to submit through the "Submit a material" channel or [email protected]
Jump out of the palm--how to trigger UIButton boundary events immediately