I am developing an enterprise App that injects touches. The method I use is the same with the one seen in the KIF framework (see https://github.com/square/KIF/tree/master/Additions).
There is at least one case, where this method doesn't work:
When the user taps on the device, the injection doesn't work anymore for scrollviews. They don't scroll anymore, when I inject moving touch events. When the user himself scrolls at the device, the injection does work again. It seems like a state is not set with KIFs method, but it is set by iOS when a real touch occurs.
Does anyone know more about it ? Maybe has an idea wich state it could be ? I tried already several private API calls that sounded right, but none worked.
Here is some code, describing the method I used:
Touch down:
UITouch *touch = [[[UITouch alloc] initAtLocation:point inWindow:hitView.window] autorelease];
[touch setPhase:UITouchPhaseBegan];
UIEvent *newEvent = [hitView _eventWithTouch:touch];
[_activeTouchEvents addObject:touch];
[[UIApplication sharedApplication] sendEvent:newEvent];
Touch move
UITouch* touch = _activeTouchEvent;
[touch setPhase:UITouchPhaseMoved];
[touch setLocationInWindow:point];
UIEvent *newEvent = [hitView _eventWithTouch:touch];
[[UIApplication sharedApplication] sendEvent:newEvent];
Touch up
UITouch* touch = _activeTouchEvent;
[touch setPhase:UITouchPhaseEnded];
[touch setLocationInWindow:point];
UIEvent *newEvent = [hitView _eventWithTouch:touch];
[[UIApplication sharedApplication] sendEvent:newEvent];
[_activeTouchEvent release];
_activeTouchEvent = nil;
UIView (extension)
- (UIEvent *)_eventWithTouch:(UITouch *)touch;
{
UIEvent *event = [[UIApplication sharedApplication] performSelector:#selector(_touchesEvent)];
CGPoint location = [touch locationInView:touch.window];
KIFEventProxy *eventProxy = [[KIFEventProxy alloc] init];
eventProxy->x1 = location.x;
eventProxy->y1 = location.y;
eventProxy->x2 = location.x;
eventProxy->y2 = location.y;
eventProxy->x3 = location.x;
eventProxy->y3 = location.y;
eventProxy->sizeX = 1.0;
eventProxy->sizeY = 1.0;
eventProxy->flags = ([touch phase] == UITouchPhaseEnded) ? 0x1010180 : 0x3010180;
eventProxy->type = 3001;
NSSet *allTouches = [event allTouches];
[event _clearTouches];
[allTouches makeObjectsPerformSelector:#selector(autorelease)];
[event _setGSEvent:(struct __GSEvent *)eventProxy];
[event _addTouch:touch forDelayedDelivery:NO];
[eventProxy release];
return event;
}
UITouch (extension)
- (id)initAtLocation:(CGPoint)point inWindow:(UIWindow *)window
{
self = [super init];
if (self == nil) {
return nil;
}
// Create a fake tap touch
_tapCount = 1;
_locationInWindow = point;
_previousLocationInWindow = _locationInWindow;
UIView *hitTestView = [window hitTest:_locationInWindow withEvent:nil];
_window = [window retain];
_view = [hitTestView retain];
_gestureView = [hitTestView retain];
_gestureRecognizers = [[NSMutableArray arrayWithArray:[hitTestView gestureRecognizers]] retain];
_phase = UITouchPhaseBegan;
_touchFlags._firstTouchForView = 1;
_touchFlags._isTap = 1;
_timestamp = [[NSProcessInfo processInfo] systemUptime];
return self;
}
- (void)setPhase:(UITouchPhase)phase;
{
_phase = phase;
_timestamp = [[NSProcessInfo processInfo] systemUptime];
}
Edit 1: Codeblock of "Touch down" was not right
Related
I am working with multitouch while writing, So basically what I am doing is, I am writing with hand support, because typically, its how user rights, I followed this link How to ignore certain UITouch Points in multitouch sequence
Everything is working fine, but their is some problem with undo when I write with my hand touching the screen, otherwise it works fine.
Below is my code
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* topmostTouch = self.trackingTouch;
for (UITouch *touch in touches)
{
ctr = 0;
touchStartPoint1 = [touch locationInView:self];
[m_undoArray removeAllObjects];
[m_redoArray removeAllObjects];
[m_parentRedoArray removeAllObjects];
if(!topmostTouch || [topmostTouch locationInView:self].y > touchStartPoint1.y)
{
topmostTouch = touch;
pts[0] = touchStartPoint1;
}
}
if (self.trackingTouch != nil && self.trackingTouch != topmostTouch) // ![touches containsObject:self.trackingTouch])
{
[self discardDrawing];
}
self.trackingTouch = topmostTouch;
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
if(self.trackingTouch== nil)
{
return;
}
CGPoint p = [self.trackingTouch locationInView:self];
ctr++;
pts[ctr] = p;
if (ctr == 4)
{
pts[3] = midPoint(pts[2], pts[4]);
self.currentPath = [[DrawingPath alloc] init];
[self.currentPath setPathColor:self.lineColor];
self.currentPath.pathWidth = [NSString stringWithFormat:#"%f",self.lineWidth];
[self.currentPath.path moveToPoint:pts[0]];
[self.currentPath.path addCurveToPoint:pts[3] controlPoint1:pts[1] controlPoint2:pts[2]];
CGPathRef cgPath = self.currentPath.path.CGPath;
mutablePath = CGPathCreateMutableCopy(cgPath);
[m_undoArray addObject:self.currentPath];
[self setNeedsDisplay];
pts[0] = pts[3];
pts[1] = pts[4];
ctr = 1;
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
if(touch == self.trackingTouch)
{
[m_parentUndoArray addObject:[NSArray arrayWithArray:m_undoArray]];
}
}
}
-(void)undoButtonClicked
{
NSMutableArray *undoArray = [m_parentUndoArray lastObject];
NSLog(#"%#",undoArray);
[m_parentUndoArray removeLastObject];
[m_parentRedoArray addObject:undoArray];
m_drawStep = UNDO;
[self setNeedsDisplay];
}
- (void)drawRect
{
I have different cases here, I am showing Of Undo
for(int i = 0; i<[m_parentUndoArray count];i++)
{
NSMutableArray *undoArray = [m_parentUndoArray objectAtIndex:i];
NSLog(#"%#",undoArray);
for(int i =0; i<[undoArray count];i++)
{
DrawingPath *drawPath = [undoArray objectAtIndex:i];
GPathRef path = drawPath.path.CGPath;
mutablePath = CGPathCreateMutableCopy(path);
//Draw into CgLayer
}
}
}
Here is the image to understand my problem better, I first wrote this
After clicking on Undo once,you can see above that, some other part has been undone, instead of the last part. So I need you help in this regard.
m_redoArray appears to be the big daddy, the one you draw from. I don't understand why you empty this out out in 'touchesBegan...', surely one of these arrays must carry through touchesBegan unaltered, or you'll be dropping stuff from the start of your drawing all the way through, no?
It appears to me that this is how you dropped the 'Hell' in your example here..
Well in my case i have used bezierPath to draw on touch and have successfully implemented undo functionality. Here is the code :
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor clearColor];
myPath = [[UIBezierPath alloc] init];
myPath.lineCapStyle = kCGLineCapRound;
myPath.miterLimit = 0;
bSize=5;
myPath.lineWidth = bSize;
brushPattern = [UIColor whiteColor];
// Arrays for saving undo-redo steps in arrays
pathArray = [[NSMutableArray alloc] init];
bufferArray = [[NSMutableArray alloc] init];
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
[brushPattern setStroke];
for (id path in pathArray){
if ([path isKindOfClass:[UIBezierPath class]]) {
UIBezierPath *_path=(UIBezierPath *)path;
[_path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
}
}
}
#pragma mark - Touch Methods
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *mytouch = [[touches allObjects] objectAtIndex:0];
myPath = [[UIBezierPath alloc] init];
myPath.lineWidth = bSize;
[myPath moveToPoint:[mytouch locationInView:self]];
[pathArray addObject:myPath];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[myPath addLineToPoint:[[touches anyObject] locationInView:self]];
[self setNeedsDisplay];
}
#pragma mark - Undo Method
-(void)undoButtonClicked
{
if([pathArray count]>0)
{
UIBezierPath *_path = [pathArray lastObject];
[bufferArray addObject:_path];
[pathArray removeLastObject];
[self setNeedsDisplay];
}
}
-(void)setBrushSize: (CGFloat)brushSize
{
bSize=brushSize;
}
-(void)redoButtonClicked
{
if([bufferArray count]>0){
UIBezierPath *_path = [bufferArray lastObject];
[pathArray addObject:_path];
[bufferArray removeLastObject];
[self setNeedsDisplay];
}
}
Hope it will help you.
I have a UIView on where i add balls with a ID. When i touch the ball with the ID 1 i draw a dashed line follows my finger.
The problem if when i move the finger to the other balls, i don't know how to detect these balls and check the ID they have. The what i need to happen is when i touch the next ball with the ID 2, the line finish draw and stay from ball 1 to ball 2, and create new one from 2 to finger, next.. 3 etc...
The code:
#import "DrawView.h"
#implementation DrawView
- (id)init
{
self = [super initWithFrame:CGRectMake(0, 0, 1024, 768)];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.opaque = YES;
checkPointCircle = [[CheckPointCircle alloc] initWithPosX:315 posY:138 andNumber:1];
checkPointCircle.userInteractionEnabled = YES;
[self addSubview:checkPointCircle];
checkPointCircle = [[CheckPointCircle alloc] initWithPosX:706 posY:138 andNumber:2];
checkPointCircle.userInteractionEnabled = YES;
[self addSubview:checkPointCircle];
checkPointCircle = [[CheckPointCircle alloc] initWithPosX:315 posY:526 andNumber:3];
checkPointCircle.userInteractionEnabled = YES;
[self addSubview:checkPointCircle];
checkPointCircle = [[CheckPointCircle alloc] initWithPosX:706 posY:526 andNumber:4];
checkPointCircle.userInteractionEnabled = YES;
[self addSubview:checkPointCircle];
}
return self;
}
- (Line *)drawLine {
return drawLine;
}
- (void)setDrawLine:(Line *)line
{
drawLine = line;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CheckPointCircle * checkTouch = (CheckPointCircle *)touch.view;
if (touch.view.class == checkPointCircle.class) {
if ([checkTouch getObjectID] == 1) {
startTouchPoint = CGPointMake([checkTouch center].x, [checkTouch center].y);
endTouchPoint = [touch locationInView:self];
}
}
self.drawLine = [[Line alloc] initWithPoint:[touch locationInView:self]];
[self setNeedsDisplay];
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Cancelado");
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
UITouch *secondaryTouch = (UITouch *)[[[event touchesForView:checkPointCircle] allObjects] objectAtIndex: 0];
NSLog(#"Que toco: %# ", secondaryTouch.view);
if (touch.view.class == checkPointCircle.class) {
CheckPointCircle * checkTouch = (CheckPointCircle *)touch.view;
if ([checkTouch getObjectID] == 1) {
endTouchPoint = [touch locationInView:self];
}
}
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CheckPointCircle * checkTouch = (CheckPointCircle *)touch.view;
if (touch.view.class == checkPointCircle.class) {
if ([checkTouch getObjectID] == 1) {
endTouchPoint = [touch locationInView:self];
}
}
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[drawLine drawLineFrom:startTouchPoint to:endTouchPoint];
}
#end
How to detect the other balls to get the ID.
Can anybody help me?
Thanks!
The Apple documentation of class UIView (https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/UIView/UIView.html#//apple_ref/occ/instm/UIView/hitTest:withEvent:) lists two methods which will determine which view contains a hit/point:
– hitTest:withEvent:
– pointInside:withEvent:
Probably hitTest will help most: Its description reads "Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point.", so it even converts coordinates as needed.
If you check [self hitTest: [touch.locationInView self] withEvent: event] (or similar, no code completion here ;-)= in your touchesMoved: method, you should be able to detect when the finger is over any of the other circles.
Hope this helps, nobi
In my app i want to create a custom gesture recognizer that recognizes a long press followed by a swipe.
I need to measure if the length of the long press is more than 1 second. If it is, then call a function and wait for the swipe action to begin.
My problem is that I the only way i know now how long the press was is by extracting the timestamp of touchesBegan from touchesMoved. However i want to know that elapsed time before touchesMoved gets called.
Is there a way to know the length of the tap before the touchesMoved is called?
Thanks in advance!
you can use may code, it can use, but maybe you should deal with some details
in the .h file you should add these ivar:
TestView *aView ;//the view which you press
NSThread *timerThread;
NSTimer *touchTimer;
NSDate *touchStartTime;
CGPoint touchStartPoint;
CGPoint lastTouchPoint;
in the .m file you should add these method:
- (void)doSomething
{
NSLog(#"Long press!!!");
}
- (void)startTimerThead{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
touchTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:#selector(checkTouchTime:)
userInfo:nil repeats:YES];
[runLoop run];
[pool release];
}
- (void)stopTouchTimer{
if (touchTimer != nil) {
[touchTimer invalidate];
touchTimer = nil;
}
if (timerThread != nil) {
[timerThread cancel];
[timerThread release];
timerThread = nil;
}
}
#define DELETE_ACTIVING_TIME 1.0
- (void)checkTouchTime:(NSTimer*)timer{
NSDate *nowDate = [NSDate date];
NSTimeInterval didTouchTime = [nowDate timeIntervalSinceDate:touchStartTime];
if (didTouchTime > DELETE_ACTIVING_TIME){
[self stopTouchTimer];
[self doSomething];
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
UIView *touchView = [touch view];
if ([touchView isKindOfClass:[TestView class]]) {
touchStartTime = [[NSDate date] retain];
if (nil == timerThread) {
timerThread = [[NSThread alloc] initWithTarget:self selector:#selector(startTimerThead) object:nil];
[timerThread start];
}
}
touchStartPoint = [touch locationInView:self.view];
lastTouchPoint = touchStartPoint;
}
#define TOUCH_MOVE_EFFECT_DIST 10.0f
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint movedPoint = [touch locationInView:self.view];
CGPoint deltaVector = CGPointMake(movedPoint.x - touchStartPoint.x, movedPoint.y - touchStartPoint.y);
if (fabsf(deltaVector.x) > TOUCH_MOVE_EFFECT_DIST
|| fabsf(deltaVector.y) > TOUCH_MOVE_EFFECT_DIST)
{
[self stopTouchTimer];
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
UIView *touchView = [touch view];
CGPoint movedPoint = [touch locationInView:self.view];
CGPoint deltaVector = CGPointMake(movedPoint.x - touchStartPoint.x, movedPoint.y - touchStartPoint.y);
if ([touchView isKindOfClass:[TestView class]]) {
if (fabsf(deltaVector.x) < TOUCH_MOVE_EFFECT_DIST
&& fabsf(deltaVector.y) < TOUCH_MOVE_EFFECT_DIST) {
[self stopTouchTimer];
}
}
}
In this cocos2d app the nslog is not firing when I press the ccsprite. Could someone help me?
-(BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event{
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
for (CCSprite *target in _targets) {
CGRect targetRect = CGRectMake(target.position.x - (target.contentSize.width/2),
target.position.y - (target.contentSize.height/2),
27,
40);
CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
if (CGRectContainsPoint(targetRect, touchLocation)) {
NSLog(#"Moo cheese!");
}
}
return YES;
}
First of all be sure that you register the sprite for touches into the onEnter method for example:
- (void)onEnter
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:defaultTouchPriority_ swallowsTouches:YES];
[super onEnter];
}
This will make your sprite touchable and so fire the event to the sprite when a user will press it.
Then refactor your code to make it more readable and test something like that:
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
NSArray *targetsToDelete = [self touchedSpritesAtLocation:touchLocation];
// Put your code here
// ...
return YES;
}
- (NSArray *)touchedSpritesAtLocation:(CGPoint)location
{
NSMutableArray *touchedSprites = [[NSMutableArray alloc] init];
for (CCSprite *target in _targets)
if (CGRectContainsPoint(target.boundingBox, location))
[touchedSprites addObject:target];
return [touchedSprites autorelease];
}
It should return the targets that have been touched.
In layer init method add this
self.isTouchEnabled = true;
Use this code for touch detection
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *myTouch = [touches anyObject];
CGPoint location = [myTouch locationInView:[myTouch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
CGRect rect = [self getSpriteRect:yourSprite];
if (CGRectContainsPoint(rect, location))
{
NSLog(#"Sprite touched\n");
}
}
To get sprite rect:
-(CGRect)getSpriteRect:(CCNode *)inSprite
{
CGRect sprRect = CGRectMake(
inSprite.position.x - inSprite.contentSize.width*inSprite.anchorPoint.x,
inSprite.position.y - inSprite.contentSize.height*inSprite.anchorPoint.y,
inSprite.contentSize.width,
inSprite.contentSize.height
);
return sprRect;
}
How would it be possible to swipe (left) html pages?
Handling gestures happening on a web view is tricky, since UIWebView is really greedy with touches and wants to handle them all.
As usual with web views, you could try and do everything with Javascript. You can read something about it in this S.O. post.
The way I prefer handling pinching (zoom) and swiping on a web view though is following:
subclass UIWindow:
#interface MyWebNavigatorWindow : UIWindow {
install an instance of that window type as the window for your app in application:didFinishLaunchingWithOptions:
_window = [[MyWebNavigatorWindow alloc] initWithFrame:rect];
Alternatively, you can assign a class to your window object in Interface Builder.
handle swipes and pinches in sendEvent in your MyWebNavigatorWindow class:
- (void)sendEvent:(UIEvent*)event {
NSSet* allTouches = [event allTouches];
UITouch* touch = [allTouches anyObject];
UIView* touchView = [touch view];
You will need a mechanism in sendEvent to detect when the touch happens inside of your web view. In my case, I "register" all the views that I want to handle touch for and then check if the touch is inside them:
for (UIView* view in self.controlledViews) { // self.controlledViews is the array of all "registered" views
if ([touchView isDescendantOfView:view]) {
then I handle the various touch phases to detect what kind of gesture it is (code snippet not compilable, but it gives the idea):
if (touch.phase == UITouchPhaseBegan) {
NSLog(#"TOUCH BEGAN");
_initialView = touchView;
startTouchPosition1 = [touch locationInView:self];
startTouchTime = touch.timestamp;
if ([allTouches count] > 1) {
startTouchPosition2 = [[[allTouches allObjects] objectAtIndex:1] locationInView:self];
previousTouchPosition1 = startTouchPosition1;
previousTouchPosition2 = startTouchPosition2;
}
}
if (touch.phase == UITouchPhaseMoved) {
NSLog(#"TOUCH MOVED");
if ([allTouches count] > 1) {
CGPoint currentTouchPosition1 = [[[allTouches allObjects] objectAtIndex:0] locationInView:self];
CGPoint currentTouchPosition2 = [[[allTouches allObjects] objectAtIndex:1] locationInView:self];
CGFloat currentFingerDistance = CGPointDist(currentTouchPosition1, currentTouchPosition2);
CGFloat previousFingerDistance = CGPointDist(previousTouchPosition1, previousTouchPosition2);
if (fabs(currentFingerDistance - previousFingerDistance) > ZOOM_DRAG_MIN) {
NSNumber* movedDistance = [NSNumber numberWithFloat:currentFingerDistance - previousFingerDistance];
if (currentFingerDistance > previousFingerDistance) {
NSLog(#"zoom in");
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_ZOOM_IN object:movedDistance];
} else {
NSLog(#"zoom out");
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_ZOOM_OUT object:movedDistance];
}
}
}
}
if (touch.phase == UITouchPhaseEnded) {
CGPoint currentTouchPosition = [touch locationInView:self];
// Check if it's a swipe
if (fabsf(startTouchPosition1.x - currentTouchPosition.x) >= SWIPE_DRAG_HORIZ_MIN &&
fabsf(startTouchPosition1.x - currentTouchPosition.x) > fabsf(startTouchPosition1.y - currentTouchPosition.y) &&
touch.timestamp - startTouchTime < 0.7
) {
// It appears to be a swipe.
if (startTouchPosition1.x < currentTouchPosition.x) {
NSLog(#"swipe right");
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_SWIPE_RIGHT object:touch];
} else {
NSLog(#"swipe left");
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_SWIPE_LEFT object:touch];
}
}
startTouchPosition1 = CGPointMake(-1, -1);
_initialView = nil;
}