What I wish to do is to fade-in and fade-out some custom annotation views. In order to do this I need to have the pointers to those views. Have been thinking of collecting these pointers in mapView:viewForAnnotation. But I am not sure if this is the right way.
The reason is since annotation views are set up to be recycled, I worry that they can be detached from their annotations once they are moved out of the screen. So my thinking is, view pointers collected in mapView:viewForAnnotation, may not be reliable since we don't know if they are still on the screen or not.
Hope somebody can suggest a proper way to keep track of these view pointers. Or suggest some other standard or reliable way of fading in and out the annotation views.
Update :
Just found out that viewForAnnotation does work. And I don't really need to keep track of the annotations (at least in this case) if they are on screen or not.
Try this where you get to the zoom that you want to animate the annotationViews:
for (MyAnnotation *annotation in myMapView.annotations)
{
MKAnnotationView *annotationView = [myMapView viewForAnnotation:annotation];
//Do your animation to the annotationView
//Example for fadeOut - fadeIn
[UIView animateWithDuration:0.5 animations:^{
annotationView.alpha = 0.2;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.5 animations:^{
annotationView.alpha = 1;
}];
}];
}
If you have all kinds of annotationView types and you want to animate just part of them, then you will need to check the type before the animation.
Related
My project has several views that are subject to radial gravity at the center of their parent view, and elastic collisions so that they don't overlap. It works great, but sometimes I need to resize the views, and, when a view gets larger, the other views must move away from it so that all the views remain non-overlapping. I would expect the collision behavior to realize things are touching and to move them apart, but it doesn't seem to.
Here's what I'm trying now:
[UIView animateWithDuration:0.5 animations:^{
view.frame = CGRectInset(view.frame, -30, -30);
} completion:^(BOOL finished) {
[self.animator updateItemUsingCurrentState:view];
}];
The view grows just fine, the animation delegate is informed that the animation has resumed, but the other views don't move away. The newly enlarged view just overlaps (or underlaps depending on z order) the other views.
I tried changing the transform and not the frame - no dice. I tried removing the view from all the behaviors - no dice. I tried removing some and all of the behaviors from the animator - no dice. I tried calling updateItemUsingCurrentState: on all of the views - no dice. I need some dice!
Can you help? Thanks...(swift-ies, feel free to answer is swift. I'll translate it)
Thanks to this post, I learned that all behaviors that include the item being changed must be removed from the animator, then, after changing the view's properties, the behaviors should be added back to the animator.
I think that's crazy, but it worked.
[self.animator removeBehavior:self.behavior]; // for all behaviors that have view as an item
[UIView animateWithDuration:0.5 animations:^{
view.frame = CGRectInset(view.frame, -30, -30);
} completion:^(BOOL finished) {
[self.animator updateItemUsingCurrentState:view];
[self.animator removeBehavior:self.behavior];
}];
I'm adding and removing annotations to a map view. When I remove one it disappears abruptly and looks a bit startling, I'd rather it faded away gracefully.
I tried removing it with UIView:animateWithDuration: but it wasn't an animatable attribute.
If there's no other easy solution I was thinking I could get the annotation view to fade its alpha and then remove its annotation from the map. However the problem with this is it doesn't seem like an annotation view has a reference to its map view? Adding one starts to get a bit messy.
Is there some easy quick solution to removing an annotation gracefully?
Using animateWithDuration should work fine. To fade the removal of an annotation, one can:
MKAnnotationView *view = [self.mapView viewForAnnotation:annotation];
if (view) {
[UIView animateWithDuration:0.5 delay:0.0 options:0 animations:^{
view.alpha = 0.0;
} completion:^(BOOL finished) {
[self.mapView removeAnnotation:annotation];
view.alpha = 1.0; // remember to set alpha back to 1.0 because annotation view can be reused later
}];
} else {
[self.mapView removeAnnotation:annotation];
}
I think your proposed solution is the correct one. Set up an animation to opacity = 0, then, upon completion, remove the annotation from the MKMapView. The code that starts the animation doesn't have to be in the view itself; a better location for the code may be the view controller. Consider using NSNotificationCenter to notify the view controller that an annotation is requesting fade-out-and-remove.
I have a UIView which is attached to a UICollisionBehavior. I'm trying to animate its center when a new location is tapped on the screen, but it appears using this block of code is not the way to do it:
[UIView animateWithDuration:0.7f
delay:0.0f
options:UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState
animations:^{
[self.mainCharacter setCenter:location];
}
completion:nil];
Can anyone suggest a more UIKitDynamics friendly way of doing this?
Thanks.
UPDATE
So the first part of my question was similar to the one posted and a possible duplicate, however, although technically it solved the issue in the first part of this question, it does seem to interfere with how collision testing in UICollisionBehavior.
The second part of this question was to ask if there was a better way of doing this. I assumed it would be possible to accelerate a UIDynamicItem at a speed over a given time using UIPushBehavior but it seems to be constant.
Can anyone recommend how to use a UIPushBehavior to do the above?
In UIKit Dynamics, when you want to animate changing the center, you'd add a UISnapBehavior to your UIDynamicAnimator (removing any prior snap behavior first). For example, create a property for the UISnapBehavior, and then your tap gesture recognizer can snap it to that point (honoring your UICollisionBehavior), like so:
- (void)handleTap:(UITapGestureRecognizer *)gesture
{
if (self.snap)
[self.animator removeBehavior:self.snap];
CGPoint point = [gesture locationInView:gesture.view];
UISnapBehavior *snap = [[UISnapBehavior alloc] initWithItem:self.viewToAnimate snapToPoint:point];
[self.animator addBehavior:snap];
self.snap = snap;
}
You can control the "bounciness" by adjusting the damping property of the UISnapBehavior. If you don't like it rotating the view as it's snapping, you can also add a UIDynamicItemBehavior with allowsRotation set to NO.
I've looked high and low for a solutions for this and I've gotten nowhere.
I want to develop an iOS app that is pretty much a simple tap, animate and play sound.
So I'd have a map of say a farm and when I click the sheep, it will animate and make some noise. I have managed to achieve this and its all good.
My issue and question is being able to click an already animating button.
So when the view loads i would like to have cloud buttons move from left to right in the sky slowly, again I have already achieved this using this code
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidLoad];
[UIView animateWithDuration:8.5
delay:1.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut
animations:^{
cloudAnimate.center = CGPointMake(-100.0, 100.0);
}
completion:NULL];
}
BUT, when the buttons are animating from left to right and back they are not clickable so the touch I want for spinning the clouds and playing the sound doesn't work. I have read a few other posts that say that this can not be done but I have a load of apps on my iPhone and iPad that do this so anyone have any idea how this can be achieved?
Any help would be greatly appreciated as I'm pulling my hair out.
Thanks in advance.
EDIT:
OK so thanks to a combination of answers below I have almost got this to work
So I am using this to set the initial CGPoint:
- (void)viewDidLoad
{
[super viewDidLoad];
// Move UIView
cloudAnimate.center = CGPointMake(400.0, 100.0);
}
And to animate the cloud from left to right I am using the same code as i originally had with a slight tweak:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidLoad];
[UIView animateWithDuration:8.5
delay:1.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat | UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
cloudAnimate.center = CGPointMake(100.0, 100.0);
}
completion:NULL];
}
And now instead of IBAction I am using this:
-(void) touchesBegan:(NSSet*) touches withEvent:(UIEvent *) event {
CGPoint point = [[touches anyObject] locationInView:self.view];
if([self.cloudAnimate.layer.presentationLayer hitTest:point]) {
SystemSoundID soundID;
NSString *soundFile = [[NSBundle mainBundle] pathForResource:#"Super_Mario_Bros_Mushroom" ofType:#"mp3"];
AudioServicesCreateSystemSoundID((__bridge CFURLRef) [NSURL fileURLWithPath:soundFile], & soundID);
AudioServicesPlaySystemSound(soundID);
cloudAnimate.imageView.animationImages = [NSArray arrayWithObjects:[UIImage imageNamed:#"panda-png-chewing"],[UIImage imageNamed:#"panda-png-happy"],nil];
cloudAnimate.imageView.animationDuration = 0.2;
cloudAnimate.imageView.animationRepeatCount = 6;
[cloudAnimate.imageView startAnimating];
}
}
So now it does almost exactly what I want but if you hit the cloud when it is at its finishing CGPoint (even though it will carry on moving) the app crashes. If I hit it while it is moving it makes a sound and animate.
Anyones have any suggestions as to why this is?
When you animate a view you set the view in its final state. This way you can only detect touches in the final position where you are animating.
However, it is possible to go around that issue. You need to catch the touch event and comparate with the presentationLayer.
-(void) touchesBegan:(NSSet*) touches withEvent:(UIEvent *) event {
CGPoint point = [[touches anyObject] locationInView:self.view];
if([self.cloudAnimate.layer.presentationLayer hitTest:point]) {
//do something
}
}
The presentation layer has information about the visual position of the CALayer that is associated with your cloudAnimate.
You may want to use UIViewAnimationOptionAllowUserInteraction as well.
The short answer is that you can't tap views while they are animating. The reason is that the views don't actually travel from the start to the end location. Instead, the system uses a "presentation layer" in Core Animation to create the appearance of your view object moving/rotating/scaling/whatever.
What you have to do is attach a tap gesture recognizer to a containing view that completely encloses the animation (maybe the entire screen) and then write code that looks at the coordinates of the tap, does coordinate conversion, and decides if the tap is on a view that you care about. If the tap is on a button and you want the button to highlight you'll need to handle that logic too.
I have a sample project on github that shows how to do this when you are doing both UIView animation and Core Animation with CABasicAnimation (which animates layers, not views.)
Core Animation demo with hit testing
I've searched and saw this question asked before but still haven't found an answer sufficient for my needs.
I have an some UIImageViews in my view. They are stored in a NSMutableArray property called viewArray. However, when I run the following animation code, the UIImageViews just skip to their final state, without any animation. If I change the duration to 10.0, the animation works fine, but that's too long for my purpose.
[UIView animateWithDuration:1.0
animations:^{
for (UIImageView *view in viewArray) {
view.transform = CGAffineTransformScale(view.transform, 1.5, 1.5);
}
}];
These UIImageViews are already in the view since I saw some answers that addSubview is not instantaneous so that's not the problem.
What exactly is going on? Is there something I need to keep in mind in designing animations?