Detect number of touches - ios

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.

Related

UIControl tracking touch that started on a different UIControl

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.

How can I create a UIWindow that will delay all events by 200 ms?

I'd like to create a custom UIWindow class that will delay all events by 200 ms.
Is it possible? If yes, how?
OK, I created a custom UIApplication class and everything worked fine:
- (void)sendEvent:(UIEvent *)event
{
[super sendEvent:event]; NSLog(#"Viola!");
}
But once I added this extra code to make it delay the events it didn't send any events.
- (void)sendEvent:(UIEvent *)event
{
[self performSelector:#selector(handleEventsAfterDelay:) withObject:event afterDelay:0.500];
}
- (void)handleEventsAfterDelay:(UIEvent *)event
{
[super sendEvent:event]; NSLog(#"Viola!");
}
Also, this doesn't work:
-(void)sendEvent:(UIEvent *)event
{
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.2]];
[super sendEvent:event];
}
Everything that delays the events doesn't fire at the end. I'm standing frustrated staring in a blank black screen as the app won't even start.
EDIT: I found a way around this problem. Yet, to this day, there's no officially plausible and safe way of doing this but I guess it's just not significant at all.
First, I don't think it's a good idea to delay device event. I would try to work around it some how. Also, I'm not sure if Apple will aprove delayed events.
Another reason for me not to try to delay events, is because I'm no guru. I have no idea how the app will start to behave.
That said, as SomeGuy suggested, you could subclass UIApplication and override all event handling methods relevant to your app. Take a look at the UIApplication class's reference.
I've never delayed native events, but I did work with some event manipulation bafore. I remember that back then, overriding sendEvent: was enough for what I was doing.
You could override sendEvent: as follows:
- (void)sendEvent:(UIEvent *)event
{
[NSThread sleepForTimeInterval:.2];
[super sendEvent:event];
}
This will delay all event 200 ms. This will however block your main thread.
I've tried to use a call to dispatch_after(3) but it didn't work.
If you only want to delay certain event types, use the UIEvent's type and subtype properties. Take a look at the UIEvent class's reference.
Again, I never had to delay, nor do I know what you're exactly trying to accomplish, but I would suggest to find an alternative to delaying the device's event.
Also, don't forget you have to tell Cocoa Touch to use your custom UIApplication. Go to the main.m file and add a principal class name to the UIApplicationMain call:
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, NSStringFromClass([YourApplication class]), NSStringFromClass([YourAppDelegate class]));
}
}
As a reference, this was my overridden method:
- (void)sendEvent:(UIEvent *)event
{
[super sendEvent:event];
// Was it a touch?
if (event.type == UIEventTypeTouches) {
// Get touch phase.
NSSet *allTouches = [event allTouches];
UITouchPhase phase = [((UITouch *)[allTouches anyObject]) phase];
// Check what to do.
switch (phase) {
case UITouchPhaseBegan:
// Reset counter.
[self.counter resetCount];
break;
case UITouchPhaseEnded:
case UITouchPhaseCancelled:
// Start counter.
[self.counter startCount];
break;
default:
break;
}
}
}
This was used to auto-sign out users from the app after some inactive time (it was a kiosk app).
Unfortunately, it's the best I can do. Maybe someone with more experience can give their input.
I hope this help.
I suppose the problem you have is caused by the UIEvent timestamp. I suppose that events will expire if it is not delivered within a short interval.
You could try adding setTimestamp: by a category and modify it accordingly but the change has to be very exact.

UITableViewCell's touchesEnded not called when displaying ViewController on top

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.

self.nextResponder vs super for touchesMoved

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.

Why is hitTest:withEvent: called three times for each touch?

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

Resources