import Flutter import UIKit import Photos public class SwiftImageGallerySaverPlugin: NSObject, FlutterPlugin { let errorMessage = "Failed to save, please check whether the permission is enabled" var result: FlutterResult?; public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "image_gallery_saver", binaryMessenger: registrar.messenger()) let instance = SwiftImageGallerySaverPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { self.result = result if call.method == "saveImageToGallery" { let arguments = call.arguments as? [String: Any] ?? [String: Any]() guard let imageData = (arguments["imageBytes"] as? FlutterStandardTypedData)?.data, let image = UIImage(data: imageData), let quality = arguments["quality"] as? Int, let _ = arguments["name"], let isReturnImagePath = arguments["isReturnImagePathOfIOS"] as? Bool else { return } let newImage = image.jpegData(compressionQuality: CGFloat(quality / 100))! saveImage(UIImage(data: newImage) ?? image, isReturnImagePath: isReturnImagePath) } else if (call.method == "saveFileToGallery") { guard let arguments = call.arguments as? [String: Any], let path = arguments["file"] as? String, let _ = arguments["name"], let isReturnFilePath = arguments["isReturnPathOfIOS"] as? Bool else { return } if (isImageFile(filename: path)) { saveImageAtFileUrl(path, isReturnImagePath: isReturnFilePath) } else { if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(path)) { saveVideo(path, isReturnImagePath: isReturnFilePath) }else{ self.saveResult(isSuccess:false,error:self.errorMessage) } } } else { result(FlutterMethodNotImplemented) } } func saveVideo(_ path: String, isReturnImagePath: Bool) { if !isReturnImagePath { UISaveVideoAtPathToSavedPhotosAlbum(path, self, #selector(didFinishSavingVideo(videoPath:error:contextInfo:)), nil) return } var videoIds: [String] = [] PHPhotoLibrary.shared().performChanges( { let req = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL.init(fileURLWithPath: path)) if let videoId = req?.placeholderForCreatedAsset?.localIdentifier { videoIds.append(videoId) } }, completionHandler: { [unowned self] (success, error) in DispatchQueue.main.async { if (success && videoIds.count > 0) { let assetResult = PHAsset.fetchAssets(withLocalIdentifiers: videoIds, options: nil) if (assetResult.count > 0) { let videoAsset = assetResult[0] PHImageManager().requestAVAsset(forVideo: videoAsset, options: nil) { (avurlAsset, audioMix, info) in if let urlStr = (avurlAsset as? AVURLAsset)?.url.absoluteString { self.saveResult(isSuccess: true, filePath: urlStr) } } } } else { self.saveResult(isSuccess: false, error: self.errorMessage) } } }) } func saveImage(_ image: UIImage, isReturnImagePath: Bool) { if !isReturnImagePath { UIImageWriteToSavedPhotosAlbum(image, self, #selector(didFinishSavingImage(image:error:contextInfo:)), nil) return } var imageIds: [String] = [] PHPhotoLibrary.shared().performChanges( { let req = PHAssetChangeRequest.creationRequestForAsset(from: image) if let imageId = req.placeholderForCreatedAsset?.localIdentifier { imageIds.append(imageId) } }, completionHandler: { [unowned self] (success, error) in DispatchQueue.main.async { if (success && imageIds.count > 0) { let assetResult = PHAsset.fetchAssets(withLocalIdentifiers: imageIds, options: nil) if (assetResult.count > 0) { let imageAsset = assetResult[0] let options = PHContentEditingInputRequestOptions() options.canHandleAdjustmentData = { (adjustmeta) -> Bool in true } imageAsset.requestContentEditingInput(with: options) { [unowned self] (contentEditingInput, info) in if let urlStr = contentEditingInput?.fullSizeImageURL?.absoluteString { self.saveResult(isSuccess: true, filePath: urlStr) } } } } else { self.saveResult(isSuccess: false, error: self.errorMessage) } } }) } func saveImageAtFileUrl(_ url: String, isReturnImagePath: Bool) { if !isReturnImagePath { if let image = UIImage(contentsOfFile: url) { UIImageWriteToSavedPhotosAlbum(image, self, #selector(didFinishSavingImage(image:error:contextInfo:)), nil) } return } var imageIds: [String] = [] PHPhotoLibrary.shared().performChanges( { let req = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: URL(string: url)!) if let imageId = req?.placeholderForCreatedAsset?.localIdentifier { imageIds.append(imageId) } }, completionHandler: { [unowned self] (success, error) in DispatchQueue.main.async { if (success && imageIds.count > 0) { let assetResult = PHAsset.fetchAssets(withLocalIdentifiers: imageIds, options: nil) if (assetResult.count > 0) { let imageAsset = assetResult[0] let options = PHContentEditingInputRequestOptions() options.canHandleAdjustmentData = { (adjustmeta) -> Bool in true } imageAsset.requestContentEditingInput(with: options) { [unowned self] (contentEditingInput, info) in if let urlStr = contentEditingInput?.fullSizeImageURL?.absoluteString { self.saveResult(isSuccess: true, filePath: urlStr) } } } } else { self.saveResult(isSuccess: false, error: self.errorMessage) } } }) } /// finish saving,if has error,parameters error will not nill @objc func didFinishSavingImage(image: UIImage, error: NSError?, contextInfo: UnsafeMutableRawPointer?) { saveResult(isSuccess: error == nil, error: error?.description) } @objc func didFinishSavingVideo(videoPath: String, error: NSError?, contextInfo: UnsafeMutableRawPointer?) { saveResult(isSuccess: error == nil, error: error?.description) } func saveResult(isSuccess: Bool, error: String? = nil, filePath: String? = nil) { var saveResult = SaveResultModel() saveResult.isSuccess = error == nil saveResult.errorMessage = error?.description saveResult.filePath = filePath result?(saveResult.toDic()) } func isImageFile(filename: String) -> Bool { return filename.hasSuffix(".jpg") || filename.hasSuffix(".png") || filename.hasSuffix(".jpeg") || filename.hasSuffix(".JPEG") || filename.hasSuffix(".JPG") || filename.hasSuffix(".PNG") || filename.hasSuffix(".gif") || filename.hasSuffix(".GIF") || filename.hasSuffix(".heic") || filename.hasSuffix(".HEIC") } } public struct SaveResultModel: Encodable { var isSuccess: Bool! var filePath: String? var errorMessage: String? func toDic() -> [String:Any]? { let encoder = JSONEncoder() guard let data = try? encoder.encode(self) else { return nil } if (!JSONSerialization.isValidJSONObject(data)) { return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] } return nil } }