Limiting my UIPanGestureRecognizer - ios

I have the following code and it works just fine, but I can't really explain it to myself. My goal was to drag my UIView from the bottom and have it stop moving when the bottom of the UIView reaches the center of the screen. The code does just that, but I'm still confused by how the limit i set (newCenter.y >= 0 && newCenter.y <= 284) works.
My perception of whats happening is that when I begin dragging the UIView from the very bottom towards the top of the screen, my newCenter.y is constantly changing (decreasing) as I drag upwards. But what makes it stop when the bottom of the view being dragged reaches the center of the screen? The view stops dragging way before I even get close to 0.
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.view];
CGPoint newCenter = CGPointMake(self.view.bounds.size.width / 2,
recognizer.view.center.y + translation.y);
if (newCenter.y >= 0 && newCenter.y <= 284) {
recognizer.view.center = newCenter;
[recognizer setTranslation:CGPointZero inView:self.view];
}
}

Related

Pang Gesture Recognizer stop

want to use a UIView like a UIScrollView for more reason, i create a Action with PanGestureRecognizer that's the simple code:
- (IBAction)moveUp:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.containerView];
recognizer.view.center = CGPointMake(recognizer.view.center.x, recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
But i want to stop dragging when my containerView bottom is in exact position, follow the screens:
This is the full viewController, all button you see is inside a containerView
This is when i open the application:
The header image is in a ViewController below and all button you see is inside a containerView, now i want to fix this position with absolute y, my containerView can't drag down.
Now the function can't stop, when i dragUp this is what happen:
My containerView can drag over the end of the containerView and i don't want that is possible, i want block the drag up in the end of containerView like this screen:
And when i drag down again i want to stop drag in starter position (screen 1).
Thanks for any idea.
What you are trying to achieve sounded fun to me at first glance, so I wanted to give it a try.
I modified your gesture recognizer's call back to the following:
- (IBAction)panned:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:yourContainerView];
CGFloat yWithinBounds = 0.0f;
if(recognizer.view.center.y + (recognizer.view.bounds.size.height / 2.0f) > yourContainerView.bounds.size.height)
{
yWithinBounds = yourContainerView.bounds.size.height - (recognizer.view.bounds.size.height / 2.0f);
[UIView animateWithDuration:1.0f animations:^
{
recognizer.view.center = CGPointMake(recognizer.view.center.x, yWithinBounds);
}];
}
else if(recognizer.view.center.y - (recognizer.view.bounds.size.height / 2.0f) < yourContainerView.bounds.origin.y)
{
yWithinBounds = yourContainerView.bounds.origin.y + (recognizer.view.bounds.size.height / 2.0f);
[UIView animateWithDuration:1.0f animations:^
{
recognizer.view.center = CGPointMake(recognizer.view.center.x, yWithinBounds);
}];
}
else
{
recognizer.view.center = CGPointMake(recognizer.view.center.x, recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0.0f, 0.0f) inView:self.view];
}
}
In the first if, we are checking if the current center point of your contained view + half the size of it is going out of bounds of the containing view on the bottom; if it is, instead of simply disallowing it to go further, I took the liberty of putting this in an animation block to animate the contained view back to within the containing view so that it looks a bit better visually.
In the else-if, we are doing the same except this time for the top of the containing view - we are checking if the contained view is going out of bounds on top; if it is, we animate it back within the containing view.
If the else clause is reached, that would mean the contained view is within the containing view's bounds - here, we simply translate it like you originally do.
Here is how I set up the views as test:
yourContainerView = [[UIView alloc] initWithFrame:self.view.bounds];
yourContainerView.backgroundColor = [UIColor brownColor];
[self.view addSubview:yourContainerView];
UIView *viewWithinContainer = [[UIView alloc] initWithFrame:(CGRect){{yourContainerView.bounds.origin.x, yourContainerView.bounds.origin.y}, yourContainerView.bounds.size.width, 50.0f}];
viewWithinContainer.backgroundColor = [UIColor yellowColor];
[yourContainerView addSubview:viewWithinContainer];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panned:)];
[viewWithinContainer addGestureRecognizer:panGesture];
Feel free to adjust the height (i.e., 50.0f to other values) of the view (hereby: the viewWithinContainer) within the container; the way the code is set up, you would not need to change any other values else where even if you change the height.
You could also change the animation duration to your need.
I am moving the thing up and down in my simulator at the moment; looks simple but fun to play around.
Update after question update:
Your new update seems to differ somewhat greatly from what you originally asked, but here is the solution:
The complexity is direction. There are other ways to find out; but I am using mine here, through which we don't need to create another variable.
If you are using storyboard, please set up an IBOutlet for your container view, because we would like to obtain the following values (carry out the following in initWithFrame and initWithCoder):
p1 = viewWithinContainer.center.y;
p2 = viewWithinContainer.center.y - (yourContainerView.bounds.size.height - viewWithinContainer.bounds.size.height);
Please set up p1 and p2 as ivars:
CGFloat p1;
CGFloat p2;
Here is the updated method for a contained view larger than the containing view:
- (IBAction)panned:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:yourContainerView];
if(translation.y < 0)
{
if(recognizer.view.center.y + (recognizer.view.bounds.size.height / 2.0f) + translation.y > yourContainerView.bounds.size.height) {
recognizer.view.center = CGPointMake(recognizer.view.center.x, recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
else
{
recognizer.view.center = CGPointMake(recognizer.view.center.x, p2);
[recognizer setTranslation:translation inView:self.view];
}
}
else if(translation.y > 0)
{
if(recognizer.view.center.y - (recognizer.view.bounds.size.height / 2.0f) + translation.y < yourContainerView.bounds.origin.y)
{
recognizer.view.center = CGPointMake(recognizer.view.center.x, recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
else
{
recognizer.view.center = CGPointMake(recognizer.view.center.x, p1);
[recognizer setTranslation:translation inView:self.view];
}
}
}
The method will ensure that, as you scroll down, the top of the contained view will stop at the edge of the container on top; and as you scroll up, at the edge of the container on the bottom.
I ran this in the simulator; and japp, it has been fun doing this. Enjoy. :-)

Make UIView stay on screen

I make a UIView that is movable by the user on the screen, it is a round rectangle that is 40x40, and the screen that it is on has a nav bar at the top and right now it can be moved under the nav bar, and then it is gone and can't come back.
Is there a way to make it so that it can't be moved off the screen?
I make it moveable like this:
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer;
- (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];
}
So my question is how can I make it so it can be moved off the screen.
Thanks for the help.

Dynamically change view constraint when related view is dragged

I have two views that overlay each other. When i drag viewA upward, viewB becomes visible. When viewAis dragged all the way to the top, these two views are lined up perfectly so that viewA.bottom == viewB.top. This is accomplished by using autolayout/constraints and works really nice on 4" screen, but the alignment breaks for 4,7" and 5,5" screens.
I need to adjust the height or the top-space-constraint on viewB at runtime so it matches the viewA.bottom value.
So far I've been playing around with KVO for listening on viewB.frame changes (smelly) and manipulated the scrollview frame directly, but without luck. The following code snippet is the most promising, but I can't make it to work in "realtime". recognizer.view is the infoCenterView.
UIView *infoCenterView; // viewA
UIScrollView *scrollView; // viewB
UITableView *tableView; // child of viewB
NSLayoutConstraint *scrollViewTopSpaceConstraint;
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint center = recognizer.view.center;
CGPoint translation = [recognizer translationInView:self.view];
if (recognizer.state == UIGestureRecognizerStateChanged) {
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
center.y += translation.y;
// Don't allow translation outside screen frame
if (center.y < self.yMin || center.y > self.yMax)
return;
recognizer.view.center = center; // (1)
self.scrollViewTopSpaceConstraint.constant += translation.y; // (2)
}
else if (recognizer.state == UIGestureRecognizerStateEnded) {
if (center.y > self.yMin && center.y < ((self.yMax + self.yMin) / 2)) {
center.y = self.yMin; // Move to top
}
else if (center.y >= ((self.yMax + self.yMin) / 2) && center.y < self.yMax) {
center.y = self.yMax; // Move to bottom
}
[UIView animateWithDuration:0.2
delay:0
options:UIViewAnimationOptionTransitionNone
animations:^{
recognizer.view.center = center;
}
completion:^(BOOL finished){
// (3)
self.scrollViewTopSpaceConstraint.constant = 200;
}];
}
// (1) and (2)
If both of these lines is executed, only the tableView height is adjusted in "realtime" and infoCenterView will not move. However, if (2) is commented out, infoCenterView can be moved.
// (3)
If (2) is commented out and (3) is executed, and if infoCenterView is dragged to the top of the screen, it will unintentionally drop to the bottom and tableView height is properly set.
What is going on here? Ideas are very welcome!

Limit Pan Gesture with Pinch Gesture

I'm creating a game in Spritekit and am using the Pan Gesture with the Pinch Gesture in order to view a map. Currently I am limiting the zoom of the Pinch Gesture to 2.0 scale. I'm trying to limit the Pan Gesture to the bounds of the screen even when zoomed. The map is larger than the screen and I only want the user to be able to pan around until the outer edge of the map hits the outer edge of the screen, even when zoomed. Here is my amateur way of trying to handle the situation:
-(void) handlePanFrom:(UIPanGestureRecognizer*)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
[recognizer setTranslation:CGPointZero inView:recognizer.view];
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
CGPoint translation = [recognizer translationInView:recognizer.view];
translation = CGPointMake(-translation.x, translation.y);
CGPoint desiredPos = CGPointSubtract(_mapNode.position, translation);
NSLog(#"Moving map to x: %f y: %f", desiredPos.x, desiredPos.y);
NSLog(#"Map node position x: %f y: %f", _mapNode.position.x, _mapNode.position.y);
NSLog(#"Map scale is %f", mapScale);
NSLog(#"Map size is x: %f y: %f", _mapNode.map.frame.size.width, _mapNode.map.frame.size.height);
if (desiredPos.y <= (mapScale * 300) && desiredPos.y >= ((1/mapScale) * 200)) {
_mapNode.position = CGPointMake(_mapNode.position.x, desiredPos.y);
[recognizer setTranslation:CGPointZero inView:recognizer.view];
}
if (desiredPos.x <= (mapScale * 250) && desiredPos.x >= ((1/mapScale) * 77)) {
_mapNode.position = CGPointMake(desiredPos.x, _mapNode.position.y);
[recognizer setTranslation:CGPointZero inView:recognizer.view];
}
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
}
}
I set a scale iVar when zooming to get the scale of the node. I should be able to use the sizes of the nodes (_mapNode.map is an SKSpriteNode) and the screen size to get the panning I want.
I thought I could do scale*xMax and (1/scale)*xMin (also with y position) but that doesn't seem to work. I would love to not have hard numbers in there (300, 200, etc) and use the sizing of the nodes/screen/etc to limit the panning. Any help is appreciated! Thanks!
Okay this is what I did for future reference for people.
I first created two iVars: one for the initial x position of the node and one for the initial y position. I then calculated a 'movable distance' by taking: sizeOfNode * 0.5 * scale and subtracting screenSize * 0.5. As long as desiredLocation <= (initialPos + movableDistance) and desiredLocation >= (initialPos - moveableDistance) then you could change the position of the node. You can do this for both x and y.

UIGestureRecognizer to Drag UIView

I'm trying to build a GestureRecognizer to drag a UIView from right to left. If the user drag the View to the half of the screen, the View will be automatically dragged into the left corner to discard it, but if the user do not drag up to half the screen it will return to the corner right of the screen.
Little lost here, any tutorials or something?
Thanks
EDIT : Heres some code, its a UIImageView, inside a UIScrollView, i need to drag the entire scroll or just the image inside it :
_myScroll = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.window.bounds.size.width, self.window.bounds.size.height)]; [_myScroll setContentSize:CGSizeMake(self.window.bounds.size.width , 1300)];
_tutorial = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.window.bounds.size.width, 1300)]; [_tutorial setImage:[UIImage imageNamed:#"Default"]];
_tutorial.contentMode = UIViewContentModeScaleAspectFit; [_myScroll addSubview:_tutorial];
_tutorial.userInteractionEnabled = YES;
UIPanGestureRecognizer * recognizer = [[UIPanGestureRecognizer alloc]initWithTarget:_tutorial action:#selector(handlePan:)];
[_tutorial addGestureRecognizer:recognizer];
[self.window addSubview:_myScroll];
And the method i try to drag the UIImageView
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:_myScroll];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:_myScroll];
}
Have a look over here. This is a UIGestureRecognizer tutorial which will give you the basics to handle pinch and pan gestures. Once you've learnt the basics, it's really easy to accomplish what you want. All you need is to check the position of the view once the pan is completed to send it to the correct position. Have a look at this other tutorial on UIScrollViews, it may help you find your way through the second part.
Both tutorials come from raywenderlich.com, probably the most useful iOS tutorials site I know.
Here is some code on your handlePan: method you can work on :
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:_myScroll];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
if (recognizer.state == UIGestureRecognizerStateEnded) {
// Check here for the position of the view when the user stops touching the screen
// Set "CGFloat finalX" and "CGFloat finalY", depending on the last position of the touch
// Use this to animate the position of your view to where you want
[UIView animateWithDuration: aDuration
delay: 0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
CGPoint finalPoint = CGPointMake(finalX, finalY);
recognizer.view.center = finalPoint; }
completion:nil];
}
[recognizer setTranslation:CGPointMake(0, 0) inView:_myScroll];
}
I won't give you the perfect answer here, you'll have to work on the code to find a way to make it work as you want it to.
Here is one last tip. You can do some kind of "smooth" animation by using a slideFactor. It will help your animation being more "realistic". Work on this code, that you add right at the beginning of the "if":
CGPoint velocity = [recognizer velocityInView:self.view];
CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
CGFloat slideMult = magnitude / 200;
float slideFactor = 0.1 * slideMult; // Increase for more slide
Then in UIView animateWithDuration:, use slideFactor as the duration.

Resources