import SwiftUI import PhotosUI /// 上传结果,包含原图和压缩图的上传信息 struct UploadResults { let original: ImageUploaderGetID.UploadResult let compressed: ImageUploaderGetID.UploadResult } /// 照片选择器,封装了系统相册选择功能 /// 使用UIViewControllerRepresentable包装PHPickerViewController,提供SwiftUI兼容的图片选择界面 struct PhotoPicker: UIViewControllerRepresentable { // MARK: - Properties /// 绑定的已选图片数组,用于存储用户选择的图片 @Binding var selectedImages: [UIImage] /// 最多可选图片数量,默认为1 let selectionLimit: Int /// 图片过滤器,默认为图片类型,可过滤特定类型的媒体 let filter: PHPickerFilter /// 图片上传完成回调,返回上传结果或错误 var onImageUploaded: ((Result) -> Void)? // MARK: - Initialization /// 初始化照片选择器 /// - Parameters: /// - selectedImages: 绑定的图片数组,用于接收用户选择的图片 /// - selectionLimit: 最多可选图片数量,默认为1 /// - filter: 媒体类型过滤器,默认为图片 /// - onImageUploaded: 图片上传完成后的回调闭包 init( selectedImages: Binding<[UIImage]>, selectionLimit: Int = 1, filter: PHPickerFilter = .images, onImageUploaded: ((Result) -> Void)? = nil ) { self._selectedImages = selectedImages self.selectionLimit = selectionLimit self.filter = filter self.onImageUploaded = onImageUploaded } // MARK: - UIViewControllerRepresentable /// 创建并返回配置好的PHPickerViewController func makeUIViewController(context: Context) -> PHPickerViewController { var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) configuration.filter = filter configuration.selectionLimit = selectionLimit configuration.preferredAssetRepresentationMode = .current let picker = PHPickerViewController(configuration: configuration) picker.delegate = context.coordinator return picker } /// 更新视图控制器(空实现,因为不需要更新) func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {} /// 创建协调器,用于处理PHPickerViewController的代理方法 func makeCoordinator() -> Coordinator { Coordinator(self) } // MARK: - Coordinator /// 协调器类,处理PHPickerViewController的代理方法 class Coordinator: NSObject, PHPickerViewControllerDelegate { /// 对父视图的弱引用 let parent: PhotoPicker /// 图片上传器实例 private let uploader = ImageUploaderGetID() init(_ parent: PhotoPicker) { self.parent = parent } /// 当用户完成图片选择时调用 func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { // 清空已选图片 parent.selectedImages.removeAll() // 使用DispatchGroup管理多个异步任务 let group = DispatchGroup() var loadedImages: [Int: UIImage] = [:] // 用于保持图片顺序的字典 var uploadResults: [Int: (original: ImageUploaderGetID.UploadResult?, compressed: ImageUploaderGetID.UploadResult?)] = [:] // 遍历所有选中的图片 for (index, result) in results.enumerated() { group.enter() // 进入组 if result.itemProvider.canLoadObject(ofClass: UIImage.self) { result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in guard let self = self, let image = image as? UIImage else { group.leave() return } // 1. 保存原始图片 loadedImages[index] = image // 2. 压缩图片(质量压缩到50%) guard let compressedImage = image.jpegData(compressionQuality: 0.5).flatMap(UIImage.init(data:)) else { group.leave() return } // 3. 上传原图 self.uploader.uploadImage(image) { [weak self] originalResult in guard let self = self else { group.leave() return } switch originalResult { case .success(let originalUploadResult): // 4. 原图上传成功后上传压缩图 self.uploader.uploadImage(compressedImage) { compressedResult in defer { group.leave() } switch compressedResult { case .success(let compressedUploadResult): // 保存两个上传结果 uploadResults[index] = (originalUploadResult, compressedUploadResult) print("✅ 原图和压缩图上传成功!") print("📂 原图信息:") print(" - 文件ID: \(originalUploadResult.fileId)") print(" - 文件大小: \(originalUploadResult.fileSize) 字节") print("📦 压缩图信息:") print(" - 文件ID: \(compressedUploadResult.fileId)") print(" - 文件大小: \(compressedUploadResult.fileSize) 字节") // 使用MaterialService上传文件信息到后端 MaterialService.shared.uploadMaterialInfo( fileId: originalUploadResult.fileId, previewFileId: compressedUploadResult.fileId ) { success, errorMessage in if success { print("✅ 文件信息上传成功 素材上传成功!!!!!") } else if let errorMessage = errorMessage { print("❌ 文件信息上传失败: \(errorMessage)") } } case .failure(let error): print("❌ 压缩图上传失败: \(error.localizedDescription)") uploadResults[index] = (originalUploadResult, nil) } } case .failure(let error): print("❌ 原图上传失败: \(error.localizedDescription)") group.leave() } } } } else { group.leave() } } // 所有上传任务完成后的处理 group.notify(queue: .main) { [weak self] in guard let self = self else { return } // 1. 更新选中的图片(只显示原图) let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value } self.parent.selectedImages.append(contentsOf: sortedImages) // 2. 检查是否所有上传都成功 if let firstResult = uploadResults.first?.value, let original = firstResult.original, let compressed = firstResult.compressed { // 3. 如果成功,返回上传结果 let results = UploadResults(original: original, compressed: compressed) self.parent.onImageUploaded?(.success(results)) } else { // 4. 如果失败,返回错误 self.parent.onImageUploaded?(.failure(NSError(domain: "com.wake.upload", code: -1, userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"]))) } // 5. 关闭图片选择器 picker.dismiss(animated: true) } } } } // MARK: - AvatarUploader /// 头像上传视图,提供头像选择功能 /// 封装了头像显示和选择逻辑,支持点击选择新头像 struct AvatarUploader: View { // MARK: - Properties /// 绑定的当前选中头像图片 @Binding var selectedImage: UIImage? /// 头像显示尺寸 let size: CGFloat /// 上传完成回调,返回上传结果或错误 var onUploadComplete: ((Result) -> Void)? // MARK: - State /// 控制图片选择器的显示状态 @State private var isImagePickerPresented = false // MARK: - Body var body: some View { // 头像按钮,点击后显示图片选择器 Button(action: { isImagePickerPresented = true }) { ZStack { if let selectedImage = selectedImage { // 显示已选中的头像 Image(uiImage: selectedImage) .resizable() .scaledToFill() .frame(width: size, height: size) .clipShape(RoundedRectangle(cornerRadius: size * 0.1)) } else { // 默认头像占位视图 Color.gray.opacity(0.1) .frame(width: size, height: size) .overlay( SVGImage(svgName: "Avatar") .frame(width: size * 0.8, height: size * 0.8) ) .clipShape(RoundedRectangle(cornerRadius: size * 0.1)) .overlay( RoundedRectangle(cornerRadius: size * 0.1) .stroke(Color.gray.opacity(0.3), lineWidth: 1) ) } } .frame(width: size, height: size) .contentShape(Rectangle()) // 确保整个区域都可点击 } .buttonStyle(PlainButtonStyle()) // 使用无样式按钮 .sheet(isPresented: $isImagePickerPresented) { // 显示图片选择器 PhotoPicker( selectedImages: Binding( get: { [selectedImage].compactMap { $0 } }, set: { images in selectedImage = images.first } ), selectionLimit: 1, // 限制只能选择一张图片 onImageUploaded: { result in // 图片上传完成后的处理 onUploadComplete?(result) } ) } } }