I was wondering how animations work in Cocoa Touch. For example:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0];
view1.alpha = 1.0;
view2.frame.origin.x += 100;
[UIView commitAnimations];
How does UIView tracks the changes to the properties of the two views?
I suspect that KVO is used, but does it really observe the changes to every views between the beginAnimations and commitAnimations calls?
Any insight would be appreciated!
A simplified explanation:
All animations for all views are handled on a single stack, beginAnimations:context: will push a new active CAAnimationGroup on the stack, and commitAnimations will pop the current active animation group.
If there is an active animation group on the stack, then all setters for animatable properties will create CAAnimation subclass instances to handle that property and add then to the active animation group.
When the last animation is popped, it is replayed.
I believe it is just a wrapper over the old CoreAnimation calls. Look into the CoreGraphics API, you can see that there are multiple keys that you must set into a dictionary, while providing beginning and ending values for these keys.
Related
I want to fade-out a view as it is scrolling inside a parent UIScrollview. When the fade-out animation begins, the scroll view stops scrolling. It jumps to the correct position when the fade is complete.
My fade-out is achieved with animateWithDuration and block objects, triggered upon a page-change I detect in scrollViewWillBeginDragging.
Does anyone know how to make them both happen simultaneously? Just to be clear, I am not 'animating' the UIScrollView scrolling - rather it is happening via user interaction of swiping.
EDIT:
Here is the code I'm using to fade the UIView. This code is in a UIViewController derived class, which is the delegate for a UIScrollView. When the user starts dragging his finger, I want to fade out the subView. But when the user starts draggin a finger, the subview fades and the scrolling stops. After the subView has completely faded out, the the scroll view will then snap to the location where the user's finger is.
-(void)scrollViewWillBeginDragging:(UIScrollView*)scrollView
{
[UIView animateWithDuration:0.5
animations:^
{
self.subView.alpha = 0.0f;
}
completion:^(BOOL finished) { }];
}
A little late, but if you want to keep using blocks, you can use:
animateWithDuration:delay:options:animation:complete:
add "UIViewAnimationOptionAllowUserInteraction" to options to allow interaction while scrolling.
I'm sure that you will still have the lag problem. Here's the best way I can explain it. Please forgive me in advance since I'm probably using the wrong terms. All animations must run on the main thread. When you call an animation, iOS first *P*rocesses then it *R*enders before it generates *F*rames. It looks like this.
PPPPRRRRFFFFFFFFFFFFFFFFFF
But since ScrollViews don't know how long your animation is going to be or when it will end, it has to perform the animation like this.
PRFPRFPRFPRFPRFPRFPRFPRF
My theory is that the lag you are experiencing has to do with these two calls colliding on the main thread at the same time. I'm not sure how you would solve this problem other than with a faster chip. I've that you could push one animation to the CPU and one to the GPU, but I'm not that advanced at programming yet.
very interesting ... I've checked this out, and yes, i have the same effect ... Well, it seems that the animateWithDuration somehow blocks the main thread ... which is not logical, and the documentation doesn't say anything about it either ..
However there is an easy workaround, something similar to this: (i've set the animation duration to 3 so i can see that it's working while i'm moving my scroll view :) ...)
[UIView beginAnimations:#"FadeAnimations" context:nil];
[UIView setAnimationDuration:3];
self.subview.alpha = 0.0f;
[UIView commitAnimations];
I would suggest, since the opacity is based on the user's finger's movements in the UIScrollView, using the delegate method scrollViewDidScroll:. The scrollView passed as a parameter can be used to check the contentOffset which is simply a CGPoint indicating how far into the content view of the UIScrollView the user has scrolled. Something like this can be used to relate the scroll position to the opacity of a given view in a paginated UIScrollView:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// The case where I used this, the x-coordinate was relevant. You may be concerned with the y-coordinate--I'm not sure
CGFloat percent = ((int)(scrollView.contentOffset.x) % (int)(scrollView.frame.size.width)) / scrollView.frame.size.width;
if (percent > 0.0 && percent < 1.0) { // Of course, you can specify your own range of alpha values
relevantView.alpha = percent; // You could also create a mathematical function that maps contentOffset to opacity in a different way than this
}
}
According to information that is still not supposed to be widely released, all iOS 4.x versions completely block user interaction while the animation is in progress.
Isn't it interesting, though, that you're UITouches are obviously still registered during the animation? Hmm... maybe that HINTS that something NEW is coming in a yet-to-be-released version!
I.e., If you can, read the iOS 5 Beta documentation on UIView class methods.
I have a UIView with a bunch of subviews, all positioned using layoutSubviews. When the view is resized, the relative positions all change. I'd like these re-calculations to happen during an animated resize (using +[UIView beginAnimations:] calls). This doesn't seem to be happening. Any ideas?
Assumption: You want to have multiple animation steps (i.e. position doesn't change linearly with frame size).
This isn't possible with a single "standard" UIView animations. Why? The frame/bounds is only set once.
Core Animation has three "layer trees":
The model tree is where your app thinks things are.
The presentation tree is approximately what's being displayed on screen.
The render tree is approximately what Core Animation is compositing.
UIView is a (somewhat thin) wrapper around the model layer. During a UIView animation, Core Animation updates the presentation/render tree — the model tree represents the endpoint of animations. The upshot is that your code can (for the most part) treat animations as instantaneous — moving a view from A to B instantly moves it to B; the change just happens to be animated to the user.
There are more complicated things you can do with CALayer/CAAnimation directly, but I haven't investigated this much.
You could chain multiple animations together using -[UIView setAnimationDidStopSelector:]. (You could also try using multiple animations together with setAnimationDelay:, but I'm not sure what happens with multiple animations on the same property; you might have luck with setAnimationBeginsFromCurrentState:.)
If you want really fine-grained control, CADisplayLink (OS 3.1+) is a timer that fires after each screen refresh. A fallback option (for 3.0 support) is to use an NSTimer at 30/60 Hz or so.
I know this is an old question, but this code works for me very well (suited for your example of changing frame).
-(void)layoutSubviews{
[super layoutSubviews];
// layout your subviews here, or whatever
}
-(void)someMethod{
double duration=...;
[UIView animateWithDuration:duration animations:^{
self.frame = ...;
[self layoutIfNeeded];
}];
}
Of course you can call this method from another object. The "trick" is to call layoutIfNeeded (or layoutSubviews directly - same thing, if You change the frame the setNeedsLayout is called).
As tc. nicely explained the "layer trees", You just force the presentation layer to display the final stage of model layer with animation.
The advantage of this method is in possibility to control when the frame/bounds change is animated and when it's instant.
Hope this helps someone:).
Completing #GrizzlyNetch's anwer, you can set the UIViewAnimationOptionLayoutSubviews animation option, so you don't need to call layoutIfNeeded:
-(void)someMethod{
double duration = ...;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{
self.frame = ...;
} completion:nil];
}
Posting for completeness. Thanks to tc. for explaining that what I want to do, exactly, is not supported by Core Animation.
I eventually came up with a reasonable solution. Rather then layout my subviews in -layoutSubviews, I do so in -setBounds:. Then, when I wrap a -setBounds: call in a UIView +beginAnimations: block, those positioning calls are also animated, and the end result is everything properly animating to where it should god.
I've been trying to make a short animation that reverses halfway through with a static method that can be called by my view controllers. This works fine.
But I need it to perform a selector when the reverse takes place, essentially in the middle of the animation. The method setAnimationDidStopSelector only fires when the entire animation is completed, not in the middle.
So I split the animation into two blocks. However, when I do this, the first animation is instantly completed, then the second one happens. I suspect it's because the animation is on the same view. Perhaps there can't be two separate animation blocks for the same view?
I've set up the second block with a different animation name, and with the appropriate delay, and even tried setAnimationBeginsFromCurrentState (even though it's discouraged in OS 4), but none seem to solve the problem.
Here's some sample code to get an idea of one of the ways I've tried. 'selector' is the method I need called halfway through (once the alpha is transparent).
[UIView beginAnimations:name context:view];
[UIView setAnimationDuration:(duration/2)];
[UIView setAnimationDelegate:delegate];
[UIView setAnimationDidStopSelector:selector];
[view setAlpha:0.0];
[UIView commitAnimations];
name = [name stringByAppendingString:#"_reverse"];
[UIView beginAnimations:name context:view];
[UIView setAnimationDelay:(duration/2)];
[UIView setAnimationDuration:(duration/2)];
[UIView setAnimationDelegate:delegate];
[UIView setAnimationDidStopSelector:selector];
[view setAlpha:1.0];
[UIView commitAnimations];
Edit: I want to avoid animation 'blocks' as I'd like the code to be compatible with pre-OS4. I'd also like to keep the method static so I can use it as a library method.
Have you tried putting the second part of the animation into a method, and use a NSTimer or performSelector: withObject: afterDelay:?
I don't know if these work or not, but UIView animations and drawing stuff tend to have weird interactions when a lot of stuff is done concurrently. For example, you set a couple animations to go off one after another in a single method. Spacing them out so that the first animation has no knowledge of the upcoming one might help. I do realize that you do set a delay on the second one, but UIViews are weird in that way.
Edit: Just tried it out, works like a charm.
So I came from a Flash background where I can animate in timeline. I've completed the Beginning iPhone Development book and just realized that I still don't know how to get an animation in. I'm guessing I need to import png sequences?
Can anyone point me to an appropriate place to learn more about this topic? I want to make a game and my game objects need to animate.
Thanks in advance!!
The simplest type of animation, moving things around and fading in and out, can be done with a few static methods of UIVIew. You can affect the center, bounds, transform matrix and alpha level of one or more views.
[UIView beginAnimations:nil context:nil];
[fadingOutView setAlpha:0.0];
[slidingView setCenter:CGPointZero];
[shrinkingView setFrame:CGRectZero];
[fadingInView setAlpha:1.0];
[spinningView setTransform:CGAffineTransformMakeRotation( M_PI )];
[UIView commitAnimations];
Animations start with the current state of the view and interpolate to the state assigned between begin and commit animation. So if fadingInView already had an alpha of 1.0 (the default) there would be no change.
If you are unfamiliar with static methods [UIView method]; means call method on the class not an instance.
Using other UIView static methods you can control several details of the animation. Every UIView has a CALayer that also has a few properties that can be animated, the most interesting of which is the 3D transform property.
If the basic animation is not sufficient for you needs, you can either look into CAAnimation and related classes, or look to a third party animation library.
I think the best place to start learning is in your code, since you are just transitioning from Flash. Look at the very bottom of UIView.h to see the animation methods. Make a few views and move them around.
Take a look at cocos2d for iPhone. It has a good framework for handling sprites, animation (frame based and motion) as well as a lot of other stuff.
http://www.cocos2d-iphone.org
You can of course do all off this on your own with core graphics and core animation, but an API like cocos2d takes care of a lot of the nitty gritty details for you.
The Beginning iPhone Development book does not talk much about animation. You may read more about animation from Apple documentation, and play with some samples from Apple, such as the Touches.
i coded in button action like
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:1.0];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:mainDelegate.window cache:NO];
[mainDelegate.window addSubview:[self.calcentryController view]];
[UIView commitAnimations];
it works fine,but when i use in calcentryController.m in one action
[self presentModalViewController:self.weeklyScoreController animated:YES];
to go another viewcontroller , it is not working any help pls?
Is there any reason you aren't using presentModalViewController for calcentryController? You can set modalTransitionStyle on calcentryController to UIModalTransitionStyleFlipHorizontal and just use presentModalViewController:animated instead of doing manual animations.
The reason this may help is because your code is not calling certain functions like viewWillAppear:, viewDidAppear:, etc., whereas presentModalViewController:animated calls all the right functions for presenting new views.
It's possible that your weeklyScoreController is nil. I'm not sure where you create it, since the code block is not pasted but that's a common mistake I see.
I could be because your code is in viewDidLoad instead of viewDidAppear as per presentModalViewController does nothing
I had the same issue.
If you have a deep hierarchy of view controllers' views subviewed into one another, then always try to present with immediately viewcontroller whose view is directly added to the window of ur iphone app.
For e.g. I had 4 levels of view like
window > vc1's view > vc2's view > vc3's view
So when I tried to call [vc3 presentModalViewController] it was not working..
I had to present with vc1 and it worked. I had vc1 referenced as a property to app delegate and hence was able to access easily.
But again, I didn't still find the actual reason, but I can say this worked for me.