animateWithDuration not working inside GMSMapView delegate method - ios

I am trying to animate a UIView with the code below, but I fail to understand why the code below does not work.
Works - self.searchBar animates smoothly // in all except for the said method
- (void) mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position
{
[UIView animateWithDuration:.3
delay:0
options: UIViewAnimationCurveEaseOut
animations:^{
[self.searchBar setFrame:CGRectMake(self.searchBar.frame.origin.x, self.searchBar.frame.origin.y - self.searchBar.frame.size.height , self.searchBar.frame.size.width, self.searchBar.frame.size.height)];
}
completion:^(BOOL finished){
}];
}
Does Not Work - self.searchBar jumps without animating
- (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture
{
[UIView animateWithDuration:.3
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
[self.searchBar setFrame:CGRectMake(self.searchBar.frame.origin.x, self.searchBar.frame.origin.y - self.searchBar.frame.size.height , self.searchBar.frame.size.width, self.searchBar.frame.size.height)];
}
completion:^(BOOL finished){
}];
}

I opened a bug on this issue with Google (https://code.google.com/p/gmaps-api-issues/issues/detail?id=10349), and this was their response:
Our understanding is that this is happening because the mapView:willMove: call is being made while already inside a CoreAnimation transaction. Our suggested work around is to wrap the UIView.animateWithDuration call within a dispatch_async back to the main thread. Something along the lines of:
dispatch_async(dispatch_get_main_queue(), ^{
// animation block here.
});
Does that help?
Placing the UIView.animateWithDuration block inside a dispatch_async block worked for me.
Also, this question is a duplicate of iOS layout animation ignores duration when fired on GMSMapView dragging, but I don't have enough rep to flag it as such. If anyone's keeping score, it should also be noted that Google's workaround is what Vishnu suggested in his answer. I didn't have enough rep to just comment on his answer either.

Use CABasicAnimation help me to solve this problem.
In my case I have to move UIView frame.
CABasicAnimation *moveAnimation = [CABasicAnimation animationWithKeyPath:#"movePosition"];
moveAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake([viewToMove center].x, [viewToMove center].y)];
moveAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake([viewToMove center].x, y)];
moveAnimation.delegate = self;
moveAnimation.duration = 1.0;
moveAnimation.speed = 3.0;
moveAnimation.cumulative = YES;
moveAnimation.repeatCount = 1.0;
moveAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
moveAnimation.removedOnCompletion = NO;
moveAnimation.fillMode = kCAFillModeForwards;
[viewToMove.layer addAnimation:rotationAnimation forKey:#"movePosition"];
Then in
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
save new position of viewToMove

You may try to add the animation inside operation main queue:
- (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
//Your animation code goes in here
}];
}

Related

Animation subview removal freezes when scrolling

My code:
I am using the following code for an add to basket animation:
-(void)triggerAddItemAnimation:(NSIndexPath*)indexPath
{
//Cell that was pressed.
CellMenuItem *cell = [self.tableView cellForRowAtIndexPath:indexPath];
// grab the imageview using cell
UIImageView *imgV = (UIImageView*)[cell viewWithTag:IDENTIFIER_ANIMATION_IMAGE];
//Get the x-coordinate of the image.
int xCoord = cell.imgAddButton.frame.origin.x;
// get the exact location of image
CGRect rect = [imgV.superview convertRect:imgV.frame fromView:nil];
rect = CGRectMake(xCoord, (rect.origin.y*-1)-10, imgV.frame.size.width, imgV.frame.size.height);
//NSLog(#"rect is %f,%f,%f,%f",rect.origin.x,rect.origin.y,rect.size.width,rect.size.height);
// create new duplicate image
UIImageView *starView = [[UIImageView alloc] initWithImage:imgV.image];
[starView setFrame:rect];
starView.layer.cornerRadius=5;
starView.layer.borderColor=[[UIColor blackColor]CGColor];
starView.layer.borderWidth=1;
[self.view addSubview:starView];
// begin ---- apply position animation
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration=0.65;
pathAnimation.delegate=self;
// tab-bar right side item frame-point = end point
CGPoint endPoint = CGPointMake(self.lblBasketItemsCount.frame.origin.x, self.lblBasketItemsCount.frame.origin.y);
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, starView.frame.origin.x, starView.frame.origin.y);
CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, starView.frame.origin.y, endPoint.x, starView.frame.origin.y, endPoint.x, endPoint.y);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
// end ---- apply position animation
float animationDuration = 0.65f;
// apply transform animation
CABasicAnimation *basic=[CABasicAnimation animationWithKeyPath:#"transform"];
[basic setToValue:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.25, 0.25, 0.25)]];
[basic setAutoreverses:NO];
[basic setDuration:animationDuration];
[starView.layer addAnimation:pathAnimation forKey:#"curveAnimation"];
[starView.layer addAnimation:basic forKey:#"transform"];
//Remove the animation 0.05 before the animationDuration to remove clipping issue.
[starView performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:animationDuration - 0.05f];
}
The Problem:
The animation works beautifully but when I scroll the image used for the animation (taken from the cell) does not remove from the view until the scrolling has stopped.
The subview being removed is removed with the last line of code within the method:
[starView performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:animationDuration - 0.05f];
What I've tried:
Ive tried using GCD:
dispatch_async(dispatch_get_main_queue(), ^{
//call method here....
});
If I recall I believe Ive also tried a run loop with a timer, as well as perform selector on main thread.
Ive also tried putting the method in a block(not the actual code I used, merely an example):
[UIView animateWithDuration:0.5
delay:1.0
options: UIViewAnimationCurveEaseOut
animations:^{
}
completion:^(BOOL finished){
//Remove subview here
}];
The Question:
I can deduce that the scrolling is tying up the main thread, hence why I've tried to call the method asynchronously.
Can someone let me know what I'm doing wrong ?
To solve this issue you can use the complete portion of a block as can be seen above. I was simply using the block incorrectly at the time.
However the way I solved this, because I couldn't get a path animation to work in a block was to set the delegate of the animation to self.
And use the following:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[theView removeFromSuperview];
}

How to reduce the opacity of an UIButton gradually?

In my project i have an UIButton which on clicking zooms. Everything is fine till here but i require the opacity of the button to gradually decrease from 1 to 0. I mean it should decrease in 1,0.9,0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0 sequence. My current code is
-(void)roundButtonAnimation
{
self.buttonZoom = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
self.buttonZoom.duration = 0.8f;
self.buttonZoom.values = #[[NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(2,2,2)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(3,3,3)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(5,5,5)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(6,6,6)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(7,7,7)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(9,9,9)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(10,10,10)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(11,11,11)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(12,12,12)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(13,13,13)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(14,14,14)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(15,15,15)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(16,16,16)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(17,17,17)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(18,18,18)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(50,50,50)]];
self.buttonZoom.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
self.roundButton.transform = CGAffineTransformMakeScale(0,0);
self.roundButton.alpha = 0.3;
[self.roundButton.layer addAnimation:self.buttonZoom forKey:#"buttonScale"];
[self.CLSpinnerView1 stopAnimating];
//[self performSelector:#selector(secondView) withObject:self afterDelay:0.3];
[self performSelector:#selector(postNotification) withObject:self afterDelay:0.6];
}
[UIView animateWithDuration:1.0
delay:0.0
options: UIViewAnimationCurveLinear
animations:^{
self.roundButton.transform = CGAffineTransformMakeScale(50.0f, 50.0f);
self.roundButton.alpha = 0.0;
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}
Let's explain a little, in this animation block you say that the animation should take place in one second, during this second based on a linear curve (thus always equal increment/decrement) the button should zoom 50 times and alpha should be animate to 0.
I do not understand why you are using a more complicated CAKeyframeAnimation over that simple animation block.
Keyframe animation are useful when you want more power over timings and animate over a set of values.
UIView animateWithDuration:animations: should serve your purpose here.
[UIView animateWithDuration:1.0 animations:^{
button.alpha = 0.0;
}];
Will gradually change the button's alpha to 0 over one second (...WithDuration:1.0...).
See the The UIView Class Reference for more information.
Hope this helps.
Try this
[button.layer removeAnimationForKey:#"opacity"];

how to pause uiview animation?

How to pause uiview animation and just leave it there?
//#define ANIMATION_WORM_OPTIONS (UIViewAnimationOptionCurveLinear|
// UIViewAnimationOptionBeginFromCurrentState)
- (IBAction)pressUp:(id)sender {
[self.game wormUp];
[self.gameScene.wormView.layer removeAllAnimations];
[UIView animateWithDuration:5 delay:0 options:ANIMATION_WORM_OPTIONS
animations:^{
[self.gameScene.wormView moveUp];
} completion:^(BOOL finished) {
}];
}
- (IBAction)pressRight:(id)sender {
[self.game wormRight];
[UIView animateWithDuration:5 delay:0 options:ANIMATION_WORM_OPTIONS
animations:^{
[self.gameScene.wormView moveRight];
} completion:^(BOOL finished) {
}];
}
For example here is what I am trying to do:
When I first tap "Right" a worm goes right.
Then when I tap "Up" during the worm goes right, the worm should stop and go up.
The problem in my code is the removeAllAnimations will first put the worm to the end of first animation end, then begin goes up.
You need to change the view's frame to the halting position frame. Just add the following before you do the removeAllAnimation on view.layer.
self.theView.frame = [self.theView.layer.presentationLayer frame];
CFTimeInterval pausedTime = [self.gameScene.wormView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
self.gameScene.wormView.layer.speed = 0.0;
self.gameScene.wormView.layer.timeOffset = pausedTime;

How can I get a CAAnimation to call a block every animation tick?

Can I somehow have a block execute on every "tick" of a CAAnimation? It could possibly work like the code below. If there's a way to do this with a selector, that works too.
NSString* keyPath = #"position.x";
CGFloat endValue = 100;
[CATransaction setDisableActions:YES];
[self.layer setValue:#(toVal) forKeyPath:keyPath];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath];
animation.duration = 1;
animation.delegate = self;
__weak SelfClass* wself = self;
animation.eachTick = ^{
NSLog(#"Current x value: %f", [wself valueForKeyPath:keypath]);
};
[self.layer addAnimation:animation forKey:nil];
The typical technique is to employ a display link (CADisplayLink).
So, define a property for the display link:
#property (nonatomic, strong) CADisplayLink *displayLink;
And then implement the methods to start, stop, and handle the display link:
- (void)startDisplayLink
{
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)stopDisplayLink
{
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
CALayer *layer = self.animatedView.layer.presentationLayer;
NSLog(#"%#", NSStringFromCGRect(layer.frame));
}
Note, while a view is animating, you cannot look at its basic properties (because it will reflect the end destination rather than the "in flight" position), but rather you access the animated view's layer's presentationLayer, as shown above.
Anyway, start the display link when you start your animation. And remove it upon completion.
With standard block-based animation, you'd do something like:
[UIView animateWithDuration:2.0 delay:0 options:0 animations:^{
view.frame = someNewFrame;
} completion:^(BOOL finished) {
[self stopDisplayLink];
}];
[self startDisplayLink];
With Core Animation, it might look like:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [NSValue valueWithCGPoint:startPoint];
animation.toValue = [NSValue valueWithCGPoint:endPoint];
[CATransaction setAnimationDuration:1.0];
[CATransaction setCompletionBlock:^{
[self stopDisplayLink];
}];
[CATransaction begin];
[self.animatedView.layer addAnimation:animation forKey:nil];
[CATransaction commit];
[self startDisplayLink];
Using a CADisplayLink to achieve this is ideal, but using it directly requires quite a bit of code.
Whether you're working at the Core Animation level or UIView animation level, take a look at the open source INTUAnimationEngine library. It provides a very simple API that is almost exactly like the UIView block-based API, but instead it runs the animations block each frame, passing in a percentage complete:
// INTUAnimationEngine.h
// ...
+ (NSInteger)animateWithDuration:(NSTimeInterval)duration
delay:(NSTimeInterval)delay
animations:(void (^)(CGFloat percentage))animations
completion:(void (^)(BOOL finished))completion;
// ...
If you simply call this method at the same time when you start your other animations and use the same duration & delay values, you'll be able to receive a callback at each frame of the animation. If you don't like duplicating code, INTUAnimationEngine can be used exclusively to run your animation as well, with advanced features like support for custom easing functions.

How to know when UIView animations have all completed

A custom view I created is being animated by its container - in my view I update portions of the subviews with other animations (for instance a UICOllectionView)
I see that this is throwing errors
*** Assertion failure in -[UICollectionView _endItemAnimations
Digging around I see that my view has animations attached to it:
<GeneratorView: 0x15b45950; frame = (0 0; 320 199); animations = { position=<CABasicAnimation: 0x145540a0>; }; layer = <CALayer: 0x15b49410>>
So now before performing animation operations I check:
NSArray *animationKeys = [self.layer animationKeys];
for (NSString *key in animationKeys) {
CAAnimation *animation = [self.layer animationForKey:key];
}
and I see that animation objects are returned:
at this point I would like to "wait" until all animations have completed before updating self.
I see that I can add myself as the CAAnimation delegate.
But this is a bit messy and hard to track.
Is there an easier way using a UIView method - much higher level?
You can use UIView method to do the animation in the container:
[UIView animateWithDuration: animations: completion:];
[UIView animateWithDuration: delay: options: animations: completion:];
You should add properties to your custom view:
#property BOOL isAnimating;
In the container run the animation block and change view isAnimating property.
This method accept block which will be fired when the animation complete.
Example:
[UIView animateWithDuration:1.0 animations:^{
//Your animation block
view1.isAnimating = YES;
view1.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
// etc.
}completion:^(BOOL finished){
view1.isAnimating = NO;
NSLog(#"Animation completed.");
}];
Now it your view you can see if the view is animating:
if (self.isAnimating)
NSLog(#"view is animating");
Ithink you can you that :
#interface UIView(UIViewAnimationWithBlocks)
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
Hope that will help

Resources