How to control CAKeyframeAnimation with touch gesture? - objective-c

I have a CAEmitterLayer animated along a bezier path (closed form, like an '8', out of four control points) with a CAKeyframeAnimation. Now I want to control the animation by a slide of a touch-finger along (but not necessarily on) the path. How is this possible and is this even possible?

Make a CGpoint click; variable to remember your initial "drag" point, then create a local NSEvent handler...
[NSEvent addLocalMonitorForEventsMatchingMask: NSMouseMovedMask
| NSLeftMouseDownMask
handler:^(NSEvent *e) {
if ( e.type == NSLeftMouseDown ) click = e.locationInWindow;
else "yourDelta" = click - e.locationInWindow; // pseudoCode
return e;
}];
"yourDelta" is the offset of that initial point from the current location... you could also achieve similar results with scroll events, by monitoring NSEventScrollWheelMask... and looking at the e.deltaX and e.deltaY values.
Edit: I'm not as familiar with event handling on iOS.. but the same technique could be applied with normal event handlers... ie.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)e {
click = [e locationInView:_yourView];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent*)e {
"yourDelta" = click - [e locationInView:_yourView]; // pseudoCode
}
As to "seeking" your animation.. One possible way is to simply [layer addAnimation:theNewAnimation], using your previous toValue's, but instead of basing the fromValue's of 0, or your model layer... use your layer.presentationLayer values instead? It is hard to say without seeing the full content of your CAKeyframeAnimation.

Related

Only first link is detected in uitextview

I have strange problem with url detection in textView. My code is like this:
_contactDescription.dataDetectorTypes = UIDataDetectorTypeLink | UIDataDetectorTypePhoneNumber;
[_contactDescription setUserInteractionEnabled:YES];
[_contactDescription setSelectable:YES];
[_contactDescription setEditable:NO];
_contactDescription.delegate = self;
_contactDescription.text = desc;
My string has a lot of phone numbers and emails to detect but using data detection only first phone and first email run action on tap. All phones and emails are highlighted but no action on tap.
Does anyone had similar problem ?
Thanks in advance.
If they are highlighted but you can't click them, but you can click the first one, that usually means that there is another view you didn't expect over lapping the text. You could try adding this in your view controller to see what is getting tapped.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch * touch in [touches allObjects])
{
NSLog(#"View: %#", touch.view);
}
}
Or run it in the simulator and in Debug turn on Color Blended Layers
Or try hiding all other views until it works to find the offender.
Another issue could bet the text is actually outside the view and you can't tell because it isn't clipping the extra. To verify that isn't the case.
[_contactDescription setClipsToBounds:YES];
Hopefully that is the case and one of those help find the offender. Good luck.

Cocos2d 'snap to grid' logic

I'm building a game in Cocos2d and I'm having a hard time implementing what I want. Imagine an 8 x 10 grid of squares. You can touch a square and drag that square's row / column horizontally / vertically, but not both. After you release the row / column, the squares will 'snap' back into place in the grid according to their closest row / col positions.
Everything works programmatically, with no overlaps or misplaced squares or invalid positions. However, I just cannot seem to get this 'snap to grid' to animate the way I want.
I'm using the MVC design pattern to separate any views from any game logic. The view is simply handed an array of 'squaresToUpdate', looks at every square, finds the corresponding sprite, and updates the position of the sprite based on the position of the square.
The problem arises when trying to do something along the lines of this:
-update():
for each square in squaresToUpdate:
if square is not being dragged at the moment:
setup a 'CCMoveTo' to bring the sprite in line with square
I can't get the squares to move freely while being dragged, but CCMoveTo when not being dragged. Either I create a new action every update, or the squares just freak out.
I don't know if it's my logic that is broken, if CCMoveTo is not doing what I want, or if this problem is actually much harder than I initially thought. Could someone help me out with this logic?
Good old pencil and paper gives me this, but I'm not sure it's 100%:
- (void)update:(ccTime)dt {
NSMutableArray *toDraw = [self.rootView whatAmIDrawing];
for (GameObject *o in toDraw) {
CCSprite *sprite = (CCSprite*)[self.batch getChildByTag:o.tag];
if (o.moving == NO) {
if (o.snapping == NO) {
o.snapping = YES;
CCMoveTo *move = [CCMoveTo actionWithDuration:self.rootView.model.activeGame.snapSpeed position:o.position];
[sprite runAction:move];
}
} else {
o.snapping = NO;
sprite.position = o.position;
sprite.rotation = o.rotation;
}
}
[super update:dt];
}
I would override the cocos2d touch methods:
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
- (void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
ccTouchBegan would compare the touch point with every game object and set a "touched" flag:
ccTouchMoved would set the position of any "touched" object to the new touch position, and set a "moving" flag.
ccTouchEnded would clear all "touched" flags, run the CCMoveTo to the new position for any object with the "moving" flag, and then clear all "moving" flags.
The update() method would then just have to change position of every sprite to the position of the object.

Mouse Enter/Exit events on partially hidden NSViews

I have a problem that I think is solvable with some hackery, but I'm very curious if there is an easier way to get the job done without having to do all of that.
I have a stack of NSViews (layer-backed, if that somehow helps provides some better solution), as shown below:
The thing here is that this is essentially a menu, but is hover-sensitive. If the user hovers over one of the exposed parts of the lower-level views, I need to perform an action depending on what that view is. It is a dynamic system so the number of stacked menu items like this may change, making static calculations more difficult. As you can see, they are basically all a copy (shape-wise) of the first item, but then rotated a bit the further you go down the stack via simple transform rotation.
My question to the SO community is what do you all think the best approach to getting mouseEntered: and mouseExited: events for just the literally visible portions of these views?
What I have attempted to do is use an NSTrackingArea on the visibleRect portion of these views, which sounds much more handy than it really is in this situation. In reality, the visibleRect seems to be "visible" for all of them, all the time. Nothing is explicitly blocked or hidden by anything more than just a partially overlapping NSView. All that happens is I get a spammed console from all of the views screaming out at once that a mouse entered their rect.
Something I am considering is making sub-NSView's of each menu item and having each of those be responsible for the tracking area... each menu item having a "strip" view along the right and bottom sides that could report, but that's still a bit of a hack and is icky.
Does anyone have a better idea? Perhaps one from experience?
Thanks!
I know you already have a solution, but I thought I would try a different approach, that didn't require getting tons of mouseMoved events. I created 3 custom views in code, added tracking rects for them and sent all mouseEntered and mouseExited messages to the same method that does a hitTest to determine which view is top most. This is the code for the content view of the window.
#implementation MainView
#synthesize oldView;
-(void)awakeFromNib {
oldView = nil;
Card *card1 = [[Card alloc]initWithFrame:NSMakeRect(150, 150, 200, 150) color:[NSColor redColor] name:#"Red Box"];
NSTrackingArea *area1 = [[NSTrackingArea alloc]initWithRect:card1.frame options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:area1];
[self addSubview:card1];
Card *card2 = [[Card alloc]initWithFrame:NSMakeRect(180, 120, 200, 150) color:[NSColor yellowColor] name:#"Yellow Box"];
NSTrackingArea *area2 = [[NSTrackingArea alloc]initWithRect:card2.frame options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:area2];
[self addSubview:card2];
Card *card3 = [[Card alloc]initWithFrame:NSMakeRect(210, 90, 200, 150) color:[NSColor greenColor] name:#"Green Box"];
NSTrackingArea *area3 = [[NSTrackingArea alloc]initWithRect:card3.frame options:NSTrackingMouseEnteredAndExited|NSTrackingActiveInActiveApp owner:self userInfo:nil];
[self addTrackingArea:area3];
[self addSubview:card3];
}
-(void)mouseEntered:(NSEvent *)theEvent {
[self reportTopView:theEvent];
}
-(void)mouseExited:(NSEvent *)theEvent {
[self reportTopView:theEvent];
}
-(void)reportTopView:(NSEvent *)theEvent {
id topView = [self hitTest:[theEvent locationInWindow]];
if (![topView isEqual:oldView]) {
oldView = topView;
([topView isKindOfClass:[Card class]])? NSLog(#"%#",[(Card *)topView name]):NULL;
}
}
This is the code for what I called cards (colored rectangles):
#implementation Card
#synthesize name,fillColor;
- (id)initWithFrame:(NSRect)frame color:(NSColor *)color name:(NSString *)aName{
self = [super initWithFrame:frame];
if (self) {
self.fillColor = color;
self.name = aName;
}
return self;
}
- (void)drawRect:(NSRect)rect {
[self.fillColor drawSwatchInRect:rect];
}
I finally came to a solution on Twitter via Steven Troughton-Smith. Here's how it works:
In each menu item, I am disregarding anything related to NSTrackingArea or direct mouse position interpretation. Instead, the parent controller view is handling all of the tracking and receiving mouse movement events.
Each menu item has an overridden hitTest: method that does the point conversion and returns whether or not the point being tested is within the background image (there are shadows and stuff in there, making it more difficult than the vanilla implementation).
I then setup a sort of "hover menu item changed" callback in the controller so that I can handle hover menu changes.
This was a pretty straightforward solution. Very glad I decided to stop and ask, rather than hack something together with my previous idea.
Thanks Steven!
Overlapping tracking-areas:
All you have to do is hitTest from view you are in. if this is true:
window.view.hitTest(window.mousePos) === self/*sudo code*/
What this code does is that it returns the view under the mouse position. Now all you have to do is setup a few "if" and "else" clauses to verify that your mouse is off or on the view.
Full code example:
https://gist.github.com/eonist/537ae53b86d5fc332fd3
Full description of the concept here: (perma link)
http://stylekit.org/blog/2015/12/20/Overlapping-tracking-areas/
VS the default enter and exit behaviour:
I had to add another answer to this question as this is another approach to solve the problem. This approach now also includes path assertion (think rects with round edges or other custom paths)
The answer is long winded but it works:
http://stylekit.org/blog/2016/01/28/Hit-testing-sub-views/
it involves using the apple provided method: CGPathContainsPoint(path,transform,point)
If you follow the link to that blog post and then from there check the styleKit repo on github. You will find the code need to achieve the gif animation example given above. Im providing this as a pointer to the answer as it may take you significantly less time than trying to research this on your own. I use this technique in all my UI elements and it works flawlessly.

multi touch..need help with cctouchesended

I've almost finished with my multi touch code for my game but I'm stuck with one more problem. This is done in Cocos2d btw :)
The player has a dpad on the left side and a button on the right, it works fine if i hold down on of the dpads and hold the jump button, so that he runs to the right and jumps.
The problem is once I lift my finger off the jump button all actions stop, so I have to lift and press my finger down again on the dpad...
this is my code for cctouchesended
-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
////when the user has stopped touching the screen set the variables
////back to false to stop its movement////
//self.myTouch = nil;
for (UITouch *touch in touches)
{
if (moveRight == TRUE) {
[player stopAction:RunForward];
}
if (moveLeft == TRUE) {
[player stopAction:RunBackwards];
}
moveLeft = FALSE;
moveRight = FALSE;
// jump = FALSE;
}
}
You would be better off mapping the logic to 2 buttons instead. Or at least for the jumping.
The problem with the current code is that you are not checking to see which finger you lift. You could ad a check so if the touch that ended is on the left side of the screen then stop moving.
Also you don't need to compare a BOOL value to TRUE. Just do, "if (moveRight)". It is much better
You will want to keep reference to the HASH (touch.hash) and save that CCTouchesBegan
then in the function you have now (the release) you check which hash has been released.
so:
in your ccTouchesBegan function you check which button is pressed:
if (CCRectContainsPoint(sprite1.boundingbox,touchlocation))
{
rightSideButtonHash = touch.hash;
)
in the release you check the touch.hash against the rightSideButtonHash, and you know if it was that touch!

How do I help prevent my users from initiating a drag accidentally in Cocoa?

I have a collection view that I've subclassed that allows me to reorder the collection view items via drag and drop. My drag code that sets up the pasterboard is currently in mouseDragged:
- (void)mouseDragged:(NSEvent *)aEvent {
if(!dragInProgress) {
dragInProgress = YES;
NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
... setup pboard, declare types, setData ...
... create drag image ....
[self dragImage: image
at: position
offset: NSZeroSize
event: aEvent
pasteboard: pboard
source: self
slideBack: YES];
}
}
I would like to only initiate a drag if the user has dragged for a certain length, so they don't initiate a drag accidentally. Is there a setting to do this in Cocoa, or do I need to move this code to mouseMoved: and check the distance between where the drag started and where the mouse is currently?
In mouseDown:, remember where the mouse went down (locationInWindow). In mouseDragged:, subtract the location of the mouse-down event from the location of the mouse-dragged event, and compare the difference to the size returned by HIMouseTrackingGetParameters with the kMouseParamsDragInitiation selector.