I am trying to combine multiple UIImage as 1 gif animated image. I have a problem if the number of frames (UIImages) is more than 20. The app is closing because of memory leaks. When createGIF
starts to make the gif, the memory jumps from 200MB to 2GB and keeps increasing until the app crashes due to the memory leak.
Here is my code:
func createGIF(from images: [UIImage], loopCount: Int = 0, frameDuration: TimeInterval = 0.1, completion: @escaping (URL?) -> Void) {
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
print("Error: Unable to create documentsDirectory.")
completion(nil)
return
}
let gifURL = documentsDirectory.appendingPathComponent("\(UUID().uuidString).gif")
guard let destination = CGImageDestinationCreateWithURL(gifURL as CFURL, kUTTypeGIF, images.count, nil) else {
print("Error: Unable to create CGImageDestination.")
completion(nil)
return
}
let gifProperties = [
kCGImagePropertyGIFDictionary as String: [
kCGImagePropertyGIFLoopCount as String: loopCount
]
]
CGImageDestinationSetProperties(destination, gifProperties as CFDictionary)
// Process frames in batches to reduce memory usage
let batchSize = 10
let totalFrames = images.count
for startIndex in stride(from: 0, to: totalFrames, by: batchSize) {
let endIndex = min(startIndex + batchSize, totalFrames)
let batchImages = Array(images[startIndex..<endIndex])
for image in batchImages {
if let cgImage = image.cgImage {
let frameProperties = [
kCGImagePropertyGIFDictionary as String: [
kCGImagePropertyGIFDelayTime as String: frameDuration
]
]
CGImageDestinationAddImage(destination, cgImage, frameProperties as CFDictionary)
} else {
print("Error: Unable to retrieve CGImage from UIImage.")
completion(nil)
return
}
}
}
guard CGImageDestinationFinalize(destination) else {
print("Error: Failed to finalize CGImageDestination.")
completion(nil)
return
}
completion(gifURL)
}
I am trying to achieve a sticker animation gif from UIImage array. What did I forget here or what is the problem that makes the memory increase to over 2GB when I use more than 20 FPS.
Well, using DispatchGroup
and resizing the images and converting them to CGImage before generating the actual gif image fixed my problem , the memory max peek between 150 and 200mb on generating gif .
here is the final code could help someone :
public func generateGifFromImages(images: [UIImage], colorSpace: colorSpace, delayTime: Double, loopCount: Int,completion: @escaping (URL?) -> Void) {
// create empty file to hold gif
let destinationFilename = String(NSUUID().uuidString + ".gif")
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(destinationFilename)
let gifGroup = DispatchGroup()
var tempImages: [UIImage] = []
for image in images {
gifGroup.enter()
tempImages.append(image.resized(to: CGSize(width: 512,height: 512)))
gifGroup.leave()
}
gifGroup.notify(queue: .main) {
let gifFileProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: loopCount]] as CFDictionary
let gifFrameProperties: CFDictionary = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: delayTime]] as CFDictionary
if let url = fileURL as CFURL? {
if let destination = CGImageDestinationCreateWithURL(url, UTType.gif.identifier as CFString, images.count, nil) {
CGImageDestinationSetProperties(destination, gifFileProperties)
for image in tempImages {
if let cgImage = image.cgImage {
CGImageDestinationAddImage(destination, cgImage, gifFrameProperties)
}
}
if !CGImageDestinationFinalize(destination) {
print("Failed to finalize the image destination")
}else{
completion(fileURL)
}
}
}
}
}
this is the resize extension :
extension UIImage {
/**
Resizes the image.
- Parameters:
- scale: If this is 1, `newSize` is the size in pixels.
*/
@nonobjc public func resized(to newSize: CGSize, scale: CGFloat = 1) -> UIImage {
let format = UIGraphicsImageRendererFormat.default()
format.scale = scale
let renderer = UIGraphicsImageRenderer(size: newSize, format: format)
let image = renderer.image { _ in
draw(in: CGRect(origin: .zero, size: newSize))
}
return image
}
}