I have a problem when playing multiple UIView animation blocks.
What I have is a game with a lot of buttons, when you press a button it spawns an UILabel which animates to the top of the screen where is gets "added" to the total point amount, this is done by using 3 nested blocks.
This works well an lets me spawn as many point labels as possible with animations on them.
//setup the label
UILabel *pointIndicator = [[UILabel alloc]initWithFrame:CGRectMake(button.button.frame.origin.x, button.button.frame.origin.y, 60, 30)];
pointIndicator.backgroundColor = [UIColor clearColor];
pointIndicator.font = [UIFont boldSystemFontOfSize:25];
pointIndicator.alpha = 0;
pointIndicator.layer.shadowOffset = CGSizeMake(1.0f, 1.0f);
pointIndicator.layer.shadowOpacity = 0.6;
pointIndicator.layer.shadowRadius = 1;
[pointIndicators addObject:pointIndicator];
[self.view addSubview:pointIndicator];
CGPoint scoreLabelPosition = [self.view convertPoint:self.totalScoreLabel.frame.origin fromView:self.totalScoreLabel.superview];
CGSize scoreLabelSize = [self.totalScoreLabel.text sizeWithFont:self.totalScoreLabel.font];
[UILabel animateWithDuration:0.3 delay:0 options:UIViewAnimationCurveEaseIn animations:^{
//Make label appear and move it above button
CGRect frame = pointIndicator.frame;
frame.origin.y -= 30;
pointIndicator.frame = frame;
pointIndicator.alpha = 1;
}completion:^(BOOL finished){
[UILabel animateWithDuration:0.4 delay:0 options:UIViewAnimationCurveEaseInOut animations:^{
//Move the label next to the score label
NSInteger YPosition = 0;
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad){
YPosition = 15;
}else{
YPosition = 2;
}
CGRect frame = pointIndicator.frame;
frame.origin.x = scoreLabelPosition.x + self.totalScoreLabel.frame.size.width/2 + scoreLabelSize.width/2 + 5;
frame.origin.y = scoreLabelPosition.y - self.totalScoreLabel.frame.size.height/2 + YPosition;
pointIndicator.frame = frame;
}completion:^(BOOL finished){
[UILabel animateWithDuration:0.5 animations:^{
pointIndicator.alpha = 0;
}completion:^(BOOL finished){
//Animate point label to increase a bit in size
CABasicAnimation *pointAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
pointAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pointAnim.duration = 0.1;
pointAnim.repeatCount = 1;
pointAnim.autoreverses = YES;
pointAnim.removedOnCompletion = YES;
pointAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.5, 1.5, 1.0)];
[self.totalScoreLabel.layer addAnimation:pointAnim forKey:nil];
[pointIndicator removeFromSuperview];
[pointIndicators removeObject:pointIndicator];
}];
}];
}];
However, when the game is ended all the buttons animate out of the screen and an image view grows in size out of the middle, each are using 1 animation block.
The problem is that if an UILabel is animating across the screen at the same time as the game ends it cancels the button animation AND the growing image animation. If no UILabels are spawned everything plays as it should. I tried to cancel and remove all the point labels before playing the other animations, but with no luck. It only seems to work if all the previous animations have been completed normally in advance. This is how I remove them:
for(UIView *pointView in pointIndicators){
[pointView.layer removeAllAnimations];
[pointView removeFromSuperview];
}
[pointIndicators removeAllObjects];
[self.totalScoreLabel.layer removeAllAnimations];
all the views which are animated are subviews of the same UIView. I noticed that if I make the point labels subviews of the buttons all the animations can play at the same time.
I cant seem to figure out this behavior, is there some sort of conflict between the animations?
Related
I have created custom buttons and worked with custom UIViews in the past. However I am not sure how to go about with this.
I am trying to create something similar to the twitter heart animation.
What I am unable to get is those tiny colored circles around the heart as shown in this gif:
https://dribbble.com/shots/2416983-Twitter-Heart-Animation
Any ideas?
Also is it possible for me to simply have a gif and add the gif as a custom view in a UIButton and play the gif when button is pressed?
Here's a concept:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIView *bubble = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 16.0, 16.0)];
bubble.layer.cornerRadius = 8.0;
bubble.center = self.view.center;
bubble.backgroundColor = [UIColor greenColor];
[self.view addSubview:bubble];
UIView *heart = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
heart.layer.cornerRadius = 50.0;
heart.center = self.view.center;
heart.backgroundColor = [UIColor blueColor];
[self.view addSubview:heart];
[UIView animateKeyframesWithDuration:2.0
delay:0.0
options:UIViewKeyframeAnimationOptionRepeat
animations:^{
// Heart expands
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.10 animations:^{
heart.transform = CGAffineTransformMakeScale(1.3, 1.3);
}];
// Heart contracts.
[UIView addKeyframeWithRelativeStartTime:0.15 relativeDuration:0.25 animations:^{
heart.transform = CGAffineTransformMakeScale(1.0, 1.0);
}];
// Bubble travels.
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.4 animations:^{
CGPoint destination = self.view.center;
destination.x += 100;
destination.y -= 100;
bubble.center = destination;
}];
// Bubble shrinks.
[UIView addKeyframeWithRelativeStartTime:0.6 relativeDuration:0.2 animations:^{
bubble.transform = CGAffineTransformMakeScale(0.3, 0.3);
}];
// Bubble fades.
[UIView addKeyframeWithRelativeStartTime:0.8 relativeDuration:0.2 animations:^{
bubble.alpha = 0.0;
}];
} completion:nil];
}
To get the full effect, you will need to add any a few more key frames so the bubble curves around before the fade. Given that you have a couple of bubbles, it might be a good idea to create a key frame generator.
Alternatively, you can use SKEmitterNode and add real particles to your button.
Late Answer but useful to other developers.
Here is the Link for exact same animation: https://github.com/xhamr/fave-button
Same Animation in Objective-C also: https://github.com/tljackyi/fave-button
Much like other apps, my app has a "welcome" page controller with a quick overview of the features. During this overview, I draw a UITabBar and have a circle show where the relevant feature is seen below:
I draw the circle using the following code that is executed every time a page is drawn:
double circleSize = 75;
[circleView removeFromSuperview];
circleView = [[UIView alloc] initWithFrame:CGRectMake(circleX,
circleY,
circleSize,
circleSize)];
circleView.layer.cornerRadius = (circleSize / 2);
circleView.layer.borderColor = [UIColor VancityTransitBlue].CGColor;
circleView.layer.borderWidth = 2;
I wish to have this circle appear to "breath" (grow slowly then shrink back to its original size). I use the exact code from my answer to this question:
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
This circle is shared across three pages of the page controller and draws just fine. The animation works just fine on the first page, periodically works on the second page, and never works on the third page.
How can I have the animation play on every page for every circle view?
Here is a solution that works across all tabs. The animation can be moved around while in progress. You may want to use a better mechanism to manage the animation so that it can be cancelled efficiently. The code below is implemented in the Tab Controller. Ensure -showOverTabBarItem: and -hideCircleView are executed on main thread. It has been built, linked, ran and tested.
Show
-(void)showOverTabBarItem:(CGFloat)x {
[self hideCircleView];
self.circleView.frame =({
CGRect frame = self.circleView.frame;
frame.origin.x = x;
frame;
});
[self.view addSubview:self.circleView];
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
self.circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
}
Hide
-(void)hideCircleView
{
[self.circleView removeFromSuperview];
}
Initialize
Such as in viewDidLoad.
double circleSize = 75;
CGFloat circleY = self.view.bounds.size.height-(circleSize/2);
self.circleView = [[UIView alloc] initWithFrame:CGRectMake(0,
circleY,
circleSize,
circleSize)];
self.circleView.layer.cornerRadius = (circleSize / 2);
self.circleView.layer.borderColor = [UIColor blueColor].CGColor;
self.circleView.layer.borderWidth = 2;
Invoke
Pass the horizontal position to your animation: [self showOverTabBarItem: self.view.bounds.size.width/2];
Keep the view around
This is a critical step: when your -removeFromSuperView, you want to make sure that the object does not get recycled.
#interface TabController ()
#property (nonatomic, retain) UIView * circleView;
#end
Why have you alloc the circle every time a page drawn?
You can remove these lines (just need these for the first init):
if (circleView == nil) {
circleView = [[UIView alloc] initWithFrame:CGRectMake(circleX,
circleY,
circleSize,
circleSize)];
circleView.layer.cornerRadius = (circleSize / 2);
circleView.layer.borderColor = [UIColor VancityTransitBlue].CGColor;
circleView.layer.borderWidth = 2;
}
For add to another page: remove from current page
[circleView removeFromSuperview];
[circleView.layer removeAllAnimations];
and then add it to another page:
[self.view addSubView:circleView];
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
I've an issue while animating a VisualEffetView.
Here is the code where I declare it.
UIBlurEffect* blur = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
effectView = [[UIVisualEffectView alloc] initWithEffect:blur];
effectView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
effectView.frame = self.bounds;
self.layer.borderWidth = 1;
[self addSubview:effectView];
When I animate the superview to make it grow, the Visual Effect follow the animation of the superview, but when I want to reduce it, the Visual Effect disappear instantly making the result very ugly ^^
Here is the code where I animate the superview (called recherche here)
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionTransitionNone
animations:^{
CGRect f = recherche.frame;
f.size.height = 0;
[recherche setFrame:f];
}
completion:^(BOOL finished){
[recherche removeFromSuperview];
recherche = nil;
}];
Here is what I have when I make recherche disappear. The white square seems to be the Effect View because the color changes if I change the UIBlurEffectStyle. But blurring effect is missing x)
From above understanding, I figure out that you just need to reset height of effect view to achieve what you want.
Added code in animation block:
Check and let me know, is it full fill your expectation.
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionTransitionNone
animations:^{
CGRect f = recherche.frame;
f.size.height = 0;
[recherche setFrame:f];
// Add this lines
CGRect effectViewFrame = effectView.frame;
effectView.size.height = 0;
effectView.frame = effectViewFrame;
}
completion:^(BOOL finished){
[recherche removeFromSuperview];
recherche = nil;
}];
At the moment I'm struggling to find a way to remove the offset glitch when I try to change the frame of a UIImageView which has a motionGroup attached to it.
This is the code for adding the motion effect:
UIInterpolatingMotionEffect *xAxis = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
xAxis.minimumRelativeValue = #(-25.0);
xAxis.maximumRelativeValue = #(25.0);
self.motionGroup = [[UIMotionEffectGroup alloc] init];
self.motionGroup.motionEffects = #[xAxis];
[self.backImageView addMotionEffect:self.motionGroup];
now when i need to change the origin I do:
[self.backImageView removeMotionEffect:self.motionGroup];
[UIView animateWithDuration:0.3 animations:^{
CGRect frame = self.backImageView.frame;
frame.origin.x = self.originalBackImageViewOffset.x + 40;
self.backImageView.frame = frame;
} completion:^(BOOL finished) {
self.originalBackImageViewOffset = self.backImageView.frame.origin;
[self.backImageView addMotionEffect:self.motionGroup];
}];
this way there is a little glitch if the device is tilted. It appears right after removeMotionEffect: as the imageView transitions to it's original state and only then the frame changes. If the phone isn't tilted you won't even see the glitch (in the simulator).
If you don't remove the animationGroup then there is an even bigger glitch is you tilt the phone during the animation.
Does anyone know how to overcome this problem?
p.s. I don't have enough reputation to create a tag "UIMotionEffectGroup" or "UIMotionEffect".
Instead of animating the frame, apply a transform, then set the frame when the animation completes, without removing the motion effect:
[UIView animateWithDuration:0.3 animations:^{
self.backImageView.transform = CGAffineTransformMakeTranslation(40, 0);
} completion:^(BOOL finished) {
CGRect frame = self.backImageView.frame;
frame.origin.x = self.originalBackImageViewOffset.x + 40;
self.backImageView.frame = frame;
self.originalBackImageViewOffset = self.backImageView.frame.origin;
}];
I'm doing a simple animation of UIView height so that it reveals.
By default it seems to be revealing from top to bottom, and I want it to reveal bottom to top.
I have the UIView anchored to the bottom of the screen.
I'm sure it something simple i'm missing..... any tips?
Thanks
I really think the simplest way to accomplish this would be to animate BOTH the height and the y properties of the view. If they happen along the same curve, it should look completely seamless to the user. As you are animating the height to 0, also animate the y component to the original y + the original height.
UIView *view = ...;
float originalY = view.frame.origin.y;
float originalH = view.bounds.size.height;
[UIView animateWithDuration:1.2f delay:1.0f options:UIViewAnimationCurveEaseInOut animations:^{
view.frame = CGRectMake(view.frame.origin.x, (originalY + originalH), view.bounds.size.width, 0);
}completion:^(BOOL finished) {
NSLog(#"Animation is complete");
}];
I believe this would give the look and feel of a collapsing view. I haven't tried this out in code, but I see no reason why it wouldn't be possible like this.
hide under bottom
[self animateViewHeight:myView withAnimationType:kCATransitionFromBottom];
for reverse animation
[self animateViewHeight:myView withAnimationType:kCATransitionFromTop];
...
- (void)animateViewHeight:(UIView*)animateView withAnimationType:(NSString*)animType {
CATransition *animation = [CATransition animation];
[animation setType:kCATransitionPush];
[animation setSubtype:animType];
[animation setDuration:0.5];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[animateView layer] addAnimation:animation forKey:kCATransition];
animateView.hidden = !animateView.hidden;
}
Like a dog with a bone I figured this out....
Instead of animating the frame height, I applied a transform to the view and set the anchor point of the layer.
//set the anchor point to the bottom of the view
[self setAnchorPoint:CGPointMake(0.5, 1.0) forView:hostView];
//Scale the height to close to zero
hostView.transform = CGAffineTransformMakeScale(1, 0.00001);
If I put 0 as the y scale, the view behaves weird.... at the end of the animation i just set it to hidden.
On the way back up I just use the Identity Transform (reset it)
hostView.transform = CGAffineTransformIdentity;
Note that changing my anchor point shifted the position of my view. See this post for the setAnchorPoint method which normalises the view after setting the anchorPoint
Changing my CALayer's anchorPoint moves the view
Instead you could try putting it in a view with clipsToBounds = YES and then animate it from the bottom to the middle of the view, like so:
viewToAnimate.frame = CGRectMake(viewToAnimate.frame.origin.x,
viewToAnimate.superview.frame.size.height,
viewToAnimate.frame.size.width,
viewToAnimate.frame.size.height);
[UIView animateWithDuration:0.5 animations:^{
viewToAnimate.center = viewToAnimate.superview.center;
}];
This way, you don't have to set the height to 0, and it solves any problems with autoresizing within the view.
As requested, this is the code that I'm using... I'm using a CAKeyFrameAnimation, which may be a bit more than what you're looking for. It would probably work the same with a CABasicAnimation, I'm just showing you this code because I already have it written.
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
springLayer = [[CALayer alloc] init];
springLayer.backgroundColor = [UIColor redColor].CGColor;
springLayer.anchorPoint = CGPointMake(0, 1);
springLayer.frame = CGRectMake(125, 285, 100, 115);
[springLayer setNeedsDisplay];
[self.layer addSublayer:springLayer];
[self test];
}
return self;
}
-(void)test {
CAKeyframeAnimation *heightAnim = [[CAKeyframeAnimation alloc] init];
heightAnim.duration = 3;
heightAnim.removedOnCompletion = NO;
heightAnim.fillMode = kCAFillModeForwards;
heightAnim.beginTime = CACurrentMediaTime() + 0.25;
NSMutableArray *v = [[NSMutableArray alloc] init];
NSMutableArray *t = [[NSMutableArray alloc] init];
float dest = 250;
float difference = 135;
while (difference > 1.0) {
[v addObject:[NSNumber numberWithFloat:dest-difference]];
[t addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
difference *= 0.7;
[v addObject:[NSNumber numberWithFloat:dest+difference]];
[t addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
difference *= 0.7;
}
heightAnim.values = v;
heightAnim.timingFunctions = t;
[springLayer addAnimation:heightAnim forKey:#"bounds.size.height"];
}
one way I've done it with an AdWhirlView, hide it below the screen, then animate it up;
AdWhirlView *adWhirlView = [AdWhirlView requestAdWhirlViewWithDelegate:self];
adWhirlView.delegate = self;
adWhirlView.frame = CGRectMake(0, 430+kAdWhirlViewHeight, kAdWhirlViewWidth, kAdWhirlViewHeight);
[self.parentViewController.view insertSubview:adWhirlView belowSubview:self.view];
[UIView beginAnimations:#"AdWhirlIn" context:nil];
[UIView setAnimationDuration:.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
adWhirlView.frame = CGRectMake(0, 430, kAdWhirlViewWidth, kAdWhirlViewHeight);
[UIView commitAnimations];