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:
- (CAKeyframeAnimation *)createGIFAnimation:(NSData *)data{
CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)(data), nil);
int frameCount =(int) CGImageSourceGetCount(src);
// Total loop time
float time = 0;
// Arrays
NSMutableArray *framesArray = [NSMutableArray array];
NSMutableArray *tempTimesArray = [NSMutableArray array];
// Loop
for (int i = 0; i < frameCount; 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;
}
according to mahal its should be done
Swift 3 version, but changed to receive URL (for my own purpose).
func createGIFAnimation(url:URL) -> CAKeyframeAnimation?{
guard let src = CGImageSourceCreateWithURL(url as CFURL, nil) else { return nil }
let frameCount = CGImageSourceGetCount(src)
// Total loop time
var time : Float = 0
// Arrays
var framesArray = [AnyObject]()
var tempTimesArray = [NSNumber]()
// Loop
for i in 0..<frameCount {
// Frame default duration
var frameDuration : Float = 0.1;
let cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(src, i, nil)
guard let framePrpoerties = cfFrameProperties as? [String:AnyObject] else {return nil}
guard let gifProperties = framePrpoerties[kCGImagePropertyGIFDictionary as String] as? [String:AnyObject]
else { return nil }
// Use kCGImagePropertyGIFUnclampedDelayTime or kCGImagePropertyGIFDelayTime
if let delayTimeUnclampedProp = gifProperties[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber {
frameDuration = delayTimeUnclampedProp.floatValue
}
else{
if let delayTimeProp = gifProperties[kCGImagePropertyGIFDelayTime as String] as? NSNumber {
frameDuration = delayTimeProp.floatValue
}
}
// Make sure its not too small
if frameDuration < 0.011 {
frameDuration = 0.100;
}
// Add frame to array of frames
if let frame = CGImageSourceCreateImageAtIndex(src, i, nil) {
tempTimesArray.append(NSNumber(value: frameDuration))
framesArray.append(frame)
}
// Compile total loop time
time = time + frameDuration
}
var timesArray = [NSNumber]()
var base : Float = 0
for duration in tempTimesArray {
timesArray.append(NSNumber(value: base))
base.add( duration.floatValue / time )
}
// From documentation of 'CAKeyframeAnimation':
// the first value in the array must be 0.0 and the last value must be 1.0.
// The array should have one more entry than appears in the values array.
// For example, if there are two values, there should be three key times.
timesArray.append(NSNumber(value: 1.0))
// Create animation
let animation = CAKeyframeAnimation(keyPath: "contents")
animation.beginTime = AVCoreAnimationBeginTimeAtZero
animation.duration = CFTimeInterval(time)
animation.repeatCount = Float.greatestFiniteMagnitude;
animation.isRemovedOnCompletion = false
animation.fillMode = kCAFillModeForwards
animation.values = framesArray
animation.keyTimes = timesArray
//animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.calculationMode = kCAAnimationDiscrete
return animation;
}
Important thing is written in the documentation of 'CAKeyframeAnimation':
the first value in the array must be 0.0 and the last value must be 1.0. The array should have one more entry than appears in the values array. For example, if there are two values, there should be three key times.
So added this line:
timesArray.append(NSNumber(value: 1.0))
And also checked in exporting as video with AVAssetExportSession.
Please call like this:
if let url = Bundle.main.url(forResource: "animation", withExtension: "gif"){
let animation = createGIFAnimation(url: url)
}
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.