So my current project has a pan gesture recognizer, and if I have panned to the top of the screen the screen it is supposed to scroll up to account for that gesture. While the gesture hasn't ended and the current position of the gesture remains at the top of the screen, I want to continually keep scrolling. My problem is the gesture recognizer only gets called when the state changes, therefore my content will only scroll if you move back and forth at the top, not continually while the gesture continues to be at the top. Is there any reasonable way to continually call code while the gesture hasn't ended, but isn't necessarily changing? Here is pseudo-code of what I have:
- (void)handleGestureRecognizer:(UIGestureRecognizer *)gesture {
if ( gesture.state == UIGestureRecognizerStateChanged ) {
CGPoint point = [gesture locationInView:self.view];
if (point.y < 100) {
//I would like this code to be called continually while the
//gesture hasn't ended, not necessarily only when it changes
[self updateScrollPosition];
}
}
I can think of a few ghetto ways to do it by setting state bools based on the current state of the recognizer and creating my own timer to periodically check, but it seems pretty hacky and I don't particularly like it, so I'm wondering if anyone could come up with a cleaner solution.
One way to use a timer and feel better about it would be to conceal it a little by using performSelector:withObject:afterDelay:
- (void)stillGesturing {
[self updateScrollPosition];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:#selector(stillGesturing) withObject:nil afterDelay:0.5];
}
// then in the recognizer target
if (gesture.state == UIGestureRecognizerStateEnded) {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
} else if ( gesture.state == UIGestureRecognizerStateChanged ) {
CGPoint point = [gesture locationInView:self.view];
if (point.y < 100) {
//I would like this code to be called continually while the
//gesture hasn't ended, not necessarily only when it changes
[self stillGesturing];
}
}
Related
The player drags a sprite in my game but when accidentally touch the screen with a second finger it screws the movement obviously.
I used the following solutions for disable the second touch, but unfortunately it doesn't work:
//--------------
-(void)touchesBegan:(NSSet*) touches withEvent:(UIEvent*) event {
if (touches.count == 1 && draggedNode == nil) {
CGPoint pos = [[touches anyObject] locationInNode:self];
SKNode * touchedNode = [self nodeAtPoint:pos];
if([touchedNode.name isEqual: #"shooterBall"]){
draggedNode = touchedNode;
}
draggedNodeOffset = CGPointMake(draggedNode.position.x - pos.x, draggedNode.position.y - pos.y);
}
}
//--------------
-(void)touchesMoved:(NSSet*) touches withEvent:(UIEvent*) event {
if (touches.count <= 1) {
CGPoint pos = [[touches anyObject] locationInNode:self];
draggedNode.position = CGPointMake(pos.x + draggedNodeOffset.x, pos.y+draggedNodeOffset.y);
}
}
//--------------
-(void)touchesEnded:(NSSet*) touches withEvent:(UIEvent*) event {
draggedNode = nil;
}
//--------------
Do you have any solution for this?
Thanks your help in advance!
You want to implement UIPanGestureRecognizer in your scene. It will allow you to track the location of the user's touch and at the same time control other "stray" touches: UIPanGestureRecognizer Documentation
After you initialize it, you need to implement a method to handle the user's pans. You will have to set flags inside of this method to control when the swipe started/ended. I think this answer on StackOverflow gave a really good explanation of using it (with Swift). BTW, when you initialize it, you should set the gesture recognizer's property maximumNumberOfTouches to 1 (that will cause it to ignore other touches while the user is panning).
The trickier part will be to translate the same code you wrote before to gesture recognizer. The difference is that your handler will be called only once for each "swipe" or "pan", while the touches method you are using now is called each time there is a "touch". There are a few ways to proceed at this point, and you could try whatever you like, but I think that this would be the easiest way to go once you have your gesture recognizer set up (spoiler):
make sure the gesture recognizer is an instance variable so you can access it from all methods.
go to the update: method and make an if statement that checks if gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged
use the same algorithm that you had before in this if statement. To check the location that the touch is at use the method: locationInView:. Use self.view as the parameter.
Hope this helped! good luck.
I need two functions which would be fired when the rotation gesture starts and finishes, because I need to know the whole angle of the rotation. Currently the gesture recogniser is fired all the time until the rotation finishes, and I cannot find out when it has finished, to find to total angle.
That's because the method you hook to your gesture gets called for all of the gestures states, like began/ended/canceled/changed. You can however ask the gesture for its current state within the method, and add specific functionality for these different states. Here's a basic example:
- (void)rotationGestureHandler:(UIRotationGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan) {
// do stuff - call method for gesture began
}else if (gesture.state == UIGestureRecognizerStateEnded) {
// do other stuff - call method for gesture ended
}
}
I have a situation where I have to work around a bug in iOS7 and need to convert the UIButton control events:
UIControlEventTouchUpOutside
UIControlEventTouchDown
UIControlEventTouchUpInside
UIControlEventTouchDragOutside
UIControlEventTouchDragInside
UIControlEventTouchDragInside
to UIGestureRecgonizer (I can't use UIView touch methods such as touchesBegan, touchesEnded, etc. either). I'm kind of interested if this is even possible anyway.
For instance, I'm thinking of a way to convert UIControlEventTouchDown and can't think of a way. UITapGestureRecognizer and UIPanGestureRecognizer both would not work.
Anyone know if this is possible?
I've done something similar with a UILongPressGestureRecognizer for a SpriteKit game to implement line segment intersection, pretty sure it would be easily adaptable for mimicking a UIButton too. Keep track of the previous location of your gesture recognizer, I just used a static CGPoint that I updated every time my selector fired. Then, just check the previous position and the current position with CGRectContainsPoint using your button's frame, and depending on those results, do whatever you need to do. If both are inside the frame, that's the same as UIControlEventDragInside, if both are outside, that's UIControlEventDragOutside, if the previous is outside and the current is inside, that's UIControlEventDragEnter, etc etc. Also make sure to check the gesture recognizer's state so you know when to call TouchUpInside/Outside. I'd make sure to have a damn good reason to do this, but it seems workable to me.
- (void) longPress:(UILongPressGestureRecognizer *)longPressGestureRecognizer
{
if (longPressGestureRecognizer.state == UIGestureRecognizerStateBegan || longPressGestureRecognizer.state == UIGestureRecognizerStateChanged)
{
CGPoint touchedPoint = [longPressGestureRecognizer locationInView: self];
if (CGRectContainsPoint(self.bounds, touchedPoint))
{
[self addHighlights];
}
else
{
[self removeHighlights];
}
}
else if (longPressGestureRecognizer.state == UIGestureRecognizerStateEnded)
{
if (self.highlightView.superview)
{
[self removeHighlights];
}
CGPoint touchedPoint = [longPressGestureRecognizer locationInView: self];
if (CGRectContainsPoint(self.bounds, touchedPoint))
{
if ([self.delegate respondsToSelector:#selector(buttonViewDidTouchUpInside:)])
{
[self.delegate buttonViewDidTouchUpInside:self];
}
}
}
}
I'm using a UIPanGestureRecognizer and UIAttachmentBehavior to move a UIView around the screen. When the user ends the gesture I apply the velocity of the gesture recognizer to the view using a UIDynamicItemBehavior and the addLinearVelocity:forItem: method.
Here is the code I use:
- (void)_handlePanGestureRecognized: (UIPanGestureRecognizer *)panGestureRecognizer
{
if (panGestureRecognizer.state == UIGestureRecognizerStateBegan)
{
_attachmentBehavior.anchorPoint = panGestureRecognizer.view.center;
[_dynamicAnimator addBehavior: _attachmentBehavior];
}
else if (panGestureRecognizer.state == UIGestureRecognizerStateChanged)
{
CGPoint point = [panGestureRecognizer locationInView: panGestureRecognizer.view.superview];
_attachmentBehavior.anchorPoint = point;
}
else if (panGestureRecognizer.state == UIGestureRecognizerStateEnded)
{
[_dynamicAnimator removeBehavior: _attachmentBehavior];
CGPoint velocity = [panGestureRecognizer velocityInView: panGestureRecognizer.view.superview];
[_dynamicItemBehavior addLinearVelocity: velocity
forItem: self];
}
}
When the view stops moving I would then like to have it snap to the closest edge of the screen but I currently have no way of knowing when it has stopped moving short of polling the view's center with a CADisplayLink.
Have you tried attaching a UIDynamicAnimatorDelegate to your animator, and using the dynamicAnimatorDidPause: method to trigger snapping to the closest edge?
From reading on the developer forums, it sounds like some have had problems with their views staying in motion for a very long time (jiggling back and forth by 1 pixel, for example), but perhaps this will work for your case.
I have the following problem.
I am using a UILongPressGestureRecognizer to put a UIView into a "toggle mode". If the UIView is in "toggle mode" the user is able to drag the UIView around the screen. For dragging the UIView around the screen I am using the methods touchesBegan, touchesMoved and touchesEnded.
It works, but: I have to lift my finger in order to drag it, because the touchesBegan method got already called and therefore is not called again and therefore I can't drag the UIView around the screen.
Is there any way to manually call touchesBegan after UILongPressGestureRecognizer got triggered (UILongPressGestureRecognizer changes a BOOL value and the touchesBegan only works if this BOOL is set to YES).
UILongPressGestureRecognizer is a continuous gesture recognizer, so rather than resorting to touchesMoved or UIPanGestureRecognizer, just check for UIGestureRecognizerStateChanged, e.g.:
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[self.view addGestureRecognizer:gesture];
}
- (void)handleGesture:(UILongPressGestureRecognizer *)gesture
{
CGPoint location = [gesture locationInView:gesture.view];
if (gesture.state == UIGestureRecognizerStateBegan)
{
// user held down their finger on the screen
// gesture started, entering the "toggle mode"
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
// user did not lift finger, but now proceeded to move finger
// do here whatever you wanted to do in the touchesMoved
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
// user lifted their finger
// all done, leaving the "toggle mode"
}
}
I would suggest you to use UIPanGestureRecognizer as it a recommended gesture for dragging.
You can configure the min. and max. number of touches required for a panning, using the following the properties:
maximumNumberOfTouches
minimumNumberOfTouches
You can handle the states like Began, Changed and Ended, like having animation for the required states.
Using the below method translate the point to the UIView in which you want it.
- (void)setTranslation:(CGPoint)translation inView:(UIView *)view
example:
You have to use a global variable to retain the old frame. Get this in UIGestureRecognizerStateBegan.
When the state is UIGestureRecognizerStateChanged. You can use the
-(void) pannningMyView:(UIPanGestureRecognizer*) panGesture{
if(panGesture.state==UIGestureRecognizerStateBegan){
//retain the original state
}else if(panGesture.state==UIGestureRecognizerStateChanged){
CGPoint translatedPoint=[panGesture translationInView:self.view];
//here you manage to get your new drag points.
}
}
Velocity of the drag. Based on the velocity you can provide a animation to show bouncing of a UIView
- (CGPoint)velocityInView:(UIView *)view