How to keep iOS animation smooth with many subviews - ios

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.

Related

Poor performance when animating transform of large-ish UIImageView

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.

What's the best most CPU efficient way to draw views with a lot of animations in iOS?

I'm trying to draw a graphic equaliser for an iOS project.
The equaliser will have 7 bars, representing different frequency bands, than move up and down based on real-time audio data.
Can anyone suggest the best way to approach this in iOS?
New frequency data comes in at about 11Hz, and so the bars would have to animate to a new size 11 times per second.
Do I create a UIView for each bar and dynamically resize it's frame height?
Do I draw the bars as thick CGStrokes and redraw them within the parent view as needed?
Another option?
Thanks in advance
You want to use Core Animation. The basic principle is to create a bunch of "layer" objects, which can either be bitmap images, vector shapes, or text. Each layer is stored on the GPU and most operations can be animated at 60 frames per second.
Think of layers like a DOM node in a HTML page, they can be nested inside each other and you can apply attributes to each one similar to CSS. The list of attributes available matches everything the GPU can do efficiently.
It sounds like you want vector shapes. Basically you create all your shapes at startup, for example in the awakeFromNib method of a UIView subclass. For simple rectangles use CALayer and set a background colour. For more complicated shapes create a CAShapeLayer and a UIBezierPath, then apply it with shapeLayer.path = bezierPath.CGPath;.
Then, whenever you want to change something, you apply those changes to the layer object. For example, here I'm rotating a layer with a 1 second linear animation:
[CATransaction begin];
[CATransaction setAnimationDuration:1];
[CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
[self.needleLayer setValue:[NSNumber numberWithFloat:DegreesToRadians(degrees) forKeyPath:#"transform.rotation.z"];
[CATransaction commit];
// you'll want to declare this somewhere
CGFloat DegreesToRadians(CGFloat degrees)
{
return degrees * M_PI / 180;
}
More complicated animations, eg a series of changes scheduled to execute back to back, can be done using a CAKeyframeAnimation: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/CreatingBasicAnimations/CreatingBasicAnimations.html
Note Core Animation only does 2D graphics. Apple has Scene Kit which is basically the same thing for 3D, but so far it's only available on OS X. Hopefully iOS 8 will include it, but until then if you want 3D graphics on iOS you need to use Open GL.
CALayers which you resize on demand would probably be the most efficient way to do this, if the bars are solid colours. This allows you to optionally animate between sizes as well.
View resizing triggers off layout cycles, which you don't want. Drawing using CG calls is pretty slow.
Obviously the only real way to find out is to profile (on a device) using instruments and the core animation tool. But from experience, layer sizing is faster than drawing.
Definitely not a UIView for each - instead, a single UIView for the entire equalizer. Fill in the drawRect method with the appropriate CG calls to draw whatever is required. You can queue the view to refresh as needed with the appropriate data. Tapping into CADisplayLink will help you get the frame-rate you're looking for.
https://developer.apple.com/library/ios/documentation/QuartzCore/Reference/CADisplayLink_ClassRef/Reference/Reference.html
NOTE: You can also subclass CALayer and draw in it if you prefer something lighter-weight than UIView but I think you'll be fine with the former.

alternative to UIView setCenter

My app, which is a game, includes a CADisplayLink timer which calls a function that instructs about 20 calls to UIView setCenter: for the various objects on screen every frame.
Time profiling it, this accounts for about 30% of all activity in the game and drastically reduces performance on older devices (anything lower than 5th generation ipod touch or iphone).
Are there any lightweight, low-overhead alternatives I can use to move objects (specifically UIViews) around the screen every frame?
EDIT:
Just to clarify, the center property of these UIViews must be set EVERY FRAME. I have a number of tiles that represent the ground in my game. They zip across the screen, only to be replaced by new tiles. After fiddling with the code for a couple hours to change the UIViews to CAlayers, I have it working at absolutely no performance gain. There surely is a better way to do this.
Some code to give a general idea of what is going on:
for(Object* o in gameController.entities){
[o step:curTimeMS];
}
gameController is, as one would think, a class that takes care of the main game functions. It includes its list of entities, which are all the objects on-screen. The step method on each of these entities is a virtual function, so it is specific to each entity - the curTimeMS variable is simply a timestamp so the object can calculate its delta position. In essence, each entity updates its layer.position property every frame, moving it at an appropriate speed across the screen.
I would recommend SpriteKit. It is a very powerful game / 2d animation framework created by apple.. Cocos2D is also a very powerful framework of similar type. You can create a new SpriteKit game straight from XC
If you want to stay in house with just UIKit stuff, check out UIView block based animations. Here is the jist of it.
[UIView animateWithDuration:numberOfSecondsTakenToAnimate animations: ^{
// do you animation here. i.e.: move view frame, adjust color.
} completions: ^(BOOL complete) {
// when the animation is complete, code in this block is executed.
}];
I just remembered Core Graphics. It is used in tandem with UIViews to create simple 2d graphics and is very powerful and very fast. Here is the jist of that.
CGContextRef cntxt = UIGraphicsGetCurrentContext();
CGContextBeginPath(cntxt);
CGContextMoveToPoint(cntxt, <x>, <y>);
CGContextAddLineToPoint(cntxt, <x>, <y>);
CGContextClosePath(cntxt);
[[UIColor <color>] setFill];
[[UIColor <color>] setStroke];
CGContextDrawPath(cntxt, kCGPathFillStroke);
Note: things in < > are variables / values specified by you.
If you want to go all out, take the time to learn Open GL. Beware, I have heard that this is extremely hard to learn.
If you need performance, do not use UIView. It is not designed to be fast.
Instead, have a single UIView that takes up the whole screen, with a bunch of CALayer objects inside the one view. Move the layers around.
CALayer works by talking direct to the GPU, so it's very fast. Perhaps even faster than OpenGL. UIView is using CALayer internally so they both behave approximately the same. The only real difference is any change to the position of a CALayer will be animated by default. You can easily turn the animation off, although in a game you probably want animation.
Your other option, of course, is to use OpenGL. But that's a lot more work.
EDIT: here is some sample code for changing the position of a layer properly: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/CreatingBasicAnimations/CreatingBasicAnimations.html#//apple_ref/doc/uid/TP40004514-CH3-SW8

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.

Synchronize UIView layout to OpenGL frames

We are developing a game that has 2d elements displayed with UIViews over an OpenGL ES view (specifically, we're using GLKit's GLKView) and are having problems keeping the positions perfectly in sync.
In the parent view's layoutSubviews, we're projecting 3d positions in the world onto the screen, and using those as locations for several UIView "markers" in the game. The whole game only updates in response to the user moving the camera, and the camera tells the view setNeedsLayout each time it moves.
Everthing's working fine, except that the markers seem to be roughly 1 frame out of sync with the 3d rendering. I say roughly because (1) it's an estimate! and (2) I'm wondering whether there's potentially a multithreading issue: doesn't GLKView sync to a special screen refresh callback or something?
Is there some way of hooking a view's layoutSubviews so that it sync's to the 3d view update?
Update: Weirdly, calling layoutIfNeeded immediately after setNeedsLayout makes the problem worse! Possibly 2 or more frames out. Really don't understand that!
What's triggering your call to LayoutSubviews?
It all depends where in the RunLoop your call is triggered vs. where your GLK update call is triggered.
In general, for what you're doing, I'd aim to do your layout as a side-effect of the GLK update - i.e. don't wait for layoutSubviews to change your position.
(if you're using OpenGL, then the whole "layout" system isn't much use to you: GLK is running in its own little world of variable frame rate, and you want to make that your reference point)
This is impossible to do correctly without drawing the frames of the video using OpenGL (drawing in the same context so that you are always sure that one frame contains the same time of video and animation). Everyting else you do, framerate compensation, lag prediction, only depends on chance, and will always be a little bit unsynchronized.
I'm not familiar with UIView but if there is any way to let it play audio and copy the frames to a texture, do that. The lag in the audio is much easier to compensate and much less noticeable by the humans than in the video.

Resources