Good Moscow evening to everyone!
I'm still unfamiliar with principles of iPhone animation (btw, does anybody know a big and nice tutorial on this?), but in my project I want to do button "highlighted-not highlighted" flicker to notify the user that its label has changed.
This code doesn't do anything (it's just a fragment of flicker animation):
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration: 0.5];
[button setHighlighted: YES];
[UIView commitAnimations];
And this one highlights the button, but does not do it in an animated form:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration: 0.5];
[button setSelected: YES];
[UIView commitAnimations];
Can anyone help me and say:
What's wrong with this code?
What would fix the problem?
----------------------------------- UPDATE ------------------------------
I tried that kind of code, but it doesn't work either:
// ------------------------
// --- animation ----------
// ------------------------
- (void)animateIn
{
[UIView beginAnimations: #"animateIn" context:nil];
[UIView setAnimationDuration: 0.2];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
[control setBackgroundColor:[UIColor blackColor]];
[UIView commitAnimations];
}
- (void)animateOut
{
[UIView beginAnimations: #"animateOut" context:nil];
[UIView setAnimationDuration: 0.2];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
[control setBackgroundColor:[UIColor whiteColor]];
[UIView commitAnimations];
}
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
if([animationID isEqualToString: #"animateIn"])
{
[self animateOut];
return;
}
else if ([animationID isEqualToString: #"animateOut"])
{
cycleCount++;
if(cycleCount < 3)
[self animateIn];
else
cycleCount = 0;
return;
}
}
#end
//isFlickerOn is declared in .h file as BOOL isFlickerOn;
//flickerTimer is declared in .h file as NSTimer *flickerTimer;
//flickerCount is declared in .h file as int flickerCount;
//control is a UILabel, UIButton background color is really hard to notice
//especially the roundedRect UIButton, so I just flickered a UILabel's textColor
-(void)flickerOn {
if (flickerCount < 5)
{
flickerCount++;
if (!isFlickerOn)
{
[control setTextColor:[UIColor yellowColor]];
isFlickerOn = YES;
}
else
{
[control setTextColor:[UIColor blueColor]];
isFlickerOn = NO;
}
}
else
{
[flickerTimer invalidate];
}
}
-(void)flickerAnimation {
flickerCount = 0;
flickerTimer = [NSTimer scheduledTimerWithTimeInterval:0.3f target:self selector:#selector(flickerOn) userInfo:nil repeats:YES];
}
Not all attributes of a UIView are animatable - and I do not believe "highlighted" or "selected" are.
Typically, it's usable for non-boolean values, like "center", "alpha", "frame" and "bounds".
Try tweaking the alpha instead, and you'll see it will work.
You need to create a callback method that executes when the first animation finishes. You use that callback to do create a deselected animation. Keep in mind that there is no gradual "selection state" like there is for transparency. You have to use the
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
[UIView beginAnimations:#"animateIn" context:NULL];
From Apple's docs:
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
Your method must take the following arguments:
animationID
An NSString containing an optional application-supplied identifier. This is the identifier that is passed to the beginAnimations:context: method. This argument can be nil.
finished
An NSNumber object containing a Boolean value. The value is YES if the animation ran to completion before it stopped or NO if it did not.
context
An optional application-supplied context. This is the context data passed to the beginAnimations:context: method. This argument can be nil.
When the animation is finished, the callback animationDidStop is called and passed in the string #"animateIn". You can use that method to check which animation was called and handle that there.
Related
I have an animation in my app that grows a UIImageView and then shrinks it (really two animations). Throughout the app this may happen on several different UIImageViews. I found a way to do this that worked really well, but it now doesn't seem to be compatible with Automatic Reference Counting. Here is my code:
[UIView beginAnimations:#"growImage" context:imageName];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDelegate:self];
imageName.transform = CGAffineTransformMakeScale(1.2, 1.2);
[UIView commitAnimations];
and then:
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(UIImageView *)context {
if (animationID == #"growImage") {
[UIView beginAnimations:#"shrinkImage" context:context];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
[UIView setAnimationDelegate:self];
context.transform = CGAffineTransformMakeScale(0.01, 0.01);
[UIView commitAnimations];
}
}
This worked perfectly and I was very happy with it, until I tried converting my project to ARC. I now get the error "Implicit conversion of an Objective-C pointer to 'void *' is disallowed with ARC" on these lines in which I try to pass a UIImageView as the context for the animation:
[UIView beginAnimations:#"growImage" context:imageName];
[UIView beginAnimations:#"shrinkImage" context:context];
Does anybody know of another way that I can alert the "animationDidStop" function of which UIImageView I want it to act on that would be compliant with ARC?
Thanks so much in advance!
Any reason you are not using the much simpler block based animation?
[UIView animateWithDuration:0.5 animation:^{
imageName.transform = CGAffineTransformMakeScale(1.2, 1.2);
} completion ^(BOOL finished) {
[UIView animateWithDuration:0.5 animation:^{
imageName.transform = CGAffineTransformMakeScale(0.01, 0.01);
}];
}];
You can do as follows:
[UIView beginAnimations:#"growImage"
context:(__bridge void*)imageName];
imageName.transform = ...
[UIView commitAnimations];
I have a simple animation which simply modify the position of a button:
[UIView beginAnimation:nil context:nil];
[UIView setAnimationsDuration:3.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
mybutton.frame = CGREctMake(20, 233, 280, 46);
[UIView commitAnimations];
I want to perform some other animations when this one is finish, how to do that?
You could look at setAnimationBeginsFromCurrentState:. All the animations are run in their own threads, so [UIView commitAnimations] is a nonblocking call. So, if you begin another animation immediately after committing the first one, after appropriately setting whether or not it begins from the current state, I think you'll get the behavior you want. To wit:
[UIView beginAnimation:nil context:nil];
// set up the first animation
[UIView commitAnimations];
[UIView beginAnimation:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:NO];
// set up the second animation
[UIView commitAnimations];
Alternately, you could provide a callback by doing something like
[UIView beginAnimation:nil context:nil];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)
// set up the first animation
[UIView commitAnimations];
//...
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
// set up the second animation here
}
a code segment for getting start.. setup initial (the first) animation:
- (void) aniPath:(AnimationPath *) path
{
[UIView beginAnimations:path->aniname context:path];
[UIView setAnimationDuration:path->interval];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(aniDone:finished:context:)];
// your initial animation
[UIView commitAnimations];
}
chains to another animation when the first is done:
- (void) aniDone:(NSString *) aniname finished:(BOOL) finished context:(void *) context
{
AnimationPath *path = (AnimationPath *) context;
if ([aniname isEqualToString:path->aniname]) {
[UIView beginAnimations:path->aniname context:context];
[UIView setAnimationDuration:path->interval];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(aniDone:finished:context:)];
// more animations, even recursively
[UIView commitAnimations];
}
}
Use the +[UIVIew setAnimationDelegate:] and +[UIView setAnimationDidStopSelector:] methods to configure your class to receive a message when the animation ends.
it quite easier.
you can just
- (void)animationDidContiune:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
UIView *currentObj = context;
[self callTheAnimation: currentObj];
}
you can easily loop the animation.
To end it - I would create an animationDidEnded and use it instead of animationDidContiune
once you want to stop it (like simple if in animation that chooses either to contiune or end).
HF.
AND WHAT's IMPORTANT:
use this type of animating meths:
- (void)callTheAnimation:(UIView*)itemView
{
[UIView beginAnimations:#"animation" context:itemView];
.
.
.
}
they are quite flexible - you can animate whatever here due to hierarchy of the objects.
I'm a newbie and I need some help.
I want to display a popup image over a given UIView, but I would like it to behave like the UIAlertView or like the Facebook Connect for iPhone modal popup window, in that it has a bouncy, rubbber-band-like animation to it.
I found some code on the net from someone who was trying to do something similar. He/she put this together, but there was no demo or instructions.
Being that I am so new, I don't have any idea as to how to incorporate this into my code.
This is the routine where I need the bouncy image to appear:
- (void) showProductDetail
{
. . .
////////////////////////////////////////////////////////////////////////
// THIS IS A STRAIGHT SCALE ANIMATION RIGHT NOW. I WANT TO REPLACE THIS
// WITH A BOUNCY RUBBER-BAND ANIMATION
_productDetail.transform = CGAffineTransformMakeScale(0.1,0.1);
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
_productDetail.transform = CGAffineTransformMakeScale(1,1);
[UIView commitAnimations];
}
. . .
}
This is the code I found:
float pulsesteps[3] = { 0.2, 1/15., 1/7.5 };
- (void) pulse {
self.transform = CGAffineTransformMakeScale(0.6, 0.6);
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:pulsesteps[0]];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(pulseGrowAnimationDidStop:finished:context:)];
self.transform = CGAffineTransformMakeScale(1.1, 1.1);
[UIView commitAnimations];
}
- (void)pulseGrowAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:pulsesteps[1]];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(pulseShrinkAnimationDidStop:finished:context:)];
self.transform = CGAffineTransformMakeScale(0.9, 0.9);
[UIView commitAnimations];
}
- (void)pulseShrinkAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:pulsesteps[2]];
self.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
}
Thanks in advance for any help that you can give me.
It's pretty simple. In your showProductDetail method, you start an animation block, then set the _productDetail.transform property, and then commit the block. This is what makes the animation happen.
The code that you found is designed to do a chain of such animations, but the property being modified is on self instead of on _productDetail. If _productDetail is an instance of a class that you created yourself, you can put the animation code inside that class.
Otherwise, just move the code inside the pulse method to the showProductDetail method, and put the two other methods below that method. Replace self.transformwith _productDetail.transformin all three methods.
I have a barButtonItem that calls an action which swaps two views in an animation block. I disable the barButtonItem at the beginning of the action method that it calls, and the I want to enable it when its finished, so that the user can't flip the view again before it has finished flipping.
It does this in a UIView animation block, so If I enable it at the ned of the method, it will be instant and that destroys the purpose because it gets enabled before the animation finishes.
In the method, I cast sender to a UIBarButtonItem, and I disable that. So I found [UIView setAnimationDidStopSelector:#selector()], but I need to pass my barButtonItem in the selector so the enableControl Method will enable that (this would be a lot easier if I could make an anonymous function..).
So how can I pass the barButton as an argument? I'd rather not have to make an ivar just for this.. Here's the code:
- (void)barBtnItemSwitchViews_Clicked:(id)sender {
UIBarButtonItem *barButton = (UIBarButtonItem *)sender;
barButton.enabled = NO;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.8];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
if(!self.yellowVC.view.superview) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:innerView cache:YES];
[yellowVC viewWillAppear:YES];
[blueVC viewWillDisappear:YES];
[blueVC.view removeFromSuperview];
self.blueVC = nil;
[innerView addSubview:yellowVC.view];
} else if(!self.blueVC.view.superview) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:innerView cache:YES];
[blueVC viewWillAppear:YES];
[yellowVC viewWillDisappear:YES];
[yellowVC.view removeFromSuperview];
self.yellowVC = nil;
[innerView addSubview:blueVC.view];
}
[UIView commitAnimations];
[UIView setAnimationDidStopSelector:#selector(enableControl:)]; // How do I pass barButton??
}
- (void)enableControl:(UIControl *)control {
control.enabled = YES;
}
Here's what I changed it to, but the method isn't being called.. What am I doing wrong?
- (void)barBtnItemSwitchViews_Clicked:(id)sender {
UIBarButtonItem *barButton = (UIBarButtonItem *)sender;
barButton.enabled = NO;
[UIView beginAnimations:nil context:barButton];
[UIView setAnimationDuration:0.8];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
if(!self.yellowVC.view.superview) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:innerView cache:YES];
[yellowVC viewWillAppear:YES];
[blueVC viewWillDisappear:YES];
[blueVC.view removeFromSuperview];
self.blueVC = nil;
[innerView addSubview:yellowVC.view];
} else if(!self.blueVC.view.superview) {
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:innerView cache:YES];
[blueVC viewWillAppear:YES];
[yellowVC viewWillDisappear:YES];
[yellowVC.view removeFromSuperview];
self.yellowVC = nil;
[innerView addSubview:blueVC.view];
}
[UIView setAnimationDidStopSelector:#selector(flipViewAnimationDidStop:finished:context:)];
[UIView commitAnimations];
}
- (void)flipViewAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
UIBarButtonItem *barButton = (UIBarButtonItem *)context;
barButton.enabled = NO;
NSLog(#"Disabled");
}
EDIT: I figured it out. I had to make the button the context, and then do setAnimationDelegate:self, and then setAnimationDidEndSelector:#selector(myMethod:finished:context:), and then in the method cast context to uibarbuttonitem and re-enable it.
From UIView's setAnimationDidStopSelector: docs:
The selector should be of the form: - (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context.
The third argument, context, is an object of your choice, set in beginAnimations:context:. So it seems like what you're missing is to use barButton as the context argument, and then rewrite enableControl with a proper signature. In that method, you take the third argument, context, and cast it from void* to a UIBarButtonItem*.
I dont think you can pass more than one argument to the selector by using #selector, however you could make your button a class variable and enable or disable it like that in the selector (instead of it being passed as an argument), to define a selector with multiple arguments you can use NSInvocation, like so
NSInvocation *inv = [[NSInvocation alloc] init];
[inv setSelector:#selector(foo:bar:)];
[inv setArgument:123 atIndex:0];
[inv setArgument:456 atIndex:1];
But I dont think this will work for what you are trying to do.
I'm trying to change the text of an UILabel with a little transition (fade out, change text, fade in) but I'm facing some problems. Here is my code:
- (void) setTextWithFade:(NSString *)text {
[self setAlpha:1];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:.25];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(fadeDidStop:finished:context:)];
[self setAlpha:0];
[UIView commitAnimations];
}
- (void)fadeDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.25];
[self setAlpha:1];
[UIView commitAnimations];
}
This code "works" (the effect is working well), but what I can't figure out is how to change the label text in the "fadeDidStop" function... How can I "pass" the text variable from my first function to the second?
Thanks in advance
You pass the text in the context:
[UIView beginAnimations:nil context:text];
Then in your fadeDidStop method:
NSString *text = (NSString*) context;
Be mindful when passing objects in the context, make sure they are retained properly.
...
[UIView beginAnimations:nil context:[text retain]];
...
- (void)fadeDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
self.text = (NSStrinhg *)context;
[context release];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.25];
[self setAlpha:1];
[UIView commitAnimations];
}
maybe the simpliest way is to declare NSString object in your .h file and use it to store text to change.