MKAnnotationView drag state ending animation - ios

I'm using the custom MKAnnotationView animations provided by Daniel here: Subclassing MKAnnotationView and overriding setDragState but I run into an issue.
After the pin drop animation, when I go to move the map the mkannotationview jumps back to its previous location before the final pin drop animation block is called.
It seems to me that dragState=MKAnnotationViewDragStateEnding is being called before the animation runs? How can I get around this issue and set the final point of the mkannotationview to be the point it's at when the animation ends?
#import "MapPin.h"
NSString *const DPAnnotationViewDidFinishDrag = #"DPAnnotationViewDidFinishDrag";
NSString *const DPAnnotationViewKey = #"DPAnnotationView";
// Estimate a finger size
// This is the amount of pixels I consider
// that the finger will block when the user
// is dragging the pin.
// We will use this to lift the pin even higher during dragging
#define kFingerSize 20.0
#interface MapPin()
#property (nonatomic) CGPoint fingerPoint;
#end
#implementation MapPin
#synthesize dragState, fingerPoint, mapView;
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated
{
if(mapView){
id<MKMapViewDelegate> mapDelegate = (id<MKMapViewDelegate>)mapView.delegate;
[mapDelegate mapView:mapView annotationView:self didChangeDragState:newDragState fromOldState:dragState];
}
// Calculate how much to life the pin, so that it's over the finger, no under.
CGFloat liftValue = -(fingerPoint.y - self.frame.size.height - kFingerSize);
if (newDragState == MKAnnotationViewDragStateStarting)
{
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-liftValue);
[MapPin animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateDragging;
}];
}
else if (newDragState == MKAnnotationViewDragStateEnding)
{
// lift the pin again, and drop it to current placement with faster animation.
__block CGPoint endPoint = CGPointMake(self.center.x,self.center.y-liftValue);
[MapPin animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
endPoint = CGPointMake(self.center.x,self.center.y+liftValue);
[MapPin animateWithDuration:0.1
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
if(!mapView)
[[NSNotificationCenter defaultCenter] postNotificationName:DPAnnotationViewDidFinishDrag object:nil userInfo:[NSDictionary dictionaryWithObject:self.annotation forKey:DPAnnotationViewKey]];
}];
}];
}
else if (newDragState == MKAnnotationViewDragStateCanceling)
{
// drop the pin and set the state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
}];
}
}
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
// When the user touches the view, we need his point so we can calculate by how
// much we should life the annotation, this is so that we don't hide any part of
// the pin when the finger is down.
fingerPoint = point;
return [super hitTest:point withEvent:event];
}
#end

I had the same problem, especially under iOS 8. After many hours of testing I believe that iOS keeps track of where it thinks self.center of the annotation is during the time that the state is MKAnnotationViewDragStateDragging. You need to use extreme caution if you animate self.center when handling MKAnnotationViewDragStateEnding. Read that as "I couldn't get that to work, ever."
Instead, I kept Daniel's original code when handling states MKAnnotationViewDragStateStarting and MKAnnotationViewDragStateCanceling, I animated self.center. When handling MKAnnotationViewDragStateEnding I animated self.transform instead of self.center. This maintains the actual location of the annotation and just changes how it is rendered.
This works well for me running either iOS 7.1 and iOS 8.0. Also fixed a bug in hitTest, and added some code to reselect the annotation after dragging or canceling. I think that is the default behavior of MKPinAnnotationView.
- (void)setDragState:(MKAnnotationViewDragState)newDragState animated:(BOOL)animated
{
if(mapView){
id<MKMapViewDelegate> mapDelegate = (id<MKMapViewDelegate>)mapView.delegate;
[mapDelegate mapView:mapView annotationView:self didChangeDragState:newDragState fromOldState:dragState];
}
// Calculate how much to lift the pin, so that it's over the finger, not under.
CGFloat liftValue = -(fingerPoint.y - self.frame.size.height - kFingerSize);
if (newDragState == MKAnnotationViewDragStateStarting)
{
CGPoint endPoint = CGPointMake(self.center.x,self.center.y-liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateDragging;
}];
}
else if (newDragState == MKAnnotationViewDragStateEnding)
{
CGAffineTransform theTransform = CGAffineTransformMakeTranslation(0, -liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.transform = theTransform;
}
completion:^(BOOL finished){
CGAffineTransform theTransform2 = CGAffineTransformMakeTranslation(0, 0);
[UIView animateWithDuration:0.2
animations:^{
self.transform = theTransform2;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
if(!mapView)
[[NSNotificationCenter defaultCenter] postNotificationName:DPAnnotationViewDidFinishDrag object:nil userInfo:[NSDictionary dictionaryWithObject:self.annotation forKey:DPAnnotationViewKey]];
// Added this to select the annotation after dragging.
// This is the behavior for MKPinAnnotationView
if (mapView)
[mapView selectAnnotation:self.annotation animated:YES];
}];
}];
}
else if (newDragState == MKAnnotationViewDragStateCanceling)
{
// drop the pin and set the state to none
CGPoint endPoint = CGPointMake(self.center.x,self.center.y+liftValue);
[UIView animateWithDuration:0.2
animations:^{
self.center = endPoint;
}
completion:^(BOOL finished){
dragState = MKAnnotationViewDragStateNone;
// Added this to select the annotation after canceling.
// This is the behavior for MKPinAnnotationView
if (mapView)
[mapView selectAnnotation:self.annotation animated:YES];
}];
}
}
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
// When the user touches the view, we need his point so we can calculate by how
// much we should life the annotation, this is so that we don't hide any part of
// the pin when the finger is down.
// Fixed a bug here. If a touch happened while the annotation view was being dragged
// then it screwed up the animation when the annotation was dropped.
if (dragState == MKAnnotationViewDragStateNone)
{
fingerPoint = point;
}
return [super hitTest:point withEvent:event];
}

Related

Little hiccup in the animation after UIPanGestureRecognizer state is end

I'm trying to achieve dragging of view and when the drag stops, the view slide to top and disappear. I can use UIPanGestureRecognizer to drag the view fine. But after that, I used animation to let it slide out. The problem is that after the drag, there's always a little hiccup before the view moves... I searched around and can't figure out how to solve this. Here is the code:
- (void)moveView2:(UIPanGestureRecognizer *)pan {
CGPoint delta = [pan translationInView:self.view];
CGPoint newCenter = CGPointMake(_view2.center.x, _view2.center.y + delta.y);
_view2.center = newCenter;
[pan setTranslation:CGPointZero inView:self.view];
if (pan.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [pan velocityInView:self.view];
NSLog(#"Velocity is %f, %f", velocity.x, velocity.y);
// Here is the delay
[UIView animateWithDuration:0.5 delay:0.0 usingSpringWithDamping:0.5f initialSpringVelocity:500 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_view2.center = CGPointMake(_view2.center.x, -600);
} completion:nil];
}
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIButton *fbButton;
#property (weak, nonatomic) IBOutlet UIButton *twitterButton;
#property UIView *view1;
#property UIView *view2;
#property CGPoint startPoint;
#property CGPoint endPoint;
#property CGPoint originCenter;
#property double startTime;
#property double endTime;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGRect frame = [UIScreen mainScreen].bounds;
_view1 = [[UIView alloc] initWithFrame:frame];
_view1.backgroundColor = [UIColor redColor];
[self.view addSubview:_view1];
_view2 = [[UIView alloc] initWithFrame:frame];
_view2.backgroundColor = [UIColor greenColor];
[self.view addSubview:_view2];
UIPanGestureRecognizer *pan1 = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(moveView2:)];
[_view2 addGestureRecognizer:pan1];
/*
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeUpDown:)];
[swipe setDirection:UISwipeGestureRecognizerDirectionUp];
[_view2 addGestureRecognizer:swipe];
*/
}
/*
- (void)swipeUpDown:(UISwipeGestureRecognizer *)swipe {
if (swipe.direction == UISwipeGestureRecognizerDirectionUp) {
NSLog(#"Swipe up");
[UIView animateWithDuration:0.5 animations:^{
_view2.center = CGPointMake(_view2.center.x, -600);
}];
}
}*/
- (void)moveView2:(UIPanGestureRecognizer *)pan {
CGPoint bigViewDelta = [pan translationInView:self.view];
CGPoint newCenter = CGPointMake(_view2.center.x, _view2.center.y + bigViewDelta.y);
_view2.center = newCenter;
[pan setTranslation:CGPointZero inView:self.view];
if (pan.state == UIGestureRecognizerStateEnded) {
CGPoint velocityOfPan = [pan velocityInView:self.view];
CGFloat velocityOfPanAbsolute = sqrt(velocityOfPan.x * velocityOfPan.x + velocityOfPan.y * velocityOfPan.y);
// get simple points per second
CGPoint currentPoint = _view2.center;
CGPoint finalPoint = CGPointMake(_view2.center.x, -600);
CGFloat distance = sqrt((finalPoint.x - currentPoint.x) * (finalPoint.x - currentPoint.x) + (finalPoint.y - currentPoint.y) * (finalPoint.y - currentPoint.y));
// how far to travel
CGFloat duration = 0.5;
CGFloat animationVelocity = velocityOfPanAbsolute / (distance / duration);
[UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:animationVelocity options:0 animations:^{
_view2.center = CGPointMake(_view2.center.x, -600);
} completion:nil]; }
}
- (void)viewDidAppear:(BOOL)animated {
/*
[UIView transitionFromView:_view2 toView:_view1 duration:2 options:UIViewAnimationOptionCurveEaseInOut completion:^(BOOL finished) {
}];
*/
/*
// get the view that's currently showing
UIView *currentView = _view2;
// get the the underlying UIWindow, or the view containing the current view view
UIView *theWindow = [currentView superview];
// remove the current view and replace with myView1
[currentView removeFromSuperview];
//[theWindow addSubview:newView];
// set up an animation for the transition between the views
CATransition *animation = [CATransition animation];
[animation setDuration:0.5];
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromLeft];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[theWindow layer] addAnimation:animation forKey:#"SwitchToView1"];
*/
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Do you mean a delay before the animation takes place? Hmm, I'm not seeing anything that would cause that, but the parameters to animateWithDuration are very, very curious.
First, UIViewAnimationOptionCurveEaseInOut doesn't makes sense to me, because that's used to say "start slowly, pick up speed, and stop slowly, too". That's fine with a standard animation where you don't want the starting of the animation to be too jarring. But it doesn't make sense in this context because you presumably want it not to start slowly, but rather to use whatever velocity you initially provided. If I used any option, I'd use UIViewAnimationOptionCurveEaseOut. Even that doesn't make sense, because it's the usingSpringWithDamping parameter that dictates what happens at the end of this animation. (And, on top of all of that, you're dragging it off screen, so I'm unclear why you care about the exit curve anyway.)
Second, the initialSpringVelocity is doesn't seem at all right to me, either. As the documentation for this parameter says:
The initial spring velocity. For smooth start to the animation, match this value to the view’s velocity as it was prior to attachment.
A value of 1 corresponds to the total animation distance traversed in one second. For example, if the total animation distance is 200 points and you want the start of the animation to match a view velocity of 100 pt/s, use a value of 0.5.
So, 500 means that you want to travel 500 times the distance between the current location and the final location in one second. That's absurdly fast.
I would try to match the initial velocity with the final velocity of gesture. So, calculate the absolute velocity of the gesture, and divide that by the distance to be traveled divided by the duration of the animation.
Third, if you're dragging it off screen, I'm not sure why you're using a usingSpringWithDamping of 0.5. That's springy. And why would you want it to spring at all you're using as it stops off screen and you can't see it anyway? It strikes me that any value less than 1.0 risks the possibility of the view springing back into the visible screen during one or more of the initial springs. I wouldn't use anything less than 1.0 here if animating off screen.
Pulling that all together, I get something like:
CGPoint velocityOfPan = [pan velocityInView:self.view];
CGFloat velocityOfPanAbsolute = hypof(velocityOfPan.x, velocityOfPan.y); // get simple points per second
CGPoint currentPoint = _view2.center.x;
CGPoint finalPoint = CGPointMake(_view2.center.x, -600);
CGFloat distance = hypof(finalPoint.x - currentPoint.x, finalPoint.y - currentPoint.y); // how far to travel
CGFloat duration = 0.5;
CGFloat animationVelocity = velocityOfPanAbsolute / (distance / duration);
[UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:1.0 initialSpringVelocity:animationVelocity options:0 animations:^{
_view2.center = CGPointMake(_view2.center.x, -600);
} completion:nil];
Having said all of that, while it's possible animateWithDuration is getting confused by the combination of "ease in-ease out" and the dramatic initialSpringVelocity, I don't actually see why that would cause a "hiccup".
Are you doing anything else immediately after this animation? Anything that might be blocking the main queue?
Do you mean the little delay before the gesture is first recognized? Yes, that's a feature of pan gesture recognizer. (I think it's trying to determine whether it's really a pan vs long press vs, etc.).
You can get around this by using a UILongPressGestureRecognizee with minimumPressDuration of zero (though you have to calculate translationInView yourself).

Animation from Pan Gesture begins from initial state instead of current state

In my application I have a view that sits on top of another view. The user is able to swipe the top view to the right, which fades it out and "fades in" the back view.
When the user pans to the right and lets go, the animation kicks in and it should translate from its current position to the new off screen position. The problem is that the animation instead snaps the view back to its starting point and then does the animation.
Here is my code for the pan gesture and animation:
- (void)handlePanGesture:(UIPanGestureRecognizer *)recognizer
{
CGPoint translation = [recognizer translationInView:recognizer.view];
CGPoint velocity = [recognizer velocityInView:recognizer.view];
switch (recognizer.state) {
case UIGestureRecognizerStateBegan: {
[recognizer setTranslation:CGPointMake(self.slidingView.frame.origin.x, 0) inView:recognizer.view];
break;
}
case UIGestureRecognizerStateChanged: {
[self.slidingView setTransform:CGAffineTransformMakeTranslation(MAX(0,translation.x), 0)];
CGFloat percentage = fmaxf(0,(translation.x/recognizer.view.bounds.size.width));
[self.backgroundBlurImageView setAlpha:1-percentage];
[self.slidingView setAlpha:1-percentage];
[self.backgroundImageView setTransform:CGAffineTransformMakeScale(1 + (percentage * 0.05), 1 + (percentage * 0.05))];
[self.backgroundBlurImageView setTransform:CGAffineTransformMakeScale(1 + (percentage * 0.05), 1 + (percentage * 0.05))];
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
if (velocity.x > 5.0 || (velocity.x >= -1.0 && translation.x > kMenuViewControllerMinimumPanDistanceToOpen * self.slidingView.bounds.size.width)) {
CGFloat transformedVelocity = velocity.x/ABS(self.slidingView.bounds.size.width - translation.x);
CGFloat duration = 0.66;
[self showViewAnimated:YES duration:duration initialVelocity:transformedVelocity];
} else {
[self hideViewAnimated:YES];
}
}
default:
break;
}
}
- (void)showViewAnimated:(BOOL)animated duration:(CGFloat)duration
initialVelocity:(CGFloat)velocity;
{
// animate
__weak typeof(self) blockSelf = self;
[UIView animateWithDuration:animated ? duration : 0.0 delay:0
usingSpringWithDamping:0.8f initialSpringVelocity:velocity options:UIViewAnimationOptionAllowUserInteraction animations:^{
blockSelf.slidingView.transform = CGAffineTransformMakeTranslation(blockSelf.slidingView.bounds.size.width, 0);
[blockSelf.backgroundBlurImageView setTransform:CGAffineTransformMakeScale(1.05, 1.05)];
[blockSelf.backgroundImageView setTransform:CGAffineTransformMakeScale(1.05, 1.05)];
[blockSelf.backgroundBlurImageView setAlpha:0];
[blockSelf.slidingView setAlpha:0];
} completion:^(BOOL finished) {
blockSelf.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapOnAaron:)];
[blockSelf.view addGestureRecognizer:self.tapGestureRecognizer];
}];
}
- (void)hideViewAnimated:(BOOL)animated;
{
__weak typeof(self) blockSelf = self;
[UIView animateWithDuration:0.3f animations:^{
blockSelf.slidingView.transform = CGAffineTransformIdentity;
[blockSelf.backgroundBlurImageView setTransform:CGAffineTransformIdentity];
[blockSelf.backgroundImageView setTransform:CGAffineTransformIdentity];
[blockSelf.backgroundBlurImageView setAlpha:1];
[blockSelf.slidingView setAlpha:1];
} completion:^(BOOL finished) {
[blockSelf.view removeGestureRecognizer:self.tapGestureRecognizer];
blockSelf.tapGestureRecognizer = nil;
}];
}
I have already tried settings the animation options to:
UIViewAnimationOptionBeginFromCurrentState
with no effect.
Anyone have a clue what I am missing? Thanks
Kyle, sometimes this sort of thing happens if you are using autolayout. If you don't need it try disabling it and see if it fixes it.

UIButton Press/Release Animation Oddly Inconsistent

I'm trying to write a custom UIButton subclass that will "animate" during press and release.
When pressed, the button should "shrink" (toward its center) to 90% of its original size.
When released, the button should "expand" to 105%, shrink again to 95% and then return to its original size.
Here's the code I've got right now:
#pragma mark -
#pragma mark - Touch Handling
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
[self animatePressedDown];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
[self animateReleased];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
[self animateReleased];
}
- (void)animatePressedDown {
NSLog(#"button.frame before animatedPressedDown: %#", NSStringFromCGRect(self.frame));
[self addShadowLayer];
CATransform3D ninetyPercent = CATransform3DMakeScale(0.90f, 0.90f, 1.00f);
[UIView animateWithDuration:0.2f
animations:^{
self.layer.transform = ninetyPercent;
}
completion:^(BOOL finished) {
NSLog(#"button.frame after animatedPressedDown: %#", NSStringFromCGRect(self.frame));
}
];
}
- (void)animateReleased {
[self.shadowLayer removeFromSuperlayer];
CATransform3D oneHundredFivePercent = CATransform3DMakeScale(1.05f, 1.05f, 1.00f);
CATransform3D ninetyFivePercent = CATransform3DMakeScale(0.95f, 0.95f, 1.00f);
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = oneHundredFivePercent;
}
completion:^(BOOL finished) {
NSLog(#"button.frame after animateReleased (Stage 1): %#", NSStringFromCGRect(self.frame));
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = ninetyFivePercent;
}
completion:^(BOOL finished) {
NSLog(#"button.frame after animateReleased (Stage 2): %#", NSStringFromCGRect(self.frame));
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = CATransform3DIdentity;
self.layer.frame = self.frame;
}
completion:^(BOOL finished) {
NSLog(#"button.frame after animateReleased (Stage 3): %#", NSStringFromCGRect(self.frame));
}
];
}
];
}
];
}
Anyway, the above code works perfectly... some of the time. At other times, the button animation works as expected, but after the "released" animation, the final frame of the button is "shifted" up and left from its original position. That's why I have those NSLog statements, to track exactly where the button's frame is during each stage of the animation. When the "shift" occurs, it happens somewhere between animatePressedDown and animateReleased. At least, the frame shown in animatePressedDown is ALWAYS what I expect it to be, but the first value for the frame in animateReleased is wrong frequently.
I can't see a pattern to the madness, though the same buttons in my app tend to behave correctly or incorrectly consistently from app run to app run.
I'm using auto layout for all of my buttons, so I can't figure out what the difference is to make one button behave correctly and another one change its position.
I really hate when I answer my own questions, but I figured it out.
(I'd been struggling with this issue for days, but it finally clicked after I asked the question. Isn't that how it always works?)
Apparently, the problem for me was the fact that (on the buttons that were "shifting") I was changing the button's title mid-animation.
Once I set up a blocks-based system to call the title change only after the button's "bounce"/"pop" animation was complete, the problems went away.
I still don't know all of why this works the way it does, as the titles I was setting in no way changed the overall size of the buttons, but setting the buttons up as described fixed my problem.
Here's the code from my custom UIButton subclass, with the block property added:
#interface MSSButton : UIButton {
}
#property (copy , nonatomic) void(^pressedCompletion)(void);
// Other, non-related stuff...
#end
#implementation MSSButton
#pragma mark -
#pragma mark - Touch Handling
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
[self animatePressedDown];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
[self animateReleased];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesEnded:touches withEvent:event];
[self animateReleased];
}
- (void)animatePressedDown {
[self addShadowLayer];
CATransform3D ninetyPercent = CATransform3DMakeScale(0.90f, 0.90f, 1.00f);
[UIView animateWithDuration:0.2f
animations:^{
self.layer.transform = ninetyPercent;
}
];
}
- (void)animateReleased {
[self.shadowLayer removeFromSuperlayer];
CATransform3D oneHundredFivePercent = CATransform3DMakeScale(1.05f, 1.05f, 1.00f);
CATransform3D ninetyFivePercent = CATransform3DMakeScale(0.95f, 0.95f, 1.00f);
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = oneHundredFivePercent;
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = ninetyFivePercent;
}
completion:^(BOOL finished) {
[UIView animateWithDuration:0.1f
animations:^{
self.layer.transform = CATransform3DIdentity;
}
completion:^(BOOL finished) {
if (self.pressedCompletion != nil) {
self.pressedCompletion();
self.pressedCompletion = nil;
}
}
];
}
];
}
];
}
#end
Since my IBAction fires before animateReleased (due to order of methods listed in my touch handling methods), I simply set the pressedCompletion block in my IBAction and the title is changed at the end of the animation sequence.
One likely problem is that you're trying to use the view's frame property after you've changed the transform property. The UIView class reference page specifically warns against that:
Warning: If the transform property is not the identity transform, the
value of this property is undefined and therefore should be ignored.
Specifically, you're changing the layer's transform, which probably impacts the view's transform, and then accessing self.frame. You might want to ensure that the view's transform is set to CGAffineTransformIdentity before accessing self.frame.

ios 7 UIView animateWithDuration: .. runs immediately

I have trouble with method for animation
I've created my own CalloutBubble for GoogleMap
#interface CalloutView : UIView
#property (nonatomic) MapMarker *marker;
#end
#implementation {
UIView *titleView;
UILabel *titleLabel, *addressLabel;
}
//another init methods aren't shown
- (void)setMarker:(MapMarker *)marker
{
_marker = marker;
titleLabel.text = marker.university.name;
addressLabel.text = marker.university.address;
[titleLabel sizeToFit];
titleLabel.minX = 0;
[titleLabel.layer removeAllAnimations];
if (titleLabel.width > titleView.width)
[self runningLabel];
}
- (void)runningLabel
{
CGFloat timeInterval = titleLabel.width / 70;
[UIView animateWithDuration:timeInterval delay:1.0 options:UIViewAnimationOptionOverrideInheritedDuration | UIViewAnimationOptionRepeat animations:^{
titleLabel.minX = -titleLabel.width;
} completion:^(BOOL finished) {
titleLabel.minX = titleView.width;
[self runningLabel];
}];
}
#end
In my viewController I create property
#implementation MapVC {
CalloutView *calloutView;
}
And then if I try create calloutView in any method all work fine with animation, but if I return view in Google map method
- (UIView *)mapView:(GMSMapView *)mapView markerInfoWindow:(GMSMarker *)marker
{
if (!calloutView)
calloutView = [[CalloutView alloc] initWithFrame:CGRectMake(0, 0, 265, 45.5)];
calloutView.marker = (MapMarker *)marker;
return calloutView;
}
My animation run immediately in calloutView, and completion called immediately too, and call runningLabel method again, So it is not work like it have to. All frame is good, and timInterval always more than 4 second. I tried to write static timeinterval like 10.0, but animation again run immediately, and flag finished in completion block always YES. So it called more than 100 times in one second =(
I create app in iOS 7. I tried to use different options for animation:
UIViewAnimationOptionLayoutSubviews
UIViewAnimationOptionAllowUserInteraction
UIViewAnimationOptionBeginFromCurrentState
UIViewAnimationOptionRepeat
UIViewAnimationOptionAutoreverse
UIViewAnimationOptionOverrideInheritedDuration
UIViewAnimationOptionOverrideInheritedCurve
UIViewAnimationOptionAllowAnimatedContent
UIViewAnimationOptionShowHideTransitionViews
UIViewAnimationOptionOverrideInheritedOptions
but there are no results.
What wrong in this Google map method, why my animation run immediately?
PS minX, width - my categories. minX set frame.origin.X . There are all good with this categories.
Remove UIViewAnimationOptionRepeat option Or comment the completion Block minX setting:
- (void)runningLabel
{
CGFloat timeInterval = titleLabel.width / 70;
[UIView animateWithDuration:timeInterval delay:1.0 options:UIViewAnimationOptionOverrideInheritedDuration | UIViewAnimationOptionRepeat animations:^{
titleLabel.minX = -titleLabel.width;
} completion:^(BOOL finished) {
//titleLabel.minX = titleView.width; //this is the problem, you should not set minX again while UIViewAnimationOptionRepeat also is animation option
[self runningLabel];
}];
}

Second animation

I have several buttons located at different sites in the view (storyboard), and I want that when I click on each of them to go to a given point,
To do this, I keep their location and initial size by:
CGPoint originalCenter1;
CGRect originalsize1;
CGPoint originalCenter2;
CGRect originalsize2;
in viewDidLoad
originalCenter1 = self.boton1.center;
originalsize1 = self.boton1.frame;
originalCenter2 = self.boton2.center;
originalsize2 = self.boton2.frame;
and the IBAction associated with each button, the animation ...
-(IBAction)move:(id)sender{
UIButton * pressedbutton = (UIButton*)sender;
[UIView animateWithDuration:3.0 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
CGAffineTransform scale = CGAffineTransformMakeScale(3.0, 3.0);
pressedbutton.transform = scale;
switch(pressedbutton.tag)
{
case 0:
pressedbutton.center = CGPointMake(784, 340);
break;
case 1:
pressedbutton.center = CGPointMake(784, 340);
break;
When already all have moved, I have a button Refresh that puts me to the initial position.
-(IBAction)refresh:(id)sender{
self.boton1.frame = originalsize1;
self.boton1.center = originalCenter1;
self.boton1.alpha=1;
self.boton2.frame = originalsize2;
self.boton2.center = originalCenter2;
self.boton2.alpha=1;
The problem is that the next time that pulse buttons, move to the position shown in the animation but the "scale" effect, doesn't work !!
Any help ??
Thanks.
I think you should use block try this...
- (IBAction)tap:(UITapGestureRecognizer *)gesture
{
CGPoint tapLocation = [gesture locationInView:self.view1];
for (UIView *view in self.view1.subviews) {
if (CGRectContainsPoint(view.frame, tapLocation)) {
[UIView animateWithDuration:5.0 animations:^{
[self setRandomLocationForView:view];
}];
}
}
}
- (void)setRandomLocationForView:(UIView *)view
{
[view sizeToFit];
CGRect sinkBounds = CGRectInset(self.view1.bounds, view.frame.size.width/2, view.frame.size.height/2);
CGFloat x = your location.x;
CGFloat y = yout location.y;
view.center = CGPointMake(x, y);
}

Resources