Charlie Charlie - 4 months ago 49
Objective-C Question

Set contents of CALayer to animated GIF?

Is it possible to set the contents of a

CALayer
to an animated GIF and have it display that animation? I know that I can set the contents to an image like so:

CALayer* subLayer = [CALayer layer];
NSImage *image = [[NSImage alloc] initWithData:data];
subLayer.contents = image;


And the image will show, but if it's animated, the animation will not display. Is the only solution to get the individual frames for the GIF, get the frame rate, then change the
content
of the sublayer according to the frame rate? Or is there a much simpler method that I'm overlooking?

Answer

Well, okay, I guess this isn't that hard to do. Basically I check to see if the image is a GIF:

if ([format isEqualToString:@"gif"]){
    CAKeyframeAnimation *animation = [self createGIFAnimation:data];
    [subLayer addAnimation:animation forKey:@"contents"];
}

And as you can see, I'm calling my custom createGIFAnimation method, which looks like this:

- (CAKeyframeAnimation *)createGIFAnimation:(NSData *)data{
    NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithData:data];
    CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)(data), nil);
    NSNumber *frameCount = [rep valueForProperty:@"NSImageFrameCount"];

    // Total loop time
    float time = 0;

    // Arrays
    NSMutableArray *framesArray = [NSMutableArray array];
    NSMutableArray *tempTimesArray = [NSMutableArray array];

    // Loop
    for (int i = 0; i < frameCount.intValue; i++){

        // Frame default duration
        float frameDuration = 0.1f;

        // Frame duration
        CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(src,i,nil);
        NSDictionary *frameProperties = (__bridge NSDictionary*)cfFrameProperties;
        NSDictionary *gifProperties = frameProperties[(NSString*)kCGImagePropertyGIFDictionary];

        // Use kCGImagePropertyGIFUnclampedDelayTime or kCGImagePropertyGIFDelayTime
        NSNumber *delayTimeUnclampedProp = gifProperties[(NSString*)kCGImagePropertyGIFUnclampedDelayTime];
        if(delayTimeUnclampedProp) {
            frameDuration = [delayTimeUnclampedProp floatValue];
        } else {
            NSNumber *delayTimeProp = gifProperties[(NSString*)kCGImagePropertyGIFDelayTime];
            if(delayTimeProp) {
                frameDuration = [delayTimeProp floatValue];
            }
        }

        // Make sure its not too small
        if (frameDuration < 0.011f){
            frameDuration = 0.100f;
        }

        [tempTimesArray addObject:[NSNumber numberWithFloat:frameDuration]];

        // Release
        CFRelease(cfFrameProperties);

        // Add frame to array of frames
        CGImageRef frame = CGImageSourceCreateImageAtIndex(src, i, nil);
        [framesArray addObject:(__bridge id)(frame)];

        // Compile total loop time
        time = time + frameDuration;
    }

    NSMutableArray *timesArray = [NSMutableArray array];
    float base = 0;
    for (NSNumber* duration in tempTimesArray){
        //duration = [NSNumber numberWithFloat:(duration.floatValue/time) + base];
        base = base + (duration.floatValue/time);
        [timesArray addObject:[NSNumber numberWithFloat:base]];
    }

    // Create animation
    CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];

    animation.duration = time;
    animation.repeatCount = HUGE_VALF;
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    animation.values = framesArray;
    animation.keyTimes = timesArray;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    animation.calculationMode = kCAAnimationDiscrete;

    return animation;
}

Pretty straightforward, although I would love some tips on how to improve it.