如何将生成的GIF保存至相册?仅首次保存成功问题排查
解决GIF首次保存相册成功、后续失败(PHPhotosErrorDomain error -1)的问题
问题根源
- 异步时序错误:
generateGifFromImages方法是异步执行GIF生成逻辑,但按钮点击代码中调用完该方法后立刻触发相册保存操作,此时GIF文件可能还未完全写入磁盘,导致相册读取失败。 - 参数未生效:
generateGifFromImages方法未使用传入的colorSpace参数,始终生成灰度图,不符合预期。 - 强制解包风险:原代码中使用
try!强制解包,可能导致未知崩溃。
解决方案
1. 修改GIF生成方法,添加完成回调
调整GIFFromImages的generateGifFromImages方法,添加completion闭包通知生成完成,同时修复colorSpace参数未使用的问题:
import UIKit import MobileCoreServices public class GIFFromImages { public enum colorSpace { case rgb case gray } public init () {} } extension GIFFromImages { public func makeFileURL(filename: String) -> URL { let gifDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let gifFileURL = gifDirectory.appendingPathComponent(filename) return gifFileURL } // 添加completion回调,通知GIF生成状态 public func generateGifFromImages(images: [UIImage], fileURL: URL, colorSpace: colorSpace, delayTime: Double, loopCount: Int, completion: @escaping (Bool) -> Void) { let gifGroup = DispatchGroup() var tempImages: [UIImage] = [] for image in images { gifGroup.enter() let imageWidth = UIScreen.main.bounds.width let imageHeight = UIScreen.main.bounds.height let imageRect = CGRect(x:0, y:0, width: imageWidth, height: imageHeight) let imageBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) // 根据传入参数选择颜色空间 let cgColorSpace: CGColorSpace switch colorSpace { case .rgb: cgColorSpace = CGColorSpaceCreateDeviceRGB() case .gray: cgColorSpace = CGColorSpaceCreateDeviceGray() } let context = CGContext(data: nil, width: Int(imageWidth), height: Int(imageHeight), bitsPerComponent: 8, bytesPerRow: 0, space: cgColorSpace, bitmapInfo: imageBitmapInfo.rawValue) if let cgImg = image.cgImage { context?.setFillColor(UIColor.white.cgColor) context?.fill(imageRect) context?.draw(cgImg, in: imageRect) if let makeImg = context?.makeImage() { tempImages.append(UIImage(cgImage: makeImg)) } } 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 var isSuccess = false if let url = fileURL as CFURL?, let destination = CGImageDestinationCreateWithURL(url, kUTTypeGIF, tempImages.count, nil) { CGImageDestinationSetProperties(destination, gifFileProperties) for image in tempImages { if let cgImage = image.cgImage { CGImageDestinationAddImage(destination, cgImage, gifFrameProperties) } } isSuccess = CGImageDestinationFinalize(destination) if !isSuccess { print("Failed to finalize the image destination") } } completion(isSuccess) } } }
2. 调整按钮点击逻辑,确保生成完成后再保存相册
将相册保存逻辑放到GIF生成完成的回调中,同时修复强制解包问题:
Button { print("Saving animation titled: \(animation.title)") var images: [UIImage] = [] for frame in animation.frames { do { let image = try PKDrawing(data: frame.frameData).generateThumbnail(scale: 1) images.append(image) } catch { print("Failed to generate thumbnail: \(error.localizedDescription)") return } } let timestamp = Date().timeIntervalSince1970 let timestampString = String(format: "%.0f", timestamp) let gifURL = gifManager.makeFileURL(filename: "\(timestampString).gif") // 等待GIF生成完成后再执行保存 gifManager.generateGifFromImages(images: images, fileURL: gifURL, colorSpace: .rgb, delayTime: (1.0/Double(animation.framesPerSecond)), loopCount: 0) { [weak self] success in guard success else { print("Failed to generate GIF file") return } guard FileManager.default.fileExists(atPath: gifURL.path) else { print("Error: GIF file does not exist at:", gifURL.path) return } print("GIF file exists at:", gifURL.path) // 保存到相册 PHPhotoLibrary.shared().performChanges({ // 优先使用Data方式创建资源,避免URL读取时机问题 if let gifData = try? Data(contentsOf: gifURL) { let creationRequest = PHAssetCreationRequest.forAsset() creationRequest.addResource(with: .photo, data: gifData, options: nil) } else { PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: gifURL) } }) { success, error in if success { print("GIF saved successfully.") } else { print("Error saving GIF:", error?.localizedDescription ?? "Unknown error") } self?.close() } } } label: { ZStack { Rectangle() .frame(width: 400, height: 100) .foregroundColor(.accentColor) .cornerRadius(30) Text("Save GIF to Photos") .foregroundStyle(.white) .font(.title) .bold() } }
关键说明
- 通过
completion回调确保GIF完全写入磁盘后再执行保存,解决异步时序问题。 - 修复
colorSpace参数,生成符合预期的颜色空间GIF。 - 替换
try!为do-catch,避免崩溃风险。 - 提供两种相册保存方式,优先使用Data方式避免URL读取权限或时机问题。
内容的提问来源于stack exchange,提问作者Zoe Sosa
相关产品推荐
相关产品推荐

