Custom Map Callout Animation - iphone

I have to create custom MKMapView annotations, which look almost the same as the ones iOS provides, but have different color (white) and slightly different shape.
My first attempt was to prevent opening the original black UIAnnotationViews and in AnnotationView's setSelected:animated: added a new subview to the same UIAnnotationView. I have animated this view after adding it to the superview. This had only one problem: my custom button in the annotation was not clickable.
After this I've found this excellent example:
https://github.com/tochi/custom-callout-sample-for-iOS
This adds a new CalloutAnnotation to the map when the user clicks on a PinAnnotation. PinAnnotations have no AnnotationViews, only CalloutAnnotations do.
This works fine, except that I can't figure out how to do the iOS-like initial zooming animation on the annotation views when they are created.
The animation I want to use is the following (found here on SO and worked fine with my first attempt):
- (void)animateCalloutAppearance:(CalloutAnnotationView *)annotaitonView
{
self.endFrame = annotaitonView.frame;
CGFloat scale = 0.001f;
annotaitonView.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, [self xTransformForScale:scale andAnnotation:annotaitonView], [self yTransformForScale:scale]);
[UIView animateWithDuration:0.075f delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
CGFloat scale = 1.2f;
annotaitonView.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, [self xTransformForScale:scale andAnnotation:annotaitonView], [self yTransformForScale:scale]);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGFloat scale = 0.90;
annotaitonView.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, [self xTransformForScale:scale andAnnotation:annotaitonView], [self yTransformForScale:scale]);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.075 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGFloat scale = 1.0;
annotaitonView.transform = CGAffineTransformMake(scale, 0.0f, 0.0f, scale, [self xTransformForScale:scale andAnnotation:annotaitonView], [self yTransformForScale:scale]);
} completion:nil];
}];
}];
}
#pragma mark - The helper methods
- (CGFloat)relativeParentXPosition:(CalloutAnnotationView *)annotationView {
return annotationView.bounds.size.width / 2.0f;
}
- (CGFloat)xTransformForScale:(CGFloat)scale andAnnotation:(CalloutAnnotationView *)annotationView {
CGFloat xDistanceFromCenterToParent = self.endFrame.size.width / 2.0f - [self relativeParentXPosition:annotationView];
CGFloat transformX = (xDistanceFromCenterToParent * scale) - xDistanceFromCenterToParent;
return transformX;
}
- (CGFloat)yTransformForScale:(CGFloat)scale {
CGFloat yDistanceFromCenterToParent = self.endFrame.size.height / 2.0f;
CGFloat transformY = yDistanceFromCenterToParent - yDistanceFromCenterToParent * scale;
return transformY;
}

I've found the answer on this blog: http://blog.asynchrony.com/2010/09/building-custom-map-annotation-callouts-part-1/
The tricky part is that you have to do the animation in the CalloutAnnotationView's didMoveToSuperView :)
- (void)didMoveToSuperview
{
[self animateCalloutAppearance:self];
}
I hope it helps somebody else :)

Related

UIView Rounded Circular view Transition

I have implemented Circular transitions as i asked in
animating UIView transitions like expand dot to circle
using following code:
-(IBAction)nextViewOpen
{
NextViewVC *NextView= [self.storyboard instantiateViewControllerWithIdentifier:#"RBANextViewVC"];
[NextView.view.layer setCornerRadius:25.0f];
[NextView.view.layer setMasksToBounds:YES];
NextView.view.frame=CGRectMake(100,120, 1, 1);
[self.view addSubview:NextView.view];
[self setRoundedView:NextView.view toDiameter:5];
[UIView animateWithDuration:5.0 animations:^{
====== [self setRoundedView:NextView.view toDiameter:480]; ======
}
completion:^(BOOL finished)
{
NextView.view.frame=CGRectMake(0, 0, 320, 480);
NextView.view.layer.cornerRadius = 0;
}
];
}
-(void)setRoundedView:(UIView *)roundedView toDiameter:(float)newSize;
{
CGPoint saveCenter =roundedView.center;
CGRect newFrame = CGRectMake(roundedView.frame.origin.x, roundedView.frame.origin.y, newSize, newSize);
roundedView.frame = newFrame;
//roundedView.layer.cornerRadius = newSize / 2.0;
roundedView.layer.cornerRadius = roundedView.frame.size.width / 2.0;
roundedView.center = saveCenter;
roundedView.clipsToBounds = YES;
}
But it is having some issues
the view gets complete circle shape at the end of the animated transition, initially its not having rounded corners perfectly

iOS translation and scale animation

I'm working on UIButton animation where:
The UIButton is set in the bottom center of the screen and scaled to a small size
_menuBtn.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
When the app starts it should be moving to the bottom left side of the screen as it scales or grow to its original size.
- (void)viewDidLoad
{
[super viewDidLoad];
_menuBtn.frame = CGRectMake(160, 513, 30, 30);
_menuBtn.superview.frame = CGRectMake(160, 513, 30, 30);
_menuBtn.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
NSLog(#"_menuBtn: %# ; _menuBtn.superview: %#", _menuBtn, _menuBtn.superview);
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
CGAffineTransform scaleTrans = CGAffineTransformMakeScale(1.0f, 1.0f);
CGAffineTransform lefttorightTrans = CGAffineTransformMakeTranslation(-200.0f,0.0f);
_menuBtn.transform = CGAffineTransformConcat(scaleTrans, lefttorightTrans);
[UIView commitAnimations];
}
problem
When the animation starts the button starts moving from the bottom right side of the screen and not in the bottom center where it is and should be. Any help ?
Log Result
NSLog(#"%#", _myBtn);
2013-08-14 09:22:38.913 GJCoolNavi[339:c07] <UIButton: 0x813ea30; frame = (0 0; 0 0); opaque = NO; autoresize = TM+BM; layer = <CALayer: 0x813eaf0>>
thats before doing the animation...and the result after the animation is:
2013-08-14 09:30:25.719 GJCoolNavi[612:c07] <UIButton: 0x71206d0; frame = (160 294; 0 0); opaque = NO; autoresize = TM+BM; animations = { transform=<CABasicAnimation: 0x7536a80>; position=<CABasicAnimation: 0x7537dd0>; }; layer = <CALayer: 0x7120790>>
Why don't you do this...
_menuBtn.transform = CGAffineTransformMakeScale(0.1, 0.1);
[UIView animateWithDuration:5.0
options:UIViewAnimationOptionCurveEaseOut
animations:^(){
_menuBtn.transform = CGAffineTransformMakeScale(1.0, 1.0);
_menuBtn.center = self.view.center;
}
completion:nil];
I'd avoid moving stuff using a transform. Change the frame instead.
EDIT
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// for convenience I'm pulling these values out in to variables.
float buttonWidth = _menuBtn.frame.size.width;
float buttonHeight = _menuBtn.frame.size.height;
float viewWidth = self.view.frame.size.width;
float viewHeight = self.view.frame.size.height;
// set the button frame to be the bottom center
// note you shouldn't have to do this as Interface Builder should already place it there.
// place the button in the horizontal center and 20 points from the bottom of the view.
_menuBtn.frame = CGRectMake((viewWidth - buttonWidth) * 0.5, viewHeight - buttonHeight - 20, buttonWidth, buttonHeight);
// scale the button down before the animation...
_menuBtn.transform = CGAffineTransformMakeScale(0.1, 0.1);
// now animate the view...
[UIView animateWithDuration:5.0
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
_menuBtn.transform = CGAffineTransformIdentity;
_menuBtn.frame = CGRectMake(viewWidth - buttonWidth - 20, viewHeight - buttonHeight - 20, buttonWidth, buttonHeight);
}
completion:nil];
}
Try this and let me know what happens.
This has solved a similar problem.
Apply a (UIButton).imageView Animation.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
CGAffineTransform scaleTrans = CGAffineTransformMakeScale(1.0f, 1.0f);
CGAffineTransform lefttorightTrans = CGAffineTransformMakeTranslation(-200.0f,0.0f);
_menuBtn.imageView.transform = CGAffineTransformConcat(scaleTrans, lefttorightTrans);
[UIView commitAnimations];
This phenomenon indicates that your button's original frame is wrong, probably because of its auto-resizing. Try setting its frame to the bottom center before you start the animation.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
_menuBtn.superview.frame = CGRectMake(0, 513, 320, 30); // This is a quick and dirty solution to make sure your button's superview is in the right place. You probably don't want to do this and are more likely to review your view hierarchy.
_menuBtn.frame = CGRectMake(145, 0, 30, 30); // Set the frame to the bottom center, please ensure that _menuBtn's transform hasn't been changed before this step
_menuBtn.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
CGAffineTransform scaleTrans = CGAffineTransformMakeScale(1.0f, 1.0f);
CGAffineTransform lefttorightTrans = CGAffineTransformMakeTranslation(-200.0f,0.0f);
_menuBtn.transform = CGAffineTransformConcat(scaleTrans, lefttorightTrans);
[UIView commitAnimations];
}
I have used your code and its moving from bottom center to to bottom left perfectly. Might be your superview isn't in proper rect. Please check.

Transforming view coordinates with CGAffineTransform

I'm fooling around with Apple's ScrollViewSuite, TapToZoom app, trying to place a view on the zoomed image. Here's what I want to do: on double tap, rotate the image by pi/2, zoom it by a factor of 8, then place a subview in the scrollView on top of the transformed image.
I know the view's desired frame in the original coordinate system. So I need to transform those coordinates to the new system after the zoom...
From the ScrollViewSuite...
- (void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer {
// double tap zooms in
float newScale = [imageScrollView zoomScale] * 8; // my custom zoom
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:[gestureRecognizer locationInView:gestureRecognizer.view]];
[imageScrollView zoomToRect:zoomRect animated:YES];
[self rotateView:imageScrollView by:M_PI_2]; // then the twist
}
My rotation code...
- (void)rotateView:(UIView *)anImageView by:(CGFloat)spin {
[UIView animateWithDuration:0.5 delay: 0.0
options: UIViewAnimationOptionAllowUserInteraction
animations:^{
anImageView.transform = CGAffineTransformMakeRotation(spin);
}
completion:^(BOOL finished){}];
}
Then, after the zoom is finished, place a view at a known location in the original coordinate system. This is where I think I need some help...
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)_scale {
CGFloat coX = scrollView.contentOffset.x;
CGFloat coY = scrollView.contentOffset.y;
// try to build the forward transformation that got us here, then invert it
CGAffineTransform rotate = CGAffineTransformMakeRotation(M_PI_2);
CGAffineTransform scale = CGAffineTransformMakeScale(_scale, _scale);
// should these be negative? some confusion here.
CGAffineTransform translate = CGAffineTransformMakeTranslation(coX, coY);
// what's the right order of concatenation on these? more confusion
CGAffineTransform aft0 = CGAffineTransformConcat(rotate, scale);
CGAffineTransform aft1 = CGAffineTransformConcat(aft0, translate);
// invert it
CGAffineTransform aft = CGAffineTransformInvert(aft1);
// this is the fixed rect in original coordinate system of the image
// the image is at 0,0 320, 300 in the view, so this will only work
// when the image is at the origin... need to fix that later,too.
CGRect rect = CGRectMake(248, 40, 90, 160);
CGRect xformRect = CGRectApplyAffineTransform(rect, aft);
// build a subview with this rect
UIView *newView = [scrollView viewWithTag:888];
if (!newView) {
newView = [[UIView alloc] initWithFrame:xformRect];
newView.backgroundColor = [UIColor yellowColor];
newView.tag = 888;
[scrollView addSubview:newView];
} else {
newView.frame = xformRect;
}
// see where it landed...
NSLog(#"%f,%f %f,%f", xformRect.origin.x, xformRect.origin.y, xformRect.size.width, xformRect.size.height);
}

CGAffineTransformScale a UIButton

i am trying to scale in and out a UIButton in the main view, when i press a action button at first, the button zooms in and when i press it again it zooms out but when i press it again to zoom in.. nothing happens.. here is my code:
THE METHODS TO ZOOM IN AND ZOOM OUT ARE IN A OBJECTIVE-C CATEGORY ON UIVIEW
- (void)viewDidLoad
[super viewDidLoad];
//this button is being added in the storyboard
[self.viewToZoom removeFromSuperview];
}
- (IBAction)zoomButton:(id)sender {
if (isShown) {
[self.view removeSubviewWithZoomOutAnimation:self.viewToZoom duration:1.0 option:0];
isShown = NO;
} else {
[self.view addSubviewWithZoomInAnimation:self.viewToZoom duration:1.0 option:0];
isShown = YES;
}
}
UIView+Animation.m
- (void) addSubviewWithZoomInAnimation:(UIView*)view duration:(float)secs option:(UIViewAnimationOptions)option {
CGAffineTransform trans = CGAffineTransformScale(view.transform, 0.01, 0.01);
view.transform = trans; // do it instantly, no animation
[self addSubview:view];
// now return the view to normal dimension, animating this tranformation
[UIView animateWithDuration:secs delay:0.0 options:option
animations:^{
view.transform = CGAffineTransformScale(view.transform, 100.0, 100.0);
}
completion:^(BOOL finished) {
NSLog(#"done");
} ];
}
- (void) removeSubviewWithZoomOutAnimation:(UIView*)view duration:(float)secs option:(UIViewAnimationOptions)option {
// now return the view to normal dimension, animating this tranformation
[UIView animateWithDuration:secs delay:0.0 options:option
animations:^{
view.transform = CGAffineTransformScale(view.transform, 0.01, 0.01);
}
completion:^(BOOL finished) {
[view removeFromSuperview];
}];
}
Thanks,
Newton
Newton, when removeSubviewWithZoomOutAnimation ends the view.transform is an affine transform that scaled the view's original size down to 0.01. The problem is that when you call addSubviewWithZoomInAnimation the second time you're again scaling down by 0.01, but now the view.transform will be scaled down to 0.0001, which is not what you're looking for.
Simply add view.transform = CGAffineTransformIdentity; at the beginning of both animations like this:
- (void) addSubviewWithZoomInAnimation:(UIView*)view duration:(float)secs option:(UIViewAnimationOptions)option {
view.transform = CGAffineTransformIdentity;
CGAffineTransform trans = CGAffineTransformScale(view.transform, 0.01, 0.01);
view.transform = trans; // do it instantly, no animation
[self addSubview:view];
// now return the view to normal dimension, animating this tranformation
[UIView animateWithDuration:secs delay:0.0 options:option
animations:^{
view.transform = CGAffineTransformScale(view.transform, 100.0, 100.0);
}
completion:^(BOOL finished) {
NSLog(#"done");
} ];
}
- (void) removeSubviewWithZoomOutAnimation:(UIView*)view duration:(float)secs option:(UIViewAnimationOptions)option {
view.transform = CGAffineTransformIdentity;
// now return the view to normal dimension, animating this tranformation
[UIView animateWithDuration:secs delay:0.0 options:option
animations:^{
view.transform = CGAffineTransformScale(view.transform, 0.01, 0.01);
}
completion:^(BOOL finished) {
[view removeFromSuperview];
}];
}
I also suggest you to pass the UIViewAnimationOptionBeginFromCurrentState UIViewAnimationOptions which improves the animation result when quickly zooming in and out.
Hope this helps!

iPhone SDK: Flip-and-scale animation between view controllers using blocks?

I'm trying to create a flip-and-scale animation between two view controllers. This seems possible using animation blocks available in iOS 4.0, but I'm still unsure how to implement it. I found this SO question which shows a flip animation.
Using this code, flipping between two views works fine, but scaling doesn't -- the flip animation completes and then the new view jumps to the correct size. How would I flip the view and scale it at the same time?
UIView *tempContainer = myView.contentView ;
[UIView transitionWithView:tempContainer
duration:2
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[[[tempContainer subviews] objectAtIndex:0] removeFromSuperview];
[tempContainer addSubview:myOtherViewController.view];
CGAffineTransform transform = CGAffineTransformMakeScale(4.0, 4.0);
tempContainer.transform = transform;
}
completion:^(BOOL finished){
[tempContainer release];
}];
I suppose this happens because the option UIViewAnimationOptionTransitionFlipFromRight somehow overrides everything else. Try using nesting animations or link them together
Here's how I flip and scale between two views of different sizes. I break the animation into a few parts. First I put the back view at the same location as the front view, but make it hidden. I then flip and scale the front view halfway. The back view is given the same transform as the front view, then rotated and scaled the rest of the way. Flipping back is basically the reverse.
I suppose you could use a different view controllers view property as the back view, but I haven't tried that.
// flip and scale frontView to reveal backView to the center of the screen
// uses a containerView to mark the end of the animation
// parameterizing the destination is an exercise for the reader
- (void)flipFromFront:(UIView*)frontView toBack:(UIView*)backView destination:(CGRect)destRect
{
float duration = 0.5;
// distance from center of screen from frontView
float dx = destRect.origin.x + CGRectGetWidth(destRect) / 2 - frontView.center.x;
float dy = destRect.origin.y + CGRectGetHeight(destRect) / 2 - frontView.center.y;
float scale = CGRectGetWidth(destRect) / CGRectGetWidth(frontView.bounds);
// this prevents any tearing
backView.layer.zPosition = 200.0;
// hide the backView and position where frontView is
backView.hidden = NO;
backView.alpha = 0.0;
backView.frame = frontView.frame;
// start the animation
[UIView animateKeyframesWithDuration:duration
delay:0.25
options:UIViewKeyframeAnimationOptionCalculationModeCubic
animations:^{
// part 1. Rotate and scale frontView halfWay.
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{
// get the transform for the blue layer
CATransform3D xform = frontView.layer.transform;
// translate half way
xform = CATransform3DTranslate(xform, dx/2, dy/2, 0);
// rotate half way
xform = CATransform3DRotate(xform, M_PI_2, 0, 1, 0);
// scale half way
xform = CATransform3DScale(xform, scale/2, scale/2, 1);
// apply the transform
frontView.layer.transform = xform;
}];
// part 2. set the backView transform to frontView so they are in the same
// position.
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.0
animations:^{
backView.layer.transform = frontView.layer.transform;
backView.alpha = 1.0;
}];
// part 3. rotate and scale backView into center of container
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{
// undo previous transforms with animation
backView.layer.transform = CATransform3DIdentity;
// animate backView into new location
backView.frame = destRect;
}];
} completion:^(BOOL finished) {
self.displayingFront = !self.displayingFront;
}];
}
// flip from back to front
- (void) flipFromBack:(UIView*)backView toFront:(UIView*)frontView
{
float duration = 0.5;
// get distance from center of screen to destination
float dx = backView.center.x - frontView.center.x;
float dy = backView.center.y - frontView.center.y;
backView.layer.zPosition = 200.0;
frontView.hidden = YES;
// this is basically the reverse of the previous animation
[UIView animateKeyframesWithDuration:duration
delay:0
options:UIViewKeyframeAnimationOptionCalculationModeCubic
animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{
CATransform3D xform = backView.layer.transform;
xform = CATransform3DTranslate(xform, -dx/2, -dy/2, 0);
xform = CATransform3DRotate(xform, M_PI_2, 0, 1, 0);
xform = CATransform3DScale(xform, 0.75, 0.75, 1);
backView.layer.transform = xform;
}];
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.0
animations:^{
backView.alpha = 0.0;
frontView.hidden = NO;
}];
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{
self.hiddenView.alpha = 0.0;
frontView.layer.transform = CATransform3DIdentity;
}];
} completion:^(BOOL finished) {
self.displayingFront = !self.displayingFront;
}];
}