Poor performance when animating transform of large-ish UIImageView - ios

I'm building a title sequence for our game, where each title is a roughly half-screen-sized retina image which I'm displaying using a UIImageView.
The title sequence has simple 3 stages as it gradually grows and fades in/out:
// 1. Fade in and grow
[UIView animateWithDuration:1.0f animations:^{
titleImageView.alpha = 1.0f;
titleImageView.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
} completion:^(BOOL finished) {
// 2. Stay opaque, grow a little more
[UIView animateWithDuration:2.0f animations:^{
titleImageView.transform = CGAffineTransformMakeScale(1.1f, 1.1f);
} completion:^(BOOL finished) {
// 3. Fade out, grow even further
[UIView animateWithDuration:2.0f animations:^{
titleImageView.alpha = 0.0f;
titleImageView.transform = CGAffineTransformMakeScale(1.3f, 1.3f);
} completion:nil];
}];
}];
At the start of each animation stage, there's a stutter as a frame or two is dropped. It's especially noticeable on older hardware such as iPhone 4 and iPad 3, but it's even noticeable on an iPad Air, which is surprising.
Some deductions:
It's got nothing to do with the loading of the UIImage itself, because I've tried pre-loading the data and ensuring that the PNG has been decompressed. Also the stutter happens at every stage of the animation, even after it has been on screen for a while.
I used the Time Profiler instrument and noticed that CA seemed to be copying around PNG data in the background each time it stuttered, although I'm not sure why / what for. Surely the CALayers shouldn't need to re-create any image data on CPU each time the transform is changed?
EDIT: Also note that I tried it without animations (just setting those transform properties, also without alpha changes), and got the same results.
Also note that I have some OpenGL ES graphics going on in the background (it's a game with UIKit controls in the foreground), but that hasn't caused problems in the past...

So, it's slightly ugly, but I solved it with two approaches simultaneously:
I converted the cascading UIView animations into a single CALayer keyframed animation so that they're continuous rather than stopping at each stage, calling the completion handler, and starting a new animation. Perhaps this stops them from "settling" and re-rendering in their new scale, as Brian Nickel pointed out.
But this didn't prevent the very first stutter as it showed each new UIImage. To solve this, I created all UIImageViews (6 of them in total) up-front, added them to the superview, and set their alphas to zero. The images aren't huge, and there aren't too many of them, so this seemed okay.

Related

Does [UIView transitionWithView:...] happen off the main thread?

I've been working on optimizing scrolling performance (f.p.s.) of an image heavy news feed table view.
I noticed that when I added in the following, which I expected would slow things down, performance actually felt better, i.e. less stuttering.
[UIView transitionWithView:self
duration:0.3f
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{ self.image = image; }
completion:NULL];
I'm willing to believe that the cross dissolve effect gives the illusion of smoother scrolling, but I'd also buy it that the animation/drawing is being dispatched off of the main thread (is that even possible), freeing up the UI to focus on the other aspects of scrolling minus drawing the image on the canvas.
So what gives, am I imagining things?
It's difficult to profile Core Animation for scrolling since it's dependent on network (images are URL based), scroll speed, velocity etc. so this is a purely anecdotal observation. Any recommendation for doing some more consistent scrolling profiling would be welcomed.
UIView Doc for reference.

iOS game - animate tile by tile

I'm developing a tile based iOS game. Objects (UIViews) are drawn above the tile map and should walk around, tile by tile.
I'm using a simple UIView animation to do that:
[UIView animateWithDuration:0.20 delay:0 options:UIViewAnimationOptionCurveLinear animations:^(void) {
// set frame
// set rotation
} completion:^(BOOL finished){
// call method to start animation again
}];
The problem is that even when only animating about 5 objects it isn't smooth. After each animation there is a very small pause. Because it should be perceived as one animation (not tile by tile) it is noticable.
I need to do some checks for each move (1 move = 1 tile), so I can't just use one big animation. The checks are done independently and should update the data before the animation is finished. So I don't think the lag is because of that.
Are there any other ways on solving this? Maybe with Sprite Kit?
Even when I rewrote the animation logic so that the animation distance would be as long as possible it was still very jerky with a few objects.
It seems that animating multiple UIImageViews with transparency isn't a very good idea.
I'm now using Sprite Kit and everything is smooth and running at 60fps. It wasn't a big change, as Sprite Kit is quite simular.

IOS 3D Transform like Clear app

so what I want is to do the same thing Clear (http://www.realmacsoftware.com/clear/) does.
If I pan up or down it makes a new rectangle appear with a 3D visual effect.
I did manage to do this by using a translation and rotation transform along with changing the m34 property.
The problem is that since I move 2 views (like Clear moving the rectangle that is showing on screen, and the rectangle appearing with the 3D visual effect), when the user stop touching the screen I use a
[UIView animateWithDuration:0.5
delay:0
options: UIViewAnimationCurveEaseOut
animations:^{
...
}
completion:nil];
to restore the views to a consistent state, either showing the new rectangle, or not (sliding it down), I can see the black background between the 2 views, meaning that the 2 rectangles are not being redrawn at same time.
How do I solve that?
It's difficult to answer your specific question without more details, but you can look at https://github.com/mpospese/MPFoldTransition for inspiration.
Mark Pospesel developed this wrapper to perform these kinds of folding transforms, and describes the process behind them in detail in his "Anatomy of a folding animation" blog post.

How to move graphic in an arc path on iOS... as in a meter or clock face

This is the result of what I want to achieve:
(source: mouseaddict.com)
I have static images for my app, but i want to add motion to them.
Imagine a meter. It has a needle that is locked at the bottom (at 6:00 on a round-clock-style-face) and whose needle-top swivels from left to right in an arc. the arc (zero) starts with the needle pointed toward (roughly) the 9:30 position on a round-clock-style-face and the top-most part of that arc should end at roughly 2:30 on a round-clock-style-face.
I have several graphical elements 1) the round meter face (png) and 2) the verticaly oriented png file of a meter needle. I need to cause the needle to move within the round face in an arc'ing pattern.
So, assuming the two requirements above, what is the best way to swivel the needle within the meter using animation?
I have seen this: Speedometer -- but is this the best way? My main issue is that I want the needle locked at the bottom and move left-to-right only a small amount..as WELL as the fact that there is little (actually no) documentation on it. Things like "calculateDeviationAngle" are unexplained.
Also the Speedometer seems to move a large amount and the pivot point on that example would not work...(i dont think)
I would like to call this with a command like:
[self moveNeedleToPosition:degreeOrClockFaceNumber] and have it move from it's current position to a bit past the degreeOrClockFaceNumber and bounce back to rest at degreeOrClockFaceNumber.
TIA
I did the following: I broke the image up into several pieces. The outline (black) portion, the backgrounds and the needle. Each piece having the same exact "footprint" (shapes).
Assuming theImageView is the imageView for the needle (layered on top of the other two imageViews), I created the following function:
-(void) rotateNeedle:(UIImageView *)theImageView toAngle:(float) theAngle
{
[UIView animateWithDuration:0.5
delay: 0.0
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
theImageView.transform =CGAffineTransformMakeRotation((M_PI / 180) * (theAngle + 10));
}
completion:^(BOOL finished){
//
[UIView animateWithDuration:0.5
animations:^{
theImageView.transform = CGAffineTransformMakeRotation((M_PI / 180) * theAngle);
}];
}];
}
The first animation in the block performs the "shoot-past" portion of the animation...ie: moving 10 degrees past the actual value. The second animation in the completion block then "bounces" the needle back to the correct value. The speed of these animations create the physics effect. It works quite well for me.

How to keep iOS animation smooth with many subviews

I am trying out different looks of a little game I am writing to play with animation on iOS.
My goal is this to have a grid of tiles which based on gameplay changes the display of each tile to one of a set of images. I'd like each tile (up to 24x24) to flip around when its face changes. As the game progresses, more and more tiles need to be flipped at the same time. In the first implementation where I tried to flip them simultaneously the animation got very jerky.
I changed my approach to not flip them all at once, but just a few at a time, by scheduling the animation for each tile with a slightly increasing delay per tile, so that when say the 10th tile starts animating, the first one is already done. It takes little while longer for the whole process to finish, but also leads to a nice visual ripple-effect.
However, one problem remains: At the beginning of a game move, when the player picks a new color, it takes a few fractions of a second on the device, before the animation starts. This gets worse as the game progresses and more flips need to be scheduled per move, up to the point where the animation seems to hang and then completes almost instantly without any of the frames in between being actually discernible.
This is the code (in my UIView game grid subclass) that triggers the flipping of relevant tiles. (I removed an optimization that skips tiles, because it only matters in the early stages of the game).
float delay = 0.0f;
for (NSUInteger row=0; row<numRows; row++) {
for (NSUInteger col=0; col<numCols; col++) {
delay += 0.03f;
[self updateFlippingImageAtRow:row col:col delay:delay animated:YES];
}
}
The game grid view has an NSArray of tile subviews which are addressed using the row and col variables in the loop above.
updateFlippingImageAtRow:col:delay:animated is a method in my FlippingImageView (also a subclass of UIView) boils down to this (game logic omitted):
-(void)animateToShow:(UIImage*)image
duration:(NSTimeInterval)time
delay:(float)delay
completion:(void (^)(BOOL finished))completion
{
[UIView animateWithDuration:time
delay:delay
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft
forView:self
cache:YES];
self.frontImage = image;
}
completion:completion
];
}
Works fine, however, I conclude from the Instruments measuring which tells me that my time is spent in the animation block, that as the game goes on and the number of tiles to flip goes up, that the number of animations that get scheduled at the very beginning of the operation is a problem, and that the system then tries to catch up by dropping frames.
Any suggestions how to improve the performance of this? The exact timing of the animation is not really important.
You can think about doing this with CoreAnimation and CALayers instead of UIViews. It is incredebly powerful and optimized framework.
It's not an easy thing, you'll have to recode at least some of your classes (view hierarchy and hit tests are the first things that come to my mind), but it's worth a try and it's rather painless process, because CALayer is very similar to UIView.
Next step is OpenGL. It definitely can operate several hundreds of objects in realtime, but it requires much more work to do.
You might want to try using CoreAnimation instead. One way to do the flip animation would be:
Create a CABasicAnimation that animates the first half of the flip (rotation along the y axis).
In the delegate method animationDidStop:finished: you set the new image and then create a new animation that animates the second half.
You simply apply the animation to the layer property of your view.
Note that you can use setValue:forKey: to "annotate" an animation (remember what object the animation is about). When you add an animation to a layer it gets copied, not retained, so you can't identify it by simply comparing pointer values.

Resources