How to animate constraint change smoothly with pan gesture in iOS?
I am trying to develop a screen, where a view is at bottom of the screen. And I've added pan gesture to that view. On dragging that view I want change top constraint of that view. Pan gesture is only allowed in vertical and downward direction. I have added some limit for dragging the view. It working but not smoothly. How to animate constraint change smoothly with pan gesture? Here is my code.
- (void)handleGesture:(UIPanGestureRecognizer *)sender
{
CGPoint velocity = [sender velocityInView:_locationContainer];
[sender setTranslation:CGPointMake(0, 0) inView:self.view];
if (fabs(velocity.y) > fabs(velocity.x)) {
NSLog(#"velocity y %f ",velocity.y * 0.13);
if(velocity.y < 0 && (self.locationDetailsTop.constant > minimumTop) )
{
NSLog(#"gesture moving Up");
self.locationDetailsTop.constant = self.locationDetailsTop.constant - fabs(velocity.y * 0.1);
}
else if (self.locationDetailsTop.constant < firstTop)
{
NSLog(#"gesture moving Bottom");
self.locationDetailsTop.constant = self.locationDetailsTop.constant + fabs(velocity.y * 0.1);
}
[self.view layoutIfNeeded];
[UIView animateWithDuration:0.1 animations:^{
[self.mapView setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.locationContainer.frame.origin.y)];
}];
}
}
This is sample image,
My screen is of the same kind like this, But on my screen, there is a map view instead of the calender view
To move view while user is touching a screen, you can use translationInView: property. You can set a translation to current constraint's value and get new value (in a handler of UIGestureRecognizerStateBegan) and change a constraint's constant in a handler of UIGestureRecognizerStateChanged:
- (void)padRecognizerStateChanged:(UIPanGestureRecognizer*)sender
{
if(sender.state == UIGestureRecognizerStateBegan)
{
[sender setTranslation:CGPointMake(0.0, [self getConstraintValue]) inView: _locationContainer];
}
else if (sender.state == UIGestureRecognizerStateChanged)
{
[self setConstraintValue: [sender translationInView:_locationContainer].y];
[self.view setNeedsLayout];
}
}
You can use velocity if you need to move view when a user raised his thumb over the screen for upward or downward movement. For example, you can implement deceleration effect.
If you want it to animate smoothly, try calling layoutIfNeeded inside the animation block like so:
[UIView animateWithDuration:0.1 animations:^{
[self.view layoutIfNeeded];
[self.mapView setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.locationContainer.frame.origin.y)];
}];
I think the original question was asking how to perform this animation smoothly. However, if the map view constraints are linked to the dragged view constraints, due to the polynomial relationship between autolayout constraints and laying out the MKMapView the computation will be quite intense and therefore there will likely be lag. I suggest disconnecting the map constraints from the dragged view constraints if the UI/UX design allows.
Related
I want to do smooth swipe animation. I just want to that swipe only can be possible when user swipe the page from the right or left border only. Middle of the page swipe should not possible.Both the swipe should be possible left to right and right to left.
I have tried lots of swipe animation sample code or demo code. But its not what I want. I want animation like this https://itunes.apple.com/in/app/clear-tasks-to-do-list/id493136154?mt=8
In this app its like when we touch the right border its swipe smoothly.Please guide me to do this animation. Thanks in advance.
Sorry for the late reply. Just saw this question.
If you want your swipe operation to happen from the edges, create 2 subviews in the far ends (left and right) of your main view and give then a width of 30 or 40.
I believe you have 2 other views popin up from left and right. So inorder to do this you need to add 2 views right on top of your main view.
Now for the left view, set it's right horizondal space constraint connecting to the main view to a value lesser than (-1)x width of the main view. For the right view set its right horizondal space constraint connecting to the main view to a value greater than the width of the main view, so that both the views are outside the main view
X stands for a value greater than or equal to the mainview's width
Add two NSLayoutConstraint variables as IBOutlet holding these 2 values.
NSLayoutConstraint *leftViewHorizondalRightPadding;
NSLayoutConstraint *rightViewHorizondalRightPadding;
Now add the UISwipeGestures to these subViews (indicated in orange).
UISwipeGestureRecognizer *leftToRightSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
[leftToRightSwipe setDirection:UISwipeGestureRecognizerDirectionRight];
[self.leftSubview addGestureRecognizer:leftToRightSwipe];
UISwipeGestureRecognizer *rightToLeftSwipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipe:)];
[rightToLeftSwipe setDirection:UISwipeGestureRecognizerDirectionLeft];
[self.rightSubview addGestureRecognizer:rightToLeftSwipe];
///Now in the swipe handler distinguish the swipe actions
-(void)handleSwipe:(UISwipeGestureRecognizer *)recognizer {
NSLog(#"Swipe received.");
if (recognizer.direction == UISwipeGestureRecognizerDirectionRight) {
//It's leftToRight
leftViewHorizondalRightPadding.constant = 0;
[UIView animateWithDuration:1
animations:^{
[self.view layoutIfNeeded];
}];
}
else {
//It's rightToLeft
rightViewHorizondalRightPadding.constant = 0;
[UIView animateWithDuration:1
animations:^{
[self.view layoutIfNeeded];
}];
}
}
}
This will make a swipe animation from left to right and right to left.
Hope this helps..
After you create the 2 swipe gesture recognisers you should set their delegates. Then use this delegate method:
UISwipeGestureRecognizer *_swipeLeft;
UISwipeGestureRecognizer *_swipeRight;
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
static const CGFloat borderWidth = 50.0f;
if(gestureRecognizer == _swipeLeft) {
return [gestureRecognizer locationInView:self].x > self.frame.size.width - borderWidth;
}
else if(gestureRecognizer == _swipeRight) {
return [gestureRecognizer locationInView:self].x < borderWidth;
}
return YES;
}
Do note that for smooth swiping/dragging you will probably need to use a pan gesture or even long press gesture recogniser rather then the swipe gesture. They are very similar except the long press takes a bit of time to begin (which is settable). If you use them you may still want to use the same delegate method. Or you can simply do all the code in the gestures target method. Try something like this:
CGPoint gestureStartPoint;
- (void)dragFromBoreder:(UIGestureRecognizer *)sender {
static const CGFloat borderWidth = 50.0f;
switch (sender.state) {
case UIGestureRecognizerStateBegan: {
CGPoint location = [sender locationInView:self];
if(location.x > borderWidth || location.x < self.frame.size.width-borderWidth) {
//break the gesture
sender.enabled = NO;
sender.enabled = YES;
}
else {
gestureStartPoint = location;
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint location = [sender locationInView:self];
CGFloat deltaX = location.x - gestureStartPoint.x;
UIView *viewToMove;
CGPoint defaultCenter;
viewToMove.center = CGPointMake(defaultCenter.x+deltaX, defaultCenter.y);
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
CGPoint location = [sender locationInView:self];
CGFloat deltaX = location.x - gestureStartPoint.x;
/*
if(deltaX > someWidth) {
show the left view
}
else if(deltaX < -someWidth) {
show the right view
}
else {
put everything back the way it was
}
*/
break;
}
default:
break;
}
}
In ios7 there is a gesture recogniser specifically for gestures beginning from the edge of the screen. You should use this.
I can't help with your "smooth" problem, because you haven't said what your current animation looks like or how you are doing it. But a pan gesture, like the one linked, which directly updates view positions, will track the user's movement much more smoothly than a swipe.
I'm developing an iOS 7 app with latest SDK.
I have an UIScrollView with three ImageViews inside it. The UIScrollView has its contentSize set to {960, 568} (each ImageView has a {320, 958}` size).
I can drag over the three images perfectly, but I want to hide the UIScrollView when user drags from right to left on the last image.
To that, I have used UIScrollViewDelegate:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x > 640)
scrollView.hidden = YES;
}
But this code hide it immediately and I want to show the following effect:
Continue dragging the image from right to left while the image fades out.
How can I do that?
If you want to have the fade effect proportional to the content offset, use this method:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x > 640)
{
scrollView.alpha = 1 / (scrollView.contentOffset.x - 639);
}
// then remove / hide the view completely at a particular offset
// ....
}
and you can change the division to make the animation more/less smooth. This is smoother:
scrollView.alpha = 2 / (scrollView.contentOffset.x - 638);
[UIView animateWithDuration:0.3 animations:^{
self.scrollView.alpha = 0.0;
} completion:^(BOOL finished) {
// remove the scrollView from your view
if(finished) {
[self.scrollView removeFromSuperview];
}
}];
Hopefully this will help
I have an UIPanGestureRecognize which I use to change the frame of a view. Is there a way to simulate the deceleration of the UIScrollView or UITableView when the gesture's state is UIGestureRecognizerStateEnded? Here is my current code:
if (panGesture.state == UIGestureRecognizerStateEnded)
{
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
self.view.frame = CGRectMake(182, 0, self.view.frame.size.width, self.view.frame.size.height);
}
completion:^(BOOL finished) {
if (finished) {
//Do something
}
}];
}
but this is not a smooth scroll. I would like something that decelerates until it stops to the point I've set. Thanks
Session 223 at WWDC 2012, "Enhancing User Experience With Scroll Views", covered a method to use a scrollview's behavior and "feel" to animate the position of a different view (without the scrollview ever actually being visible to the user).
The benefit of the method shown in the session is that your deceleration would always match UIScrollView's, now and forever.
https://developer.apple.com/videos/wwdc/2012/?id=223
You would have to come up with an algorithm of some sort to calculate where you want the view to stop at depending on the velocity of the gesture, which can be obtained like this:
CGPoint velocity = [panGesture velocityInView:panGesture.view];
From there it should just be a matter of animating your view into its calculated resting place and adding an animation to get it there. I believe UIViewAnimationOptionCurveEaseOut would be appropriate here.
i have simple UIView animation block which animates the origin of 2 views. I have a Button on a special position placed on a mapview view. so when i'd like to animate the center of the map and move the pin with the map, the map moves faster than the button. Is there a way, to speed up the animation of the button or to slow down the animation of the map? At the moment it looks like the map moves and the button jumps to his end position.
CGPoint newCenter = mapView.center;
newCenter.x -= 1;
newCenter.y -= (button.frame.size.height/2)
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationCurveEaseIn animations:^{
CGPoint screenPoint = fakePin.frame.origin;
screenPoint.x -= 5;
screenPoint.y += button.frame.size.height-4;
mapView.mapCoord = [mapView.map convertPoint:screenPoint toCoordinateFromView:self.view];
self.mapView.map.centerCoordinate = mapView.mapCoord;
[button setCenter:newCenter];
}];
Any Ideas?
Why not show the button as an annotation? Then it would move along the map.
Still, try to animate the property frame on the button instead of center. You'll need to do the calculations on your own but I think that may be the problem.
i just figured out, that the Problem was that my setCenter: was called without animating, somehow it worked with the Google Maps in ios5 (animating itself but not in ios6=
No it works!!
I'm looking to animate bubbles with text on them to slide on and off the screen. The ideal implementation for this animation is iOS's horizonatal scroll with paging enabled. I definitely want the "bounce" when I reach the end of the speech bubbles and I definetely want the bubbles to track the finger until a certain point before they will slide off the screen. I believe this is not the same as a swipe (which is just a flick in one direction).
However, the problem with the horizontal scroll is that it is optimized for a static number of images. I will be having a dynamic number of images and as far as I can tell, you cannot dynamically append images to horizontal scroller. The idea is the app dynamically adds content to the scroller as you continue to progress through it.
The scroller was easy enough to get going but I'm going to have to tear it down now. How can I get started with the gesture (I'm not sure if the standard gesture recognizers will work for me at this point) as well as the animation? I've never worked with that portion of iOS code before.
I'm not sure if I follow your question entirely, but if you want to animate the movement of something based upon a gesture, you can use a UIPanGestureRecognizer and change the center of whatever subview you want. For example, in viewDidLoad you would:
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(movePiece:)];
[whateverViewYouWantToAnimate addGestureRecognizer:panGesture];
You can then have your gesture recognizer move it where ever you want:
- (void)movePiece:(UIPanGestureRecognizer *)gestureRecognizer
{
static CGPoint originalCenter;
if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
originalCenter = [gestureRecognizer view].center;
}
else if (gestureRecognizer.state == UIGestureRecognizerStateChanged)
{
CGPoint translation = [gestureRecognizer translationInView:self.view];
gestureRecognizer.view.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y);
// if you wanted to animate both left/right and up/down, it would be:
// gestureRecognizer.view.center = CGPointMake(originalCenter.x + translation.x, originalCenter.y + translation.y);
}
else if (gestureRecognizer.state == UIGestureRecognizerStateEnded)
{
// replace this offscreen CGPoint with something that makes sense for your app
CGPoint offscreen = CGPointMake(480, gestureRecognizer.view.center.y);
[UIView animateWithDuration:0.5
animations:^{
gestureRecognizer.view.center = offscreen;
}
completion:^(BOOL finished){
// when you're done, you might want to do whatever cleanup
// is appropriate for your app (e.g. do you want to remove it?)
[gestureRecognizer.view removeFromSuperview];
}];
}
}