Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I have a UILabel which i added a pan gesture recognizer to and I also have a trashcan image on my view using the UIImage view. I want to delete the UILabel from my program view every time i drag the UILabel to the trashcan image.
I assume you want to do something like this:
I'll show you how to implement that.
We're going to need an outlet for the label and an outlet for the trashcan view:
#interface ViewController ()
#property (strong, nonatomic) IBOutlet UIImageView *trashView;
#property (strong, nonatomic) IBOutlet UILabel *label;
#end
Connect these to your label and your trashcan view. We'll also need two instance variables:
#implementation ViewController {
CGPoint labelOriginalCenter;
BOOL trashIsShowingPendingDropAppearance;
}
We need to save the original position of the label, so we can animate it back there if the drag is cancelled:
- (void)viewDidLoad {
[super viewDidLoad];
labelOriginalCenter = self.label.center;
}
Now let's make the action for the pan gesture recognizer. We need to move the label based on the gesture. Then we need to take action based on the state of the gesture.
- (IBAction)labelWasDragged:(UIPanGestureRecognizer *)recognizer {
[self moveLabelForDrag:recognizer];
switch (recognizer.state) {
case UIGestureRecognizerStateChanged:
[self labelDragDidChange:recognizer];
break;
case UIGestureRecognizerStateEnded:
[self labelDragDidEnd:recognizer];
break;
case UIGestureRecognizerStateCancelled:
[self labelDragDidAbort:recognizer];
break;
default:
break;
}
}
To move the label, we change its center by the gesture's translation. We also reset the gesture's translation to zero each time it changes.
- (void)moveLabelForDrag:(UIPanGestureRecognizer *)sender {
CGPoint translation = [sender translationInView:self.label];
[sender setTranslation:CGPointZero inView:self.label];
CGPoint center = self.label.center;
center.x += translation.x;
center.y += translation.y;
self.label.center = center;
}
If the gesture changed, we want to update the appearance of the trash can based on whether the touch is over the trash can:
- (void)labelDragDidChange:(UIPanGestureRecognizer *)recognizer {
if ([self dragIsOverTrash:recognizer]) {
[self updateTrashAppearanceForPendingDrop];
} else {
[self updateTrashAppearanceForNoPendingDrop];
}
}
If the gesture ended, we want to throw away the label, or abort the drag, based on whether the touch was over the trash can when the gesture ended:
- (void)labelDragDidEnd:(UIPanGestureRecognizer *)recognizer {
if ([self dragIsOverTrash:recognizer]) {
[self dropLabelInTrash];
} else {
[self abortLabelDrag];
}
}
If the gesture was cancelled, we want to abort the drag:
- (void)labelDragDidAbort:(UIPanGestureRecognizer *)recognizer {
[self abortLabelDrag];
}
To detect whether the gesture's touch is over the trash can, we ask the gesture recognizer for its location in the trash view's coordinate system. Then we ask the trash view whether that location is inside the trash view's bounds.
- (BOOL)dragIsOverTrash:(UIPanGestureRecognizer *)recognizer {
CGPoint pointInTrash = [recognizer locationInView:self.trashView];
return [self.trashView pointInside:pointInTrash withEvent:nil];
}
We could update the trash can's appearance in lots of different ways. Here, we'll make the trash can wiggle while the drag is over the trash can:
- (void)updateTrashAppearanceForPendingDrop {
if (trashIsShowingPendingDropAppearance)
return;
trashIsShowingPendingDropAppearance = YES;
self.trashView.transform = CGAffineTransformMakeRotation(-.1);
[UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat animations:^{
self.trashView.transform = CGAffineTransformMakeRotation(.1);
} completion:nil];
}
When the drag moves off the trash can, we need to make the trash can stop wiggling:
- (void)updateTrashAppearanceForNoPendingDrop {
if (!trashIsShowingPendingDropAppearance)
return;
trashIsShowingPendingDropAppearance = NO;
[UIView animateWithDuration:0.15 animations:^{
self.trashView.transform = CGAffineTransformIdentity;
}];
}
When we want to drop the label in the trash, we need to do several things. We need to stop the trash can wiggling, we need to animate the label into the trash can, and when the animation ends, we need to remove the label entirely.
- (void)dropLabelInTrash {
[self updateTrashAppearanceForNoPendingDrop];
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
self.label.center = self.trashView.center;
self.label.transform = CGAffineTransformMakeScale(0.1, 0.1);
} completion:^(BOOL finished) {
[self.label removeFromSuperview];
self.label = nil;
}];
}
If the drag was aborted, we need to stop the trash can wiggling and animate the label back to its original position:
- (void)abortLabelDrag {
[self updateTrashAppearanceForNoPendingDrop];
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.label.center = labelOriginalCenter;
} completion:nil];
}
That's all!
The label can be gotten from the gesture recognizer's view property. As far as the trash can, you could use CGRectIntersectsRect to determine if your dragged label's rect overlaps the trashcan's rect. Something like this within the gesture recognizer's action method:
- (IBAction)handlePan:(UIPanGestureRecognizer *)sender {
// your other panning code here
if (sender.state == UIGestureRecognizerStateEnded){
if (CGRectIntersectsRect(sender.view.frame, trashcanImageView.frame))
[ sender.view removeFromSuperview];
}
}
Related
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.
I have a problem with my function.
[UIView animateWithDuration:5 animations:^{
//set end coordinates for marker = MKAnnotationPoint
[self.followDriverMarker setCoordinate:CLLocationCoordinate2DMake(latitde, longitude)];
//set end coordinate for camera/map
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(latitde, longitude) animated:NO];
}
completion:^(BOOL finished) {
if (finished) {
if (self.currentPosition < [self.followDriverList count] - 2) {
self.currentPosition++;
//start next one
[self runAnimation];
} else {
//animation is finished
//TODO
self.isAnimationRunning = NO;
}
}
}];
The function will look in a List if there are locations left. If so it will run again. That works. The only problem is. If the animation is running. There is no interaction possible with the mapView. The other problem is that i cannot find how to cancel,stop or remove my Animation.
If I put:
[self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(latitde, longitude) animated:NO];
inside the the completionBlock, I have interaction with map. But I don't want to do that because I want to animate it the same time. Also here I can't find a way to cancel the animation.
Please don't say removeAllAnimation. This is not working.
I believe this answers your question.
Basically you commit a new animation on the same target with a short duration. By setting the setAnimationBeginsFromCurrentState flag you prevent weird jumps.
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 have a video player that has a standard toolbar. The toolbar is dismissed by a swipe down gesture. I also have a view (a panel really) that can appear directly above the toolbar and is also dismissed by a swipe down gesture. When both the panel and the toolbar are open, one swipe down gesture should dismiss the panel, a second will dismiss the toolbar. Problem is that when the swipe gestures occur quickly back-to-back (before the panel animation completes) then the toolbar animation jitters.
- (void)handleSwipe:(UISwipeGestureRecognizer *)gestureRecognizer
{
UISwipeGestureRecognizerDirection direction = [gestureRecognizer direction];
if (direction == UISwipeGestureRecognizerDirectionDown) {
if (![toolbar isHidden]) {
// Only dismiss the bottom panel if it is open
if (_selectedSegmentIndex != UISegmentedControlNoSegment) {
_selectedSegmentIndex = UISegmentedControlNoSegment;
[bottomPanelView dismissPanel];
} else {
CGRect tempRect = CGRectMake(0, self.view.frame.size.height, toolbar.frame.size.width, toolbar.frame.size.height);
[UIView animateWithDuration:0.25f
animations:^{
// Move the toolbar off the screen.
toolbar.frame = tempRect;
}
completion:^(BOOL finished) {
[toolbar setHidden:YES];
}];
}
}
}
}
[bottomPanelView dismissPanel] is in a separate class and is not aware of the class that calls it. It has the follow animation...
[UIView animateWithDuration:self.panelAnimationDuration
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
// slideOutLocation is off the screen
self.view.frame = slideOutLocation;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
[self removeFromParentViewController];
self.panelActive = NO;
}];
So basically, the dismissPanel animation is still running when the animation to dismiss the toolbar begins. When performing a double swipe in slow motion in the simulator, the first animation looks fine, but the toolbar animation is jittery.
I know how to nest animations in the completion block, but that cannot be done here since dismissing both the panel and the toolbar is not always what is wanted. Also, the dismissPanel code is handled elsewhere and is not in control of the toolbar.
Is there a way to allow multiple animation blocks to run simultaneously without putting the completion block? Let me know if any clarification is needed! Thanks!
I wonder if the problem might have to do with auto layout (setting frames while auto layout is on causes problems). I tried a simple test of animating a view and a tool bar off the bottom of the screen by animating their constraint constants, and the animation looked fine. I made IBOutlets to their respective bottom constraints (called viewBottomCon and toolBarBottomCon).
- (void)viewDidLoad {
[super viewDidLoad];
self.isFirstSwipe = YES;
}
-(IBAction)downSwipe:(UISwipeGestureRecognizer *)sender {
if (self.isFirstSwipe) {
self.viewBottomCon.constant = -52;
self.isFirstSwipe = NO;
[UIView animateWithDuration:5 animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}else if (!self.isFirstSwipe) {
self.toolBarBottomCon.constant = -44;
[UIView animateWithDuration:3 animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
}
This is a simpler setup than yours, but I think it should work in your case too.
I want to create an effect where a button's image slides in / out of button rect, the final effect should look like this:
sliding buttons
So the first one just started it's "transition", the second is half way through and the last one is fully slided out.
To achieve this, I set the button's clipsToBounds property to YES and then animate button's imageView:
- (void)setupButtons
{
self.buttonsYOffset = -60.0;
self.buttonsOriginalY = self.twitterButton.imageView.center.y;
self.twitterButton.clipsToBounds = YES;
self.twitterButton.imageView.center = CGPointMake(self.twitterButton.imageView.center.x, self.buttonsOriginalY + self.buttonsYOffset);
}
and:
- (void)show
{
[UIView animateWithDuration:0.2 delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^ {
self.twitterButton.imageView.alpha = 1.0;
self.twitterButton.imageView.center = CGPointMake(self.twitterButton.imageView.center.x, self.buttonsOriginalY);
}
completion:^(BOOL success) {
}
];
}
- (void)hide
{
[UIView animateWithDuration:0.2 delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^ {
self.twitterButton.imageView.center = CGPointMake(self.twitterButton.imageView.center.x, self.buttonsOriginalY + self.buttonsYOffset);
}
completion:^(BOOL success) {
}
];
}
Everything works fine if I fire this code when button is in "inactive" state (not tapped) - it slides in and out just fine.
The problem starts when I fire hide() method immediately after button has been tapped - It does not slide out then - just highlights for a second, and it's image stays in "opened" position for a while, then skips to "closed" position immediately.
It seems that button's imageView is used only when button is in its default inactive state? Can it be easily fixed?