Create and export an animated gif via iOS?

隐身守侯 提交于 2019-11-26 03:46:52

问题


I have a series of user-customized images within an iOS app that are being animated in a simple, frame-by-frame flip book style.

My question is this: is there a way to allow users to export their animation as an animated gif? Ideally, I\'d like to enable them to email, social share (T/FB) or (worst case..) save an animated gif to their documents folder for retrieval via iTunes.

I know how to save a .png to the photo library, and I found a way to record an animation as a QT file (http://www.cimgf.com/2009/02/03/record-your-core-animation-animation/), but I haven\'t found a way to just kick out a plain old animated gif. Am I missing something in Core Animation or somewhere else? Are there any approaches, frameworks, or resources that anyone can recommend? Sorry if the question is too general - struggling to find a starting point.


回答1:


You can create an animated GIF using the Image I/O framework (which is part of the iOS SDK). You will also want to include the MobileCoreServices framework, which defines the GIF type constant. You need to add these frameworks to your target, and import their headers in the file where you want to create the animated GIF, like this:

#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/MobileCoreServices.h>

It's easiest to explain by example. I'll show you the code I used to make this GIF on my iPhone 5:

First, here's a helper function that takes a size and an angle and returns a UIImage of the red disk at that angle:

static UIImage *frameImage(CGSize size, CGFloat radians) {
    UIGraphicsBeginImageContextWithOptions(size, YES, 1); {
        [[UIColor whiteColor] setFill];
        UIRectFill(CGRectInfinite);
        CGContextRef gc = UIGraphicsGetCurrentContext();
        CGContextTranslateCTM(gc, size.width / 2, size.height / 2);
        CGContextRotateCTM(gc, radians);
        CGContextTranslateCTM(gc, size.width / 4, 0);
        [[UIColor redColor] setFill];
        CGFloat w = size.width / 10;
        CGContextFillEllipseInRect(gc, CGRectMake(-w / 2, -w / 2, w, w));
    }
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

Now we can create the GIF. First we'll define a constant for the number of frames, because we need it twice later:

static void makeAnimatedGif(void) {
    static NSUInteger const kFrameCount = 16;

We'll need a property dictionary to specify the number of times the animation should repeat:

    NSDictionary *fileProperties = @{
        (__bridge id)kCGImagePropertyGIFDictionary: @{
            (__bridge id)kCGImagePropertyGIFLoopCount: @0, // 0 means loop forever
        }
    };

And we'll need another property dictionary, which we'll attach to each frame, specifying how long that frame should be displayed:

    NSDictionary *frameProperties = @{
        (__bridge id)kCGImagePropertyGIFDictionary: @{
            (__bridge id)kCGImagePropertyGIFDelayTime: @0.02f, // a float (not double!) in seconds, rounded to centiseconds in the GIF data
        }
    };

We'll also create a URL for the GIF in our documents directory:

    NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
    NSURL *fileURL = [documentsDirectoryURL URLByAppendingPathComponent:@"animated.gif"];

Now we can create a CGImageDestination that writes a GIF to the specified URL:

    CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef)fileURL, kUTTypeGIF, kFrameCount, NULL);
    CGImageDestinationSetProperties(destination, (__bridge CFDictionaryRef)fileProperties);

I discovered that passing fileProperties as the last argument of CGImageDestinationCreateWithURL does not work. You have to use CGImageDestinationSetProperties.

Now we can create and write our frames:

    for (NSUInteger i = 0; i < kFrameCount; i++) {
        @autoreleasepool {
            UIImage *image = frameImage(CGSizeMake(300, 300), M_PI * 2 * i / kFrameCount);
            CGImageDestinationAddImage(destination, image.CGImage, (__bridge CFDictionaryRef)frameProperties);
        }
    }

Note that we pass the frame properties dictionary along with each frame image.

After we've added exactly the specified number of frames, we finalize the destination and release it:

    if (!CGImageDestinationFinalize(destination)) {
        NSLog(@"failed to finalize image destination");
    }
    CFRelease(destination);

    NSLog(@"url=%@", fileURL);
}

If you run this on the simulator, you can copy the URL from the debug console and paste it into your browser to see the image. If you run it on the device, you can use the Xcode Organizer window to download the app sandbox from the device and look at the image. Or you can use an app like iExplorer that lets you browse your device's filesystem directly. (This doesn't require jailbreaking.)

I tested this on my iPhone 5 running iOS 6.1, but I believe the code should work as far back as iOS 4.0.

I've put all the code in this gist for easy copying.




回答2:


For Swift 3

import Foundation
import UIKit
import ImageIO
import MobileCoreServices

extension UIImage {
    static func animatedGif(from images: [UIImage]) {
        let fileProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: 0]]  as CFDictionary
        let frameProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [(kCGImagePropertyGIFDelayTime as String): 1.0]] as CFDictionary

        let documentsDirectoryURL: URL? = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
        let fileURL: URL? = documentsDirectoryURL?.appendingPathComponent("animated.gif")

        if let url = fileURL as CFURL? {
            if let destination = CGImageDestinationCreateWithURL(url, kUTTypeGIF, images.count, nil) {
                CGImageDestinationSetProperties(destination, fileProperties)
                for image in images {
                    if let cgImage = image.cgImage {
                        CGImageDestinationAddImage(destination, cgImage, frameProperties)
                    }
                }
                if !CGImageDestinationFinalize(destination) {
                    print("Failed to finalize the image destination")
                }
                print("Url = \(fileURL)")
            }
        }
    }
}

I have converted it from the above answer. I hope it helps.

Available as a gist.

Edits are welcomed.




回答3:


If you're looking for Swift 3 solution, you can take a look at https://github.com/onmyway133/GifMagic. It has Encoder and Decoder which assembles and disassembles gif file.

Basically, you should use Image IO framework with these functions CGImageDestinationCreateWithURL, CGImageDestinationSetProperties, CGImageDestinationAddImage, CGImageDestinationFinalize

Also with Swift https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html

Core Foundation objects returned from annotated APIs are automatically memory managed in Swift—you do not need to invoke the CFRetain, CFRelease, or CFAutorelease functions yourself.



来源:https://stackoverflow.com/questions/14915138/create-and-export-an-animated-gif-via-ios

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!