Search code examples
iosperformanceanimationuiviewcore-animation

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.

Screen shot from the simulator

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.

Instruments screen shot of a few flip operations

Any suggestions how to improve the performance of this? The exact timing of the animation is not really important.


Solution

  • 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.