Different Objective C syntax for functiona call - objective-c

[UIView animateWithDuration:1.0 animations:^(void) {
im.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
[UIView animateWithDuration:1.0 delay:1.0 options:UIViewAnimationCurveEaseOut animations:^(void) {
im.alpha = 0.0;
} completion:^(BOOL finished) {
[im removeFromSuperview];
}];
}];
The code is for animating UIImageView, that I know;
I want to know the calling mechanism, as I haven't seen this kind of function call for the very first time.
Mainly, What is ^(void) and why im.transform = CGAffineTransformIdentity; passed to it?
I have gone thorugh Apple docs, to find anything related to this function call, and I got it too, but I didn't get any idea from there; or I might have been to wrong section.
Can anyone here guide me to this?

This is called a block, and was introduced in iOS 4 and Mac OS X 10.6.
Here are some links where you can learn more about them:
Apple's Documentation - Blocks Programming Topics
Ask Big Nerd Ranch: Blocks in Objective-C
Programming with C Blocks
Introduction to Blocks in Objective-C – Part 1
The above example should read as follows:
// Start an animation over the next 1 second
[UIView animateWithDuration:1.0 animations:^(void) {
// For this animation, animate from the current value of im.transform back to the identity transform
im.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) { // At the completion of the first animation...
// Wait 1 second, then start another 1-second long animation
[UIView animateWithDuration:1.0 delay:1.0 options:UIViewAnimationCurveEaseOut animations:^(void) {
im.alpha = 0.0; // Fade out im during this animation
} completion:^(BOOL finished) { // When you complete this second animation
[im removeFromSuperview]; // Remove im from its superview
}];
}];
So you will have one second where im animates the removal of its transform, a one second delay, and then a one second fadeout of im.

Related

objective-c animations - how to set end position, direction and type

I've seen many examples and read tutorial on animations,
all examples look +- the same like so:
- (void)showAnimation
{
[UIView animateWithDuration:0.3f animations:^{
backgroundView.alpha = 1.0f;
}];
[UIView animateWithDuration:1.0f
delay:0.0f
usingSpringWithDamping:0.4f
initialSpringVelocity:0.0f
options:UIViewAnimationOptionCurveLinear
animations:^{
CATransform3D init = CATransform3DIdentity;
alertView.layer.transform = init;
}
completion:^(BOOL finished) {
if( [self.delegate respondsToSelector:#selector(alertViewDidAppear:)] && finished) {
[self.delegate alertViewDidAppear:self];
}
}];
}
what I'm failing to understand is in the animation block where:
1. I set the beginning position
2. set the end position
3. the direction of movement
4. type of animation (fly-in/ turn/ fade-in/appear etc.)
There is no "starting position". There is no "direction of movement" or "type of animation". Animation is a change over time. The view is a certain way at the time your code runs. UIView animation has 6 possible view properties. You change any of those in the animation block (which states the time) and the change is animated - that is, instead of the change just happening, kaboom, it is performed over the given time.
That's all there is to it (as far as UIView class-method animation is concerned).

Alpha changing instantly with UIView animateWithDuration: instead of animating

I'm attempting to fade in a UIView that I've created by animating the alpha. I need to fade it in, leave it visible for a few seconds, then fade it out.
The fade out function works fine. The view smoothly disappears. But the fade in just makes the view appear instantly instead of slowly appearing over an interval of 0.5 seconds.
So it seems like the fade in animation isn't working, just instantly setting the alpha to 1.0. I'm kind of at a loss here. Any ideas what I'm doing wrong? Thanks!
-(void)presentPopupPhrase:(NSString *)phrase
inView:(UIView *)view
withDelegate:(id)delegate
andCompletion:(void (^)(BOOL completed))completion {
MessagePopupView *pv = [[[MessagePopupView alloc] initWithFrame:self.frame andText:phrase] autorelease];
pv.alpha = 0.0;
[view addSubview:pv];
[self fadeInMPV:pv
withDuration:self.fadeDuration
andDelay:self.fadeInDelay];
[self fadeOutMPV:pv
withDuration:self.fadeDuration
afterDelay:self.fadeOutDelay
withCompletion:completion
andDelegate:delegate];
}
-(void)fadeInMPV:(MessagePopupView *)mpv
withDuration:(NSTimeInterval)duration
andDelay:(NSTimeInterval)delay
{
[UIView animateWithDuration:duration
delay:delay
options:UIViewAnimationOptionCurveLinear
animations:^{
mpv.alpha = 1.0;
}
completion:nil];
}
-(void)fadeOutMPV:(MessagePopupView *)mpv
withDuration:(NSTimeInterval)duration
afterDelay:(NSTimeInterval)delay
withCompletion:(void (^)(BOOL completed))completion
andDelegate:(id)delegate
{
[UIView animateWithDuration:duration
delay:delay
options:UIViewAnimationOptionCurveLinear
animations:^{
mpv.alpha = 0.0;
}
completion:completion];
}
EDIT:
If it helps, here's the VC code where I'm calling it from:
-(void)viewDidAppear:(BOOL)animated {
CGRect phraseFrame = CGRectMake(20, 341, 280, 65);
PopupPhraseController *phraseController = [[[PopupPhraseController alloc] initWithFrame:phraseFrame] autorelease];
[phraseController presentPopupPhrase:#"Test Phrase" inView:self.view withDelegate:self andCompletion:^(BOOL completed){
if (completed) {
NSLog(#"completed");
} else {
NSLog(#"not completed");
}
NSLog(#"blocked!");
}];
[super viewDidAppear:animated];
}
You can't chain the animations like that as the second animation block essentially causes the first one to be cancelled out.
You have two options for Linking Multiple Animations Together:
Use the completion handler of the first animation
Nest the animations but delay the second one
Looks something like this
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeInDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 1.0;
} completion:^(BOOL finished) {
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeOutDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 0.0;
} completion:completion];
}];
Looks something like this
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeInDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 1.0;
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeOutDelay + self.fadeDuration
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 0.0;
} completion:completion];
} completion:nil];
The issue is because of the way you are setting up the animations.
When you set animatable properties on a view the backing model of the view is updated instantly. So when you set the alpha = 1.0 in the first block the backing data model for the view has the alpha as 1.0 and then the actual animation is kicked off on another thread to show the interpolation between the two values.
When you define the second block straight after the first the view essentially says "ok I need to animate from 1.0 (my current value in the model) to 0.0", which is why you don't see what you expected.
Thanks to Paul.s for dropping the hint there. I think this expected behaviour is a little weird, even if it seems logical from a low level perspective.
To expand on Paul's 2 solutions, there is a 3rd solution which is better IMO.
Use GCD to add a delay to the fade out code, like such:
//Make sure this delay exceeds your fade-in time...
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self fadeOutMPV:pv
withDuration:self.fadeDuration
afterDelay:self.fadeOutDelay
withCompletion:completion
andDelegate:delegate];
});
This solution's better because the executing view controller has complete control on not only when, but what gets to trigger the fade out (for example, a user action).

Multiple UIView Animations

I am trying to perform multiple UIView animations one after the other. However, I've heard that it's bad practice to perform multiple UIView animations one after the other, and that I should instead use Core Animation. I tried this code:
//First Animation
[UIView beginAnimations:#"animation1" context:nil];
[UIView setAnimationDuration:2];
nwView.backgroundColor = [UIColor redColor];
nwView.frame = CGRectMake(CGRectGetMidX(screenSize),
CGRectGetMinY(screenSize),
width,
height);
nwView.transform = CGAffineTransformMakeRotation(45.0f);
[UIView commitAnimations];
//Second Animation
[UIView beginAnimations:#"second animation" context:nil];
[UIView setAnimationDuration:2];
nwView.transform = CGAffineTransformMakeScale(0.5, 0.33);
nwView.backgroundColor = [UIColor purpleColor];
[UIView commitAnimations];
But it only does the second animation. I know this question is similar to UIView two animations coexisting, but it has a slightly different context.
I don't think there is anything wrong with doing 2 animations in a row using UIView blocks. Just make sure you start your second animation in the completino block of the first animation.
Without blocks (your example) it is not working as you will have to set a delegate to the animation or set a selector for setAnimationDidStopSelector. There you should start the second animation.
But again, nothing wrong in doing animations with blocks (it is the preferred way).
If you are looking to do multiple animations right after another this is not the way to do it. The code you posted with execute at almost the same time, meaning the animations would be performed at about the same time.
Instead what you should do is set the delegate for the first animation, and then do the first animation. Then when the animationDidStop method is called for the first animation, you should do the second animation. This makes sure they are one after another.
This is how you would do it, assuming you call doMyAnimations to start the animation.
-(void)doMyAnimations{
//First Animation
[UIView beginAnimations:#"animation1" context:nil];
[UIView setAnimationDuration:2];
[UIView setAnimationDelegate:self];
nwView.backgroundColor = [UIColor redColor];
nwView.frame = CGRectMake(CGRectGetMidX(screenSize),
CGRectGetMinY(screenSize),
width,
height);
nwView.transform = CGAffineTransformMakeRotation(45.0f);
[UIView commitAnimations];
}
- (void)animationWillStart:(NSString *)animationID context:(void *)context{
}
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context{
if([animationID isEqualToString:#"animation1"]){
//Second Animation
[UIView beginAnimations:#"second animation" context:nil];
[UIView setAnimationDuration:2];
nwView.transform = CGAffineTransformMakeScale(0.5, 0.33);
nwView.backgroundColor = [UIColor purpleColor];
[UIView commitAnimations];
}
}
Keep in mind that the nwView would have to be accessible throughout the entire class. If it isn't you can either make it an instance variable, or find another way to get access to it in the animationDidStop method.
You can use blocks for this purpose and get a very clean result.
NSMutableArray* animationBlocks = [NSMutableArray new];
typedef void(^animationBlock)(BOOL);
// getNextAnimation
// removes the first block in the queue and returns it
animationBlock (^getNextAnimation)() = ^{
animationBlock block = (animationBlock)[animationBlocks firstObject];
if (block){
[animationBlocks removeObjectAtIndex:0];
return block;
}else{
return ^(BOOL finished){};
}
};
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:1.0 animations:^{
//...animation code...
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
[UIView animateWithDuration:1.0 animations:^{
//...animation code...
} completion: getNextAnimation()];
}];
//add a block to our queue
[animationBlocks addObject:^(BOOL finished){;
NSLog(#"Multi-step Animation Complete!");
}];
// execute the first block in the queue
getNextAnimation()(YES);
Taken from: http://xibxor.com/objective-c/uiview-animation-without-nested-hell/

List examples of how to "briefly draw attention" to an object on screen in iOS?

In iOS, how can one briefly draw attention to an object on screen? Suppose, create a brief glow or cause a shadow to appear and then disappear?
For the purposes of this question, let's define "object on screen" as an instance of UIImageView.
Also, if possible provide an example of how to draw attention to a button.
Most people list code, but I'm sticking to describing some examples;
I've seen objects briefly grow and shrink back to their normal size to draw attention
Bejeweled (a Popcap game) lets diamonds briefly 'shine' (as if sunlight passed over it) to give you a subtle hint
I've seen certain applications use a hand or a fictive character point to a certain object briefly
And of course, you could always introduce a talking paperclip to tell you what's what.
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.1f];
yourView.transform = CGAffineTransformMakeScale(1.1, 1.1);
[UIView commitAnimations];
Or, of course the same thing with a block animations. And after the attention got away from your view you can use :
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.1f];
yourView.transform = CGAffineTransformIdentity;
[UIView commitAnimations];
Here is a simple hop animation...
- (void)drawAttn
{
float jumpHeight = 20.0;
CGPoint originalPoint = objectForAttn.center;
CGPoint jumpPoint = CGPointMake(objectForAttn.center.x, objectForAttn.center.y - jumpHeight);
[UIView animateWithDuration:0.20 delay:0.0 options:UIViewAnimationOptionAutoreverse animations:^{
[objectForAttn setCenter:jumpPoint];
} completion:^(BOOL finished) {
/* do other stuff after hop */
}];
}
My app QCount (free in the store, tap a "wrong" number 3 times) uses a fade animation as follows:
(note I wrote this when I was REALLY new to iOS, so there is probably a more compact way to write it)
aLabel = // a UILabel I get from somewhere
aColor = aLabel.backgroundColor;
[UIView animateWithDuration:0.2
delay: 0.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
aLabel.alpha = 0.0;
}
completion:^(BOOL finished){
// Wait .2 seconds and then fade in the view
[UIView animateWithDuration:.2
delay: 0.0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
aLabel.alpha = 1.0;
}
completion:nil];
}];
There are lots of options: hopping, underlining, vibrating, blinking, rotating, scaling, endarkening around, and their combinations.
But IMO, shine effect on "slide to unlock" text is excellent.
You can check it out here:
iPhone "slide to unlock" animation
I wanted to draw attention to a label whose value had changed. This simple animation produces a nice 'I've changed' animation...
//copy the label
UILabel *newPageLabel = [[UILabel alloc] initWithFrame:_countLabel.frame];
[newPageLabel setFrame:CGRectOffset(newPageLabel.frame, 0, 0)];
newPageLabel.text = _countLabel.text;
newPageLabel.textAlignment = _countLabel.textAlignment;
newPageLabel.backgroundColor = _countLabel.backgroundColor;
newPageLabel.textColor = _countLabel.textColor;
newPageLabel.font = _countLabel.font;
//scale and fade out the copied label
[self.navigationController.toolbar addSubview:newPageLabel];
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionLayoutSubviews
animations:^{
newPageLabel.transform = CGAffineTransformMakeScale(2,2);
newPageLabel.alpha = 0;
}
completion:^(BOOL finished){
[newPageLabel removeFromSuperview];
}
];

Canceling an UIView Animation Block

The code below shows an animation of a label which contains a status message for the user. If an event happens the label is showing prompt and is slowly disappearing via uiview animation block.
- (void)showStatusOnLabelWithString:(NSString *)statusMessage
{
// [self.view.layer removeAllAnimations]; // not working
[labelStatus.layer removeAllAnimations]; // not working, too
[labelStatus setText:statusMessage];
[labelStatus setHidden:NO];
[labelStatus setAlpha:1.0];
[UIView animateWithDuration:5.0 animations:^
{
[labelStatus setAlpha:0.0];
} completion:^(BOOL finished)
{
[labelStatus setHidden:YES];
[labelStatus setAlpha:1.0];
}];
}
If there is another event in the following 5s after the first the label should animate again, so I removed the previous animation with [self.view.layer removeAllAnimations] (thats what I thought).
But the label just completely disappear and the next 5s the label is invisible again.
If I (or the user) wait(s) the 5s everything is working properly.
Why isn't this working?
Kind Regards,
$h#rky
Change this:
completion:^(BOOL finished)
{
[labelStatus setHidden:YES];
[labelStatus setAlpha:1.0];
}];
to this:
completion:^(BOOL finished)
{
if (finished) {
[labelStatus setHidden:YES];
[labelStatus setAlpha:1.0];
}
}];
The reason is you are reaching this completion block when you remove the animations for the layer, but finished will be false because you interrupted it. Also, the order is important here. Perhaps you were expecting removeAllAnimations to call the completion block instantly, but instead it will be called after your showStatusOnLabelWithString: method finishes, so what is happening is you are calling setHidden:NO followed immediately by setHidden:YES.
Did you try removing animations from the label's layer (labelStatus.layer)?