This question has been asked at least as many times as I've searched for the answer but never to my satisfaction. I have a view with 52 subviews representing a deck of cards. The cards appear in the center of the screen, split into two stacks of cards and then merge together as a shuffled deck.
If I call self.standardShuffle() from viewDidAppear(), after instantiating the card images, it works just fine.
But if I call self.standardShuffle() 2 times in a row, the second call catches the first animation in flight and and the animation is ruined. There has to be a way to block the calling thread until the animation chain completes. Timers get too unwieldy and I can't put all of my code inside animation blocks (and it doesn't work, anyway.)
Anybody have the real answer? Please.
There has to be a way to block the calling thread
No there is not. Never block. Never.
If you think the answers you've seen are not satisfactory, you have not read them carefully. Read them and believe them. You can easily (and in many different ways) arrange to be called back after the first animation; that is the moment to begin the second animation. Or, create a single grouped animation that performs two animations in succession.
Those are your choices — your only choices. If you're finding that difficult to reconcile with your code, it's because you've architected your code incorrectly. Rearchitect it. Use the framework, don't fight it.
Related
All the animations mean change of layer.transform or frame properties. There are 2 main questions about performance:
I have multiple views which I should animate simultaneously. What is better: to create multiple animateWithDuration blocks or to create one such block and iterate views in it?
My animation requires complex calculations and I need to call animateWithDuration enough often. Should I perform all the calculations before the animation block or is there no difference for performance?
Nobody has answered yet. So let I answer my own question and you will correct it if I'm mistaken.
I recommend one block only. The usual animateWithDuration:animations: doesn't support simultaneous animations properly (at least in my case it caused some troubles). I suggest to use RZViewActions instead - it uses more complex approach with dispatch_group_t for simultaneous animations (and actions in general). In the last version you don't even need to prepare groups/sequences before the animation start.
The only difference I see is the code outside animateWithDuration:animations: is performed in the same NSRunLoop cycle and if I place it inside the animation block it will be calculated in the next cycle. So for example, it will be useful to place the calculations outside the block if sometimes you don't want to start the animation according to the results of these calculations.
On the other hand it may slow down the current cycle which is usually more critical than than the slower performance of the next cycle. Additionally you should think about variables you need to pass into the block correctly.
In both cases the animation will not start until the end of the animation block. So if you set some visual variable (for example, view's frame) it is set as usually and you can use it at once. But this value is not rendered until the beginning of the completion block. So you with animateWithDuration:animations: you will see the starting and the ending value only. Even if the animation is interrupted it jumps to the ending value at once (it is default behaviour if you don't specify additional options).
So in an iOS app I'm using a UINavigationController as the root view controller. I then push a few ViewControllers on the stack, let's call them A, B, C and D.
Now if I try to pop D from the stack, I sometimes * run into the following problem:
After calling popViewControllerAnimated, the view of the topmost view controller (D) remains. If I display the stack however, it is displayed correctly, i.e. A-B-C is displayed and D is gone. The pop method seems to do just what it's supposed to but the view does not reflect this.
I have tried various other approaches, (popToRootViewController, popToViewController) instead but the behaviour doesn't change (i.e. the stack is printed correctly but D's view remains). I have called setNeedsDisplay on all views that seem to make sense.
After popping D, I can still push and remove other view controllers. The stack reflects those changes, the view does not. Also, D's view remains responsive, i.e. it is not frozen and the app does behave correctly in the background. All views are very simple, there are no tabbed views or the like, just a couple of labels and buttons.
I have read a number of posts on SO and elsewhere about people having similar problems but none of the solutions seem to fit here. Does anyone have an idea why this happens?
*_ I haven't been able to figure out under which circumstances exactly. I first thought it was because I am popping D very soon after pushing it, but it also occasionally happens when there is more time between push and pop.
Only reason i can think of where this might happen is if you are not doing that in a main thread. If you are doing it after some network call, then do make sure you are going back to main thread. Sometimes, app won't crash if you do UI work on background thread but rather will behave like what you are seeing!
The problem seems to have been one of timing. As I mentioned, the push and pop operations (of D) sometimes do happen rather quickly after one another. Adding a delay of 2500 ms solved the problem. Adding 500 ms did not (always). Since neither is acceptable to me, I switched to a custom container view controller and therefore didn't spend a lot of time on figuring out what the minimum delay would be.
Just as sidenote: Changing from dispatch_async to dispatch_sync didn't have any effect. The delay seems to be necessary either way.
Thanks for everyone's help. I'm going to close this thread - if someone can provide a more complete answer as to why things are the way they are, please post it and I'll mark it as the correct answer then.
I have often noticed that UIView animations are often not smooth during the first becomeFirstResponder event when the keyboard appears for the first time. I am referring to animations that occur with the keyboard animation, such as manually scrolling the UIView to make a textField visible. The animation is always smooth after the first time it is executed.
Is there a technical reason why this would be the case? I was thinking that there might be some lazy loading or optimization that happens with UIView animations on the first run, then gets stored in cache for reuse. Are there lessons learned around this? If this is not clear to this audience, I can try to recreate the issue in a test project.
While this does not answer the question WHY this happens, it explains how to fix it.
Why are iOS animations slow the first time they are run?
Basically, you need to do animations on "DID" events rather than "WILL" or "SHOULD". The system performs it's animations during the "will/should" events, so apparently there is some colluding happening. This does not explain why the behavior is inconsistent between the first run and all other runs.
I thought, as may some of you, that I should put the animation in the "textFieldWillBeginEditing" because I wanted the animation to run concurrently with the keyboard animation. Luckily, putting the animation code in "DID" actually still ensures that the animation happens concurrently. Fantastic.
If anyone still has an explanation of the inconsistency between the first and latter runs, I'll still hold his question open and award you with an upvote and question answer. Thanks!
I've used these three classes several times by separate several times.
For example when I want to group several animations (i.e.: CABasicAnimations, etc) to happen simultaneously I first think of CAAnimationGroup, when I want to see a layer change from one state to another (i.e.: appear, slide in, etc) I think of CATransition.
But at the same time CATransaction is meant to execute animations in batches (WTH?)
CATransaction and CATransition seem to be different beasts but I usually see CATransitionsinside CATransactions? I've read the docs several times but they are never compared side by side and when/what they should be used for.
It would be great if someone could point what is their relationship/usage :)
CATransaction and CATransition are indeed different beasts...
It seems that the missing bit in your understanding is about CATransaction; once you get that, then maybe all the pieces will fall into place by themselves.
A CATransaction is always created every time you have a Core animation going on.
Every modification to a layer is part of a transaction. CATransaction is the Core Animation class responsible for batching multiple layer-tree modifications into atomic updates to the render tree.
(source)
What happens is that if you do not specify one explicitly, then an implicit CATransaction is created for you.
You can create an explicit transaction (by means of [CATransaction begin/commit]) to tune several parameters of an animation, like whether default animations should be used, how long they are etc. Those are all described in CATransaction reference.
Explicit transactions are particularly useful when setting the properties of many layers at the same time (for example, while laying out multiple layers), temporarily disabling layer actions, or temporarily changing the duration of resulting implied animations.
So, resuming it all, CATransaction is the "big umbrella" under which a core animation animation is run, wether it is a CABasicAnimation, a CATransition, or group animation. It allows you to set some general parameters affecting the way the animation/transition takes place and if you do not provide one, a default (implicit) one is used.
Hope this helps.
I have a custom view which has a piano keyboard drawn inside of it. Each key is drawn as a separate call, so that I can only draw keys that need redrawing. The view supports multiple touch, so multiple keys can be held down at once.
Each key is somewhat expensive to draw, so I pass a specific region to setNeedsDisplay whenever a touch is detected on the view in order to avoid re-drawing the entire view (which produces noticeable lag).
In order to handle multiple touches, I iterate over the set of received touches, check if each touch is within one of the keys, and if so, update it and call setNeedsDisplay with the rectangle of that key. In short, setNeedsDisplay is called multiple times in one function, but with a different rect each time.
The behavior that I expected was that drawRect would be invoked multiple times with different dirty regions, however, it seems that if I press the far left and far right keys at the same time, the entire view is redrawn, rather than just the far left and far right keys (as in, all the keys in between are redrawn unnecessarily).
What can I do to achieve what I want? I want to just draw the keys that are touched, not all the keys in between the two dirty keys.
The system will send you one drawRect: message per turn of the main run loop, regardless of how many times you called setNeedsDisplayInRect:. It passes you a rect that is at least the "union" of all of the dirty rects you passed to setNeedsDisplayInRect:. The system provides no way to find out exactly which rects were passed to setNeedsDisplayInRect:.
You could override setNeedsDisplayInRect: to keep an array of dirty rects (you will find +[NSValue valueWithCGRect:] useful), and clear out the array in drawRect:.
You could create your own setNeedsDisplayForKey: method that keeps an array of dirty keys and calls setNeedsDisplay.
I ran into this and posted my question here.
Curiously, I was also drawing a keyboard (with 88 keys). I never solved it and decided that I would deal with it if it turned out to be a performance problem (don't optimize until you need to etc.). One thing I did do was at start up, rendered that default keyboard into an image and use that as a base, so that I was only drawing the keys which were depressed and not the entire keyboard. Faster to draw an image than all that CGPath stuff.
I was displaying midi notes as they played and performance was fine - so maybe you don't need to be concerned # this right now.