Pang Gesture Recognizer stop - ios

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. :-)

Related

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!

Limiting my UIPanGestureRecognizer

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];
}
}

How to restrict a moveable view by Pan gesture

I have a UIImageView which is moveable via a pan gesture.
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.photoMask addGestureRecognizer:pan];
I would like to restrict the area this can be moved on screen. Rather than the user be able to drag the view right to the side of the screen, I want to restrict it by a margin of some sort. How can I do this?
Also, how is this then handled when rotated?
EDIT ---
#pragma mark - Gesture Recognizer
-(void)handlePan:(UIPanGestureRecognizer *)gesture {
NSLog(#"Pan Gesture");
gesture.view.center = [gesture locationInView:self.view];
}
This is my current method to handle the pan. What I need to do is continue to move the imageview by the center point and also restrict its movement when close to the edge of the screen by 50 for example.
One possible solution to this is in your handlePan method, check the location of the point on the screen, and only commit the change if it is within the bounds you wish to restrict it to.
For ex.
-(void) handlePan:(UIGestureRecognizer*)panGes{
CGPoint point = [panGes locationInView:self.view];
//Only allow movement up to within 100 pixels of the right bound of the screen
if (point.x < [UIScreen mainScreen].bounds.size.width - 100) {
CGRect newframe = CGRectMake(point.x, point.y, theImageView.frame.size.width, theImageView.frame.size.height);
theImageView.frame = newframe;
}
}
I believe this would also correctly handle any screen rotation
EDIT
To move your image view by the center of its frame, the handlePan method could look something like this.
-(void)handlePan:(UIPanGestureRecognizer *)gesture {
CGPoint point = [gesture locationInView:self.view];
//Only allow movement up to within 50 pixels of the bounds of the screen
//Ex. (IPhone 5)
CGRect boundsRect = CGRectMake(50, 50, 220, 448);
if (CGRectContainsPoint(boundsRect, point)) {
imgView.center = point;
}
}
Check whether the point is within your desired bounds, and if so, set the center of your image view frame to that point.
I'm not sure if I'm being over-simplistic here but I think you can accomplish this by using an if clause.
-(void)handlePan:(UIPanGestureRecognizer*)gesture {
UIImageView *viewToDrag = gesture.view; // this is the view you want to move
CGPoint translation = [gesture translationInView:viewToDrag.superview]; // get the movement delta
CGRect movedFrame = CGRectOffset(viewToDrag.frame, translation.x, translation.y); // this is the new (moved) frame
// Now this is the critical part because I don't know if your "margin"
// is a CGRect or maybe some int values, the important thing here is
// to compare if the "movedFrame" values are in the allowed movement area
// Assuming that your margin is a CGRect you could do the following:
if (CGRectContainsRect(yourPermissibleMargin, movedFrame)) {
CGPoint newCenter = CGPointMake(CGRectGetMidX(movedFrame), CGRectGetMidY(movedFrame));
viewToDrag.center = newCenter; // Move your view
}
// -OR-
// If you have your margins as int values you could do the following:
if ( (movedFrame.origin.x + movedFrame.size.width) < 50) {
CGPoint newCenter = CGPointMake(CGRectGetMidX(movedFrame), CGRectGetMidY(movedFrame));
viewToDrag.center = newCenter; // Move your view
}
}
You'll probably have to adapt this to meet your specific needs.
Hope this helps!
Here is the answer in Swift 4 -
Restrict the view's movement to superview
#objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer)
{
// Allows smooth movement of stickers.
if gestureRecognizer.state == .began || gestureRecognizer.state == .changed
{
let point = gestureRecognizer.location(in: self.superview)
if let superview = self.superview
{
let restrictByPoint : CGFloat = 30.0
let superBounds = CGRect(x: superview.bounds.origin.x + restrictByPoint, y: superview.bounds.origin.y + restrictByPoint, width: superview.bounds.size.width - 2*restrictByPoint, height: superview.bounds.size.height - 2*restrictByPoint)
if (superBounds.contains(point))
{
let translation = gestureRecognizer.translation(in: self.superview)
gestureRecognizer.view!.center = CGPoint(x: gestureRecognizer.view!.center.x + translation.x, y: gestureRecognizer.view!.center.y + translation.y)
gestureRecognizer.setTranslation(CGPoint.zero, in: self.superview)
}
}
}
}
If you want more control over it, match restrictByPoint value to your movable view's frame.
- (void)dragAction:(UIPanGestureRecognizer *)gesture{
UILabel *label = (UILabel *)gesture.view;
CGPoint translation = [gesture translationInView:label];
if (CGRectContainsPoint(label.frame, [gesture locationInView:label] )) {
label.center = CGPointMake(label.center.x,
label.center.y);
[gesture setTranslation:CGPointZero inView:label];
}
else{
label.center = CGPointMake(label.center.x,
label.center.y + translation.y);
[gesture setTranslation:CGPointZero inView:label];
}
}

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.

Restricting UIPanGestureRecognizer movement

I have a UIView object that can be dragged around using UIPanGestureRecognizer, but I only want it to be able to move up 3/4 of the screen. I don't want to it be clipped, but to get to a certain point and not be able to be dragged any further. What I have so far allows it to only move on the Y axis (which is desired).
- (IBAction)panGesture:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
}
Thanks for any help.
So just check whether the new Y coordinate would be too small. Don't change the view if it would be too small:
- (IBAction)panGesture:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:self.view];
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
CGPoint center = recognizer.view.center;
center.y += translation.y;
if (center.y < self.yMin)
return;
recognizer.view.center = center;
}
Its work fine for me.
CGPoint currentTouchPoint = [gesture translationInView:self.bottomView];
if (fabsf(currentTouchPoint.x) > fabsf(currentTouchPoint.y)) {
// avoid horizontal movement of pan geuture.
return;
}
thanks,
Naveen Shan
Implement the following gesture delegate and check your condition inside it. Returning YES or NO from this delegate will make the gesture active and inactive.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;

Resources