The documentation for Google Maps for iOS states that:
Call one of several methods that allow you to animate the camera moving to a new location. You can control the duration of the animation with CoreAnimation.
For the life of me, I can't figure out how to control the animation duration. I have tried using UIView animations, like:
[UIView animateWithDuration: 5 animations:^{
GMSCameraPosition *camera = [self newCamera];
self.mapView.camera = camera;
} completion:^(BOOL finished) {
}];
And I have looked at CALayer animations in CoreAnimation. However, I don't know how you would apply a layer animation to the map view.
Can someone point me in the right direction please?
I found the answer ... you can control the animation duration by wrapping one of the animate* methods in a CATransaction, like this:
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat: 1.0f] forKey:kCATransactionAnimationDuration];
// change the camera, set the zoom, whatever. Just make sure to call the animate* method.
[self.mapView animateToCameraPosition: [self newCamera]];
[CATransaction commit];
for Swift 3.0:
CATransaction.begin()
CATransaction.setValue(1.5, forKey: kCATransactionAnimationDuration)
// your camera code goes here, example:
// mapView.animate(with: update)
CATransaction.commit()
The bigger the value (1.5 in this case), the slower the animation.
Swift 2.0
CATransaction.begin()
CATransaction.setValue(NSNumber(float: 1.0), forKey: kCATransactionAnimationDuration)
// change the camera, set the zoom, whatever. Just make sure to call the animate* method.
CATransaction.commit()
what a pitty that using the same methods you provided there is no way to know if the animation has ended.
Yes I know, there is a CATransaction completion block using this method but it does simply not work! :(
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat: 1.0f] forKey:kCATransactionAnimationDuration];
[CATransaction setCompletionBlock:^{
// ... whatever you want to do when the animation is complete
}];
[self.googleMapsView animateToCameraPosition:[GMSCameraPosition
cameraWithLatitude:LATITUDE
longitude:LONGITUDE
zoom:ZOOM]];
[CATransaction commit];
And I can't use MapView:didIdle hack to know that the animation has ended because it will not be called if there is no camera position change.
Anyone knows how to detect animateon has ended event?
FOUND A THREAD ABOUT THIS (solved):
CATransaction completion being called immediately
Related
I'm trying to animate some markers initialised with a picture as follows RMMarker *marker = [[RMMarker alloc] initWithUIImage:lImage anchorPoint:lPoint];
I get a static image on my map with no problem.
Now, I need it to be more visible than other annotations, so I want to make it blink.
Here's what I already tried:
Creating scale animation on annotation layer
[CATransaction begin];
[CATransaction setAnimationDuration:0.70];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
CABasicAnimation *bounceAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
bounceAnimation.repeatCount = MAXFLOAT;
bounceAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1.0)];
bounceAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.8, 0.8, 1.0)];
bounceAnimation.removedOnCompletion = NO;
bounceAnimation.autoreverses = YES;
[layer addAnimation:bounceAnimation forKey:#"animateScale"];
[CATransaction commit];
This is working well, except that my callout view is also blinking as it shares the same layer (and Mapbox messes with the animation during the tracking mode, resulting in an incorrect orientation as it is reseted with every move of the user anyway).
Creating blink animation on annotation layer
[CATransaction begin];
[CATransaction setAnimationDuration:0.60];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAnimation.repeatCount = MAXFLOAT;
opacityAnimation.fromValue = [NSNumber numberWithFloat:1.0];
opacityAnimation.toValue = [NSNumber numberWithFloat:0.3];
opacityAnimation.removedOnCompletion = NO;
opacityAnimation.autoreverses = YES;
[layer addAnimation:opacityAnimation forKey:#"animateOpacity"];
[CATransaction commit];
This is less noticeable, but the annotation orientation is correct this time. I still have my callout view blinking on the layer though.
What I want to do
I would need a way to animate the marker image without affecting the layer.
The best animation I'd use would be the first one I tried, without the rotation problem.
So far I couldn't animate the UIImage as it doesn't have the addAnimation: forKey: call.
I've never played much with animations, so any help or guidance would be appreciated.
You're on the right track with CABasicAnimation, as this is what is used for the animated user location annotations in the SDK. You can see this around here:
https://github.com/mapbox/mapbox-ios-sdk/blob/509fa7df46ebd654d130ab2f530a8e380bf2bd59/MapView/Map/RMMapView.m#L3593
Could you elaborate on this?
This is working well, except that my callout view is also blinking as it shares the same layer (and Mapbox messes with the animation during the tracking mode, resulting in an incorrect orientation as it is reseted with every move of the user anyway).
You do bring up a good point that the callout is a sublayer, so it would also blink. This use case hasn't been considered — is it possible for you to deselect your annotation and thus hide the callout?
But what is meant by the latter part?
I'm trying to execute a completion-block after my CAAnimation has finished. However, it seems that animation block is called before my animation completes. The animation still happens correctly though.
[CATransaction begin];
[self.view.layer addAnimation:self.dropAndBounceAnimation forKey:#"appearance"];
[CATransaction setCompletionBlock:completionBlock];
[CATransaction commit];
The dropAndBounceAnimation is a CAKeyFrameAnimation on position.y, with a fixed duration.
I'm not sure if this really is the correct fix, but by setting the completion-block before adding the animation for the layer, the completion-block is consistently called at the correct time.
[CATransaction begin];
[CATransaction setCompletionBlock:completionBlock];
[self.view.layer addAnimation:self.dropAndBounceAnimation forKey:#"appearance"];
[CATransaction commit];
You need to set the completion block before adding the animation.
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat: 1.0f] forKey:kCATransactionAnimationDuration];
[CATransaction setCompletionBlock:^{
// ... whatever you want to do when the animation is complete
}];
[self.googleMapsView animateToCameraPosition:[GMSCameraPosition
cameraWithLatitude:LATITUDE
longitude:LONGITUDE
zoom:ZOOM]];
[CATransaction commit];
This must trigger the completion block after the completion of that animation on the view.
Here is Swift 3.0.1, Xcode 8 version:
CATransaction.begin()
CATransaction.setCompletionBlock({
print("Transaction completed")
})
print("Transaction started")
view.layer.add(dropAndBounceAnimation, forKey: "appearance")
CATransaction.commit()
Try to start the animation asynchronously:
DispatchQueue.main.async {
self.startAnimation()
}
because it can interfere with view drawing if you make some view setup before calling the animation.
I am coding in iOS.
I have an NSArray, which contains a few MKMapCameras. I want to display MKMapCameras from the array one after another.
I put a while loop and used [self.mapView setCamera:nextCamera animated:YES];
However, this is only showing the first and the last views. Everything in between is going too fast.
I want to slow down the movement of each camera. Is there a way to achieve it using CATransaction or using any other animation tricks. If so, could you please show me an example code?
Want to give an update... I tried below code. But it isn't working... Camera movements are fast as I mentioned earlier.
[CATransaction begin];
[CATransaction setAnimationDuration:5.5];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[CATransaction setCompletionBlock:^{
[self.mapView setCamera:nextCamera animated:YES];
}];
[CATransaction commit];
After fiddling with it a few hours, I figured out a way to make it work. Thought of sharing the same with everyone...
I made two changes. I replaced CATransaction with UIView's animation. I also removed Camera's default animation settings, which was conflicting with UIView's animation.
Below is the code.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
[UIView setAnimationDuration:2.5];
self.mapView.camera = nextCamera;
[UIView commitAnimations];
According to the WWDC 'Putting MapKit in Perspective' video you should avoid any approach using completion handlers for animating map cameras in sequence. Rather you should set a delegate on your map view and listen for the regionDidChangeAnimated: call to trigger the next camera in your sequence. This way the speed of the camera movement can be controlled with animateWithDuration:
-(void)flyToLocation:(CLLocationCoordinate2D)toLocation {
CLLocationCoordinate2D startCoord = self.mapView.camera.centerCoordinate;
CLLocationCoordinate2D eye = CLLocationCoordinate2DMake(toLocation.latitude, toLocation.longitude);
MKMapCamera *upCam = [MKMapCamera cameraLookingAtCenterCoordinate:startCoord
fromEyeCoordinate:startCoord
eyeAltitude:80000];
MKMapCamera *turnCam = [MKMapCamera cameraLookingAtCenterCoordinate:toLocation
fromEyeCoordinate:startCoord
eyeAltitude:80000];
MKMapCamera *inCam = [MKMapCamera cameraLookingAtCenterCoordinate:toLocation
fromEyeCoordinate:eye
eyeAltitude:26000];
self.camerasArray = [NSMutableArray arrayWithObjects:upCam, turnCam, inCam, nil];
[self gotoNextCamera];
}
-(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
[self gotoNextCamera];
}
-(void)gotoNextCamera {
if (self.camerasArray.count == 0) {
return;
}
MKMapCamera *nextCam = [self.camerasArray firstObject];
[self.camerasArray removeObjectAtIndex:0];
[UIView animateWithDuration:3.0 animations:^{
self.mapView.camera = nextCam;
}];
}
I can change the bearing of my map using
[googleMapView animateToBearing:0.5];
but how does one do this so the change is instantaneous, with no animation?
Grab the current camera position, change the bearing, then set the view's camera position:
GMSCameraPosition *myCamera = googleMapView.camera;
GMSCameraPosition *myNewCamera = [GMSCameraPosition cameraWithLatitude:myCamera.targetAsCoordinate.latitude longitude:myCamera.targetAsCoordinate.longitude zoom:myCamera.zoom bearing:0.5 viewingAngle:myCamera.viewingAngle];
googleMapView.camera = myNewCamera;
Since there is no direct option like movetobearing, one can use the above mentioned approach. Another option is to use [googleMapView animateToBearing:0.5]; and then set the animation duration to a very low value , say like 0.0001f, which gives the visual effect of a movement
[CATransaction begin];
[CATransaction setValue:[NSNumber numberWithFloat: 0.0001f] forKey:kCATransactionAnimationDuration];
[mapView_ animateToBearing:0.5];
[CATransaction commit];
For a smoother and a transitioned update try this (view is a GMSMapView)
[self.view animateToCameraPosition:[GMSCameraPosition cameraWithLatitude:coordinate.latitude longitude:coordinate.longitude zoom:15 bearing:bearing viewingAngle:45]];
I have a setter method for a property on a custom UIView class. If it's set within a UIView animation block, I'd like it to add a CABasicAnimation to the view's layer with the same duration and easing as the UIView animation. How do I find out whether I'm inside a UIView animation block, and how do I get its duration and easing curve?
Very simple..
You can get all animation keys applied to your view using
[self.YourView.layer animationKeys];
Based on this question I made this extension to UIView block animations: UIView+AnimatedProperty.
It allows you to run CAAnimations when the setter is called from animation block. Example with cornerRadius is included.
You can get the current animation easily enough. For instance, setting up a CATransaction:
CAAnimation *animation = [self.layer animationForKey:self.layer.animationKeys.firstObject];
[CATransaction begin];
[CATransaction setAnimationDuration:animation.duration];
[CATransaction setAnimationTimingFunction:animation.timingFunction];
// CALayer animation here
[CATransaction commit];
The documentation for [UIView animateWithDuration] states that
This method performs the specified animations immediately using the UIViewAnimationOptionCurveEaseInOut and UIViewAnimationOptionTransitionNone animation options.
As for the duration, you set the duration yourself, so you already now that.