I's using this method:
- (void)setTranslation:(CGPoint)translation inView:(UIView *)view
From UIPanGestureRecognizer class, and I don't understand the velocity discussions around that.
Appel's documentation says:
Sets the translation value in the coordinate system of the specified
view. Changing the translation value resets the velocity of the pan.
What's that exactly mean? What is "reset velocity of the pan"?
Any help will be appreciated.. Thanks in advance!
--EDIT
See this code:
if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:self.cardsScrollView];
recognizer.view.center = CGPointMake(recognizer.view.center.x, recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:recognizer.view];
...
}
What it means is that if you're in the middle of the panning gesture (you're dragging something) and you call setTranslation:inView: on that gesture, it's velocity is going to be reset to 0;
A pan gesture not only gives you the translation but also the velocity of the gesture (how fast you're dragging) in units per second (points per second in this case). You can access the velocity by calling velocityInView:.
If you don't use the velocity then don't worry about it, otherwise keep the above in mind.
UPDATE:
I'm guessing you're trying to move the view as you drag it. I would do it slightly differently.
if (recognizer.state == UIGestureRecognizerStateBegan) {
CGAffineTransform transform = recognizer.view.transform;
[recognizer setTranslation:CGPointMake(transform.tx, transform.ty) inView:self];
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:self.cardsScrollView];
recognizer.view.transform = CGAffineTransformMakeTranslation(translation.x, translation.y);
}
The thing to keep in mind is that self.cardsScrollView should be the superview of the view you're trying to move.
Related
I have a UIScrollView with 2 images inside of it. I have pan, pinch, and rotate gestures hooked up to the scrollview and everything works fine. However when I tap a button that performs an action on the scroll view, the pan will reset to its center. The pinch to zoom and rotate do not reset their values but the pan does. How can I code it to not reset to center but still not fly off the screen when touched?
Here is my code for the pan gesture:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
Any help is greatly appreciated
In my UITableViewCell subclass I add a pan gesture and in gestureRecognizerShouldBegin method I checked self.frame.origin.x and self.frame.origin.y both are 0.000000 and 0.000000and after applying TranslationInView CGPoint translation = [gestureRecognizer translationInView:[self superview]]; I am getting x=-4.000000 and y=0.000000
How TranslationInView work, I am trying to wrap my head around it, when I am getting the correct location of cell 0.0 and 0.0 because the first cell will have 0.0 and 0.0, why I need TranslationInView.
TranslationInView is a method of UIPanGestureRecognizer and it tells you how far the touch moved since it was last reset. It resets when the touch goes down or if you reset it yourself.
For example
- (void) pan: (UIPanGestureRecognizer *) recognizer
{
if ((recognizer.state == UIGestureRecognizerStateChanged)||(recognizer.state == UIGestureRecognizerStateEnded)) {
CGPoint translation = [recognizer translationInView:self];
}
}
The CGPoint traslation gets increased/decreased the distance that the gesture has moved.
So I'm trying to make an app where you pick up objects with your finger, it moves with your finger and then you flick it away to throw it. I found some threads for similar games but not with this particular problem.
Since I couldn't really keep the object along with my finger by applying force or impulse I disable the physics on it while I'm "holding it", measure velocity so when I release it I apply that vector as an impulse.
This works excellent visually but when I try to read the position of the object it's not synced up with "reality". I've made a condition so if velocity isn't strong enough when you release the object it is supposed to return to its starting position but it doesn't move at all (except if you manage to throw it, that works fine) so when I tried to output the .position to the console it didn't correlate to what I was seeing on the screen.
Any ideas? I know you shouldn't set position and simulate physics at the same time but I disable the physics for when I'm dragging the object. Why won't it keep track of .position just because it is moving it with physics?
Here I try to throw a potion:
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) { //Picked up
potion.physicsBody.dynamic = NO;
potion.physicsBody.velocity = CGVectorMake(0.0, 0.0);
potion.physicsBody.angularVelocity = 0.0;
potion.zRotation = 0.0;
CGPoint touchLocation = [recognizer locationInView:recognizer.view];
touchLocation = [self convertPointFromView:touchLocation];
[self selectNodeForTouch:touchLocation];
} else if (recognizer.state == UIGestureRecognizerStateChanged) { //Moved around
CGPoint translation = [recognizer translationInView:recognizer.view];
translation = CGPointMake(translation.x, -translation.y);
[self panForTranslation:translation];
[recognizer setTranslation:CGPointZero inView:recognizer.view];
velocity = CGVectorMake(translation.x, translation.y);
} else if (recognizer.state == UIGestureRecognizerStateEnded) { //Dropped
_selectedNode = nil;
potion.physicsBody.dynamic = YES;
if (potion.position.y < 250 && velocity.dy < 20) { //Returned to starting position
NSLog(#"Pos:%f,%f",potion.position.x,potion.position.y);
potion.physicsBody.velocity = CGVectorMake(0.0, 0.0);
potion.physicsBody.angularVelocity = 0.0;
potion.zRotation = 0.0;
potion.position = CGPointMake(gameWidth / 2, 150);
NSLog(#"Returning potion");
NSLog(#"Pos:%f,%f",potion.position.x,potion.position.y);
} else { //Flung away
[potion.physicsBody applyImpulse:velocity];
NSLog(#"Add impulse: %f, %f", velocity.dx, velocity.dy);
NSLog(#"Pos:%f,%f",potion.position.x,potion.position.y);
}
}
}
EDIT:After some more testing I've observed that if it has never been thrown (dynamic with impulse) it always shows (160, 150) as its position no matter where it is. Sometimes it shows something like 160.00031 and I don't know why. After it has been thrown it shows the correct position in the NSLog but changing the position still doesn't work
EDIT2: Just saw that it doesn't show the correct position after a throw. Just different but they seem offset. I throw objects in the +y direction (up) and when I put it down in the left corner the position.x is correct but position.y is offset by about 300. So where y should be 0 it's 300.
I'm learning iOS and I can't find how to add drag and drop behavior to a UIView.
I tried:
[_view addTarget:self action:#selector(moved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
It says "no visible interface for UIView declares selector addTarget (etc)"
Also I tried adding a pan gesture recognizer but not sure if that's what I need
- (IBAction)test:(id)sender {
NSLog(#"dfsdfsf");
}
It's called, but don't know how to get the coordinates of the event. What't the standard, simple way in iOS to register a callback for move events / do drag and drop?
Thanks in advance.
A UIPanGestureRecognizer is definitely the way to go. If you want the user to drag the view around, you'll need the “translation” (movement) of the gesture in the superview's coordinate system:
- (IBAction)panWasRecognized:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:_view.superview];
Once you have the translation, you can move (“drag”) the view by changing its center:
CGPoint center = _view.center;
center.x += translation.x;
center.y += translation.y;
_view.center = center;
Finally, you want to set the pan gesture recognizer's translation back to zero, so the next time you get the message, it only tells you how much the gesture has moved since the last message:
[recognizer setTranslation:CGPointZero inView:_view.superview];
}
Here it is all together for easy copy/paste:
- (IBAction)panWasRecognized:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:_view.superview];
CGPoint center = _view.center;
center.x += translation.x;
center.y += translation.y;
_view.center = center;
[recognizer setTranslation:CGPointZero inView:_view.superview];
}
Start with touchesBegan, touchesMoved, touchesEnded. Override these in your UIView subclass and you'll be on your way to learning the event system. You can get the event coordinates like so:
- (void) touchesBegan:(NSSet *) touches withEvent:(UIEvent *) event
{
float x = [[touches anyObject] locationInView:self].x;
float y = [[touches anyObject] locationInView:self].y;
}
Then there's a ton of stuff for converting coordinates between different views and so on. Once you've understood that, you can work with the UIGestureRecognizer stuff you've already found which is what you need.
You are going to need a pan gesture recognizer to do drag/drop. You can use the locationInView: selector in UIPanGestureRecognizer to find out where you are at any given moment.
You add your gesture recognizer like so, not with the target-action stuff you were trying:
UIPanGestureRecognizer *dragDropRecog = [[UIPanGestureRecognizer alloc] initWithTarget:yourView action:#selector(thingDragged:)];
[yourView addGestureRecognizer:dragDropRecog];
Then you have to implement the selector thingDragged: in your view:
- (void) thingDragged:(UIPanGestureRecognizer *) gesture
{
CGPoint location = [gesture locationInView:self];
if ([gesture state] == UIGestureRecognizerStateBegan) {
// Drag started
} else if ([gesture state] == UIGestureRecognizerStateChanged) {
// Drag moved
} else if ([gesture state] == UIGestureRecognizerStateEnded) {
// Drag completed
}
}
You'll be translating the view being dragged in the changed bit, and handling the drop in the ended section.
I am building an iPad app where I needed to allow resizing views functionality using divider view provided between two views. The divider view is just a 20px height view between two half screen content views - please refer attached images.
When user scrolls this divider view up or down, both content views changes their sizes appropriately. I have extended UIView and implemented this using touchMoved delegate as code given below in touchesMoved delegate. It works fine. The only thing is missing with TouchMoved is you can't flick divider view to top or bottom directly. You have to drag all the way to top or bottom!
To support flicking the view I have tried UIPanGestureRecognizer but I don't see smooth dragging with it. Setting split-position of parent resizes both content views as shown in the code. When I handle split position change in UIGestureRecognizerStateChanged state, just touching divider view flick it to top or bottom. Handling split position change in UIGestureRecognizerStateEnded does the same but I don't see content view resizing with dividerview scrolling!
Could someone please tell me how could I achieve both smooth scrolling of divider view with resizing content views (like touchMoved) and flicking the view? Any alternative approach would also be fine. Thanks.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if (touch) {
CGPoint lastPt = [touch previousLocationInView:self];
CGPoint pt = [touch locationInView:self];
float offset = pt.y - lastPt.y;
self.parentViewController.splitPosition = self.parentViewController.splitPosition + offset;
}
}
- (void)handlePan:(UIPanGestureRecognizer*)recognizer {
CGPoint translation = [recognizer translationInView:recognizer.view];
CGPoint velocity = [recognizer velocityInView:recognizer.view];
if (recognizer.state == UIGestureRecognizerStateBegan) {
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
// If I change split position here, I don't see smooth scrolling dividerview...it directly jumps to the top or bottom!
self.parentViewController.splitPosition = self.parentViewController.splitPosition + translation.y;
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
// If I change split position here, the same thing happens at end and I don't see my divider view moving with my scrolling and resizing my views.
self.parentViewController.splitPosition = self.parentViewController.splitPosition + translation.y;
}
}
Initial screen
Increased top view size by scrolling divider view towards bottom.
Top view is totally hidden here but I have to scroll divider view all
the way to top. I want to flick the divider view so that it directly
goes from any position to top
If I understand you correctly, you have two separate problems here.
1st: The 'unsmooth' dragging when using the GestureRecognizer. The problem in your code is, that you add the translation every time the GR calls it's handle method. Since the translation is measured continuously, you're adding a higher value every time it's called (i.e. the first call adds 10 to the split position, the 2nd 20, the 3rd 30 and so on).
To solve this you should set the translation to zero after you've updated the split position:
- (void)handlePan:(UIPanGestureRecognizer*)recognizer {
CGPoint translation = [recognizer translationInView:recognizer.view];
if (recognizer.state == UIGestureRecognizerStateChanged) {
self.parentViewController.splitPosition = self.parentViewController.splitPosition + translation.y;
}
[recognizer setTranslation:CGPointZero inView:recognizer.view];
}
2nd: To allow the user to flick the split position to the top or bottom, you could check the velocity in UIGestureRecognizerStateEnded, and if it exeeds some value instantly set the splitposition to top or bottom.
But it might be more convenient to use a UISwipeGestureRecognizer.
- (void)handleSwipe:(UISwipeGestureRecognizer *)gr
{
if (gr.direction == UISwipeGestureRecognizerDirectionUp){
[self.parentViewController moveSplitViewToTop];
}
else if (gr.direction == UISwipeGestureRecognizerDirectionDown){
[self.parentViewController moveSplitViewToBottom];
}
}
In this case it might be necessary (not sure, maybe it's not) to require the SwipeGR to fail before the PanGR triggers by setting:
[panGestureRecognizer requireGestureRecognizerToFail:swipeGestureRecognizer];
Hope that helped.
Edit:
Regarding your comment, I assume by frequency you mean velocity. If you only use the PanGR you could modify the code above to sth. like this:
- (void)handlePan:(UIPanGestureRecognizer*)recognizer {
CGPoint translation = [recognizer translationInView:recognizer.view];
CGPoint velocity = [recognizer velocityInView:recognizer.view];
if (recognizer.state == UIGestureRecognizerStateChanged) {
self.parentViewController.splitPosition = self.parentViewController.splitPosition + translation.y;
}
else if (recognizer.state == UIGestureRecognizerStateEnded){
if (velocity.y > someValue){
[self.parentViewController moveSplitViewToBottom];
}
else if (velocity.y < someValue){
[self.parentViewController moveSplitViewToTop];
}
}
[recognizer setTranslation:CGPointZero inView:recognizer.view];
}
I'm not sure if the velocity is signed, if not you've got to check for the translation again in the if-block.