I have a subclass of UIView in which I've overridden hitTest:withEvent: as follows:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
NSLog(#"Event = %#", event);
return self;
}
For each touch in the view, I am seeing three calls to hitTest:withEvent:. These three calls are made before touch up. The output is as follows:
2011-07-01 09:20:58.553 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4297.16 touches: {(
)}
2011-07-01 09:20:58.553 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4297.16 touches: {(
)}
2011-07-01 09:20:58.554 AppName[930:207] Event = <UITouchesEvent: 0x6a08360> timestamp: 4304.54 touches: {(
)}
Based on the timestamps and addresses, it appears as if a single UITouchesEvent object is being used and its timestamp isn't properly set until the third call. Can anyone explain why hitTest:withEvent: gets called three times like this? I'm not looking for a workaround. I just want to understand what's going on.
I had the same problem and was able to solve it with this code. Even though pointInside and hitTest get called 3 times, touchesBegan (or touchesEnded) of the UIView that was touched only gets called once.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if (event.type == UIEventTypeTouches)
NSLog(#"%#", self);
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if ([self pointInside:point withEvent:event])
return self;
return [super hitTest:point withEvent:event];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (CGRectContainsPoint([self bounds], point))
{
if (event.type == UIEventTypeTouches)
{
return YES;
}
}
return NO;
}
Do you have more than one subview?
From the docs:
This method traverses the view hierarchy by sending the pointInside:withEvent: message to each subview to determine which subview should receive a touch event. If pointInside:withEvent: returns YES, then the subview’s hierarchy is traversed; otherwise, its branch of the view hierarchy is ignored. You rarely need to call this method yourself, but you might override it to hide touch events from subviews.
Yes, it’s normal. The system may tweak the point being hit tested between the calls. Since hitTest should be a pure function with no side-effects, this should be fine.
Refer to Apple Mailing List: Re: -hitTest:withEvent: called twice?
You should check to see if the subtype and type property are all the same. those 3 events do make sense since there's event that needs to be triggered in order for the OS to understand the nature of the touch event.
For example, swipe, pinch and tap all start with the same touch event. My guess is that the first two are fired 1 to register the tap event and the second to test for tap event to "move". the second is called not long afterwards probably to either cancel the pinching/zooming/whatever.
Bottom line, the documentations talks about 3 different type of events: touch, motion and remote events. UIEvent Class Reference
Related
I have a segmented control,with 5 segments..I want some event to be fired when a touch down happens on any of the segment and i mean tap and hold,without lifting the finger.
And when the user lifts his finger,it should reset
I have tried using touchesBegan and touchesEnded but i don't get the current selectedIndex in touchesBegan,here's my code
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
long oldValue = self.selectedSegmentIndex;
[super touchesBegan:touches withEvent:event];
if (oldValue == self.selectedSegmentIndex )
[self sendActionsForControlEvents:UIControlEventValueChanged];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesEnded");
NSLog(#"selectedIndex%lu",self.selectedSegmentIndex);
[super touchesEnded:touches withEvent:event];
self.selectedSegmentIndex = -1;
}
Is there any way to achieve a touch down event effect for a segmentedControl,or any other alternative?
You can use touchDown: IBAction selector.
You can try to override:
- (void) setSelectedSegmentIndex:(NSInteger)toValue
Quite frankly, I do not think it is possible to do with UISegmentedControl. You do not get selected segment index until touchesEnded:withEvent: is called.
If you want to get in touchesBegan:withEvent:, my advise would be to write your own custom view that look like UISegmentedControl and gives you call back on touchesBegan:withEvent:.
As suggested by kientux above, adding transparent views or buttons on top of each segment in UISegmentedControl could be another potential solution to your problem but it would be very very tricky!
Good luck!
I have a problem, In my app I have a Label and when I touch on it a method is fired, I implemented with -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event, the problem is that when I touch, the method is fired (everything is working) but I want when I touch once again another method to fire. How to do that???
You're talking about changing state after a touch. Most people would use just a boolean, or an integer enum to keep track of what state they're in, and fork the code accordingly. However, if you need to discern between a tap, a double tap, a triple tap, a pan, or press and hold, then you should look at gesture recognizers, and in particular, watch the WWDC video where they were introduced (2011?).
I want when I touch once again another method to fire.
-touchesEnded:withEvent: and the other touch handling methods will always fire in response to touch events -- you can't change that. What you can do is look at the touch in your implementations of those methods, figure out which method you want to handle that particular event, and then call that:
- (void)touchesEnded:(NSSet *)touches
withEvent:(UIEvent *)event
{
switch ([touches count]) {
case 1: [self oneTouch:touches forEvent:event];
break;
case 2: [self twoTouches:touches forEvent:event];
break;
default: [self moreThanTwoTouches:touches forEvent:event];
break;
}
}
Obviously, you don't have to decide which method to call based on the number of touches -- that's just an example. You can use any information at your disposal, such as the state of the object handling the touch event.
I am making a custom iOS keyboard and have a UIControl subclass to represent my button. I am trying to get the same behaviour as the normal iOS keyboard:
User begins touch on one button
User drags over other buttons (need to detect this so they can highlight/dehighlight accordingly)
Register the actual keyboard "press" when the user lifts their finger; that is, the touch ends
I am testing using the touch tracking methods like this:
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super beginTrackingWithTouch:touch withEvent:event];
NSLog(#"Begin for %#", [self label]);
return YES;
}
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super continueTrackingWithTouch:touch withEvent:event];
NSLog(#"Continue for %#", [self label]);
return YES;
}
- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
[super endTrackingWithTouch:touch withEvent:event];
NSLog(#"End for %#", [self label]);
}
These methods are all called, except they are only ever called on the UIControl where the touch began.
What is the best way to recognise touches coming and going across all my buttons? Do I have to do it all via the parent view?
I'll select a better answer if offered... but in case anybody finds this by search, I've managed to get what I need this way:
Set userInteractionEnabled to NO for the button class (UIControl subclass)
Don't override any touch methods in the button class
Implement touchesBegan:withEvent:, touchesMoved:withEvent: and touchesEnded:withEvent: in the view controller
On each event, extract the location from the UITouch object
Iterate over all of the button subviews and find the one containing the touch location:
- (MyButton *)buttonForTouch:(UITouch *)touch
{
CGPoint windowLocation = [touch locationInView:keyboardView];
for (MyButton *button in buttons) {
if (CGRectContainsPoint([button frame], windowLocation)) {
return button;
}
}
return nil;
}
Having determined which button the user is interacting with, make the view controller send messages to the relevant buttons to adjust their appearance
If appropriate, keep a reference to the UITouch instance in touchesBegan:withEvent: so you can be sure that you're tracking the same one in the other methods
I think that you should have a single big UIControl which has different subviews (like UIButton) but tracks touches by itself like you did already but finds out which subview to highlight depending on the touch position.
I've got a bit of a construction here; I extended a UITableViewCell and am displaying a UIViewController modally from the UITableViewController class when the touchesBegin hits in the UITableViewCell. When the touchesEnded hits, I remove this UIViewController again.
So I'm showing something when you touch the cell, and when you release the screen I hide it again. All good, but it doesn't work if I quickly press and release on the UITableViewCell; in this case touchesEnded isn't called. If I hold it for a second or longer, the system works fine. 'touchesCancelled' is not called either.
Any thoughts on this? Since the focus is on the UITableViewCell, the Touch methods of the newly displayed UIViewController or the existing UITableViewController won't hit either (until I release and press again, but that defeats the purpose).
The relevant code:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
amDisplayingCapture = YES;
[self.showCaptureDelegate displayCapture:self.capture];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
if( amDisplayingCapture )
{
[self.showCaptureDelegate endDisplayCapture];
amDisplayingCapture = NO;
}
}
(didSelectRowAtIndexPath of the UITableView isn't called either once I present the new UIViewController, so can't use that)
From the UIResponder header file:
Your responder will receive either touchesEnded:withEvent: or touchesCancelled:withEvent: for each touch it is handling (those touches it received in touchesBegan:withEvent:). You must handle cancelled touches to ensure correct behavior in your application. Failure to do so is very likely to lead to incorrect behavior or crashes.
Summary: you should also implement touchesCancelled:withEvent:.
In the event someone has a similar situation, I resolved this by adding the new viewcontroller's view as a subview rather than presenting the entire viewcontroller modally. That seemed to allow the touchesEnded to be called.
Here's the setup.
I have a custom UIScrollView as a subview of PrimeView.
The custom UIScrollView can be regarded as overlaid scrollview and it gets all touch events initially.
Now, when the scroll view isn't dragging, I want it to pass touch events to other responders.
Below is my code at present, but I'm not sure about the difference between self.nextResponder and super here.
I don't get why touchesBegan is passed to the superview correctly, but touchesMoved isn't passed to the superview.
-(void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
if (!self.dragging)
{
[self.nextResponder touchesBegan: touches withEvent:event];
}
[super touchesBegan: touches withEvent: event];
}
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.nextResponder touchesBegan: touches withEvent:event]; //
[super touchesMoved:touches withEvent:event];
}
Apple's stance is that you should never do something like this:
[self.nextResponder touchesBegan: touches withEvent:event];
If you do this, you're going outside UIKit's own forwarding of messages up the responder chain, and the result is undefined.
Also, in your touchesMoved:withEvent:, you're sending touchesBegan:withEvent: to nextResponder, which seems suspicious.
Also, in your touchesMoved:withEvent:, you're passing on the event twice, which seems like a bad idea.
If you want to handle drags conditionally, your best bet is to use a UIPanGestureRecognizer. If the recognizer doesn't accept the event, it will be forwarded up the responder chain normally.
If you are a registered (paid) iOS developer, you should have access to the WWDC 2012 videos. I strongly recommend you watch the “Enhancing User Experience with Scroll Views” video. I won't say more about it because its contents are still under NDA.