import SwiftUI import PhotosUI import os.log @available(iOS 16.0, *) public struct MultiImageUploader: View { @State private var selectedImages: [UIImage] = [] @State private var uploadResults: [UploadResult] = [] @State private var isUploading = false @State private var showingImagePicker = false @State private var uploadProgress: [UUID: Double] = [:] // 跟踪每个上传任务的进度 private let maxSelection: Int private let onUploadComplete: ([UploadResult]) -> Void private let uploadService = ImageUploadService.shared private let logger = Logger(subsystem: "com.yourapp.uploader", category: "MultiImageUploader") public init( maxSelection: Int = 10, onUploadComplete: @escaping ([UploadResult]) -> Void ) { self.maxSelection = maxSelection self.onUploadComplete = onUploadComplete } public var body: some View { VStack(spacing: 16) { // 上传按钮 Button(action: { showingImagePicker = true }) { Label("选择图片", systemImage: "photo.on.rectangle") .font(.headline) .foregroundColor(.white) .padding() .frame(maxWidth: .infinity) .background(Color.blue) .cornerRadius(10) } .padding(.horizontal) .sheet(isPresented: $showingImagePicker) { ImagePicker(images: $selectedImages, selectionLimit: maxSelection) { // 当选择完成时,开始上传 if !selectedImages.isEmpty { uploadImages() } } } // 上传进度和图片网格 ScrollView { LazyVStack(spacing: 16) { ForEach($uploadResults) { $result in VStack(spacing: 8) { // 图片预览 Image(uiImage: result.image) .resizable() .scaledToFill() .frame(height: 200) .frame(maxWidth: .infinity) .clipped() .cornerRadius(8) // 上传进度条 if case .uploading = result.status { ProgressView(value: uploadProgress[result.id] ?? 0, total: 1.0) .progressViewStyle(LinearProgressViewStyle()) .padding(.horizontal) Text("上传中: \(Int((uploadProgress[result.id] ?? 0) * 100))%") .font(.caption) .foregroundColor(.secondary) } else if case .success = result.status { HStack { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) Text("上传成功") .font(.caption) .foregroundColor(.green) } } else if case .failure = result.status { HStack { Image(systemName: "exclamationmark.triangle.fill") .foregroundColor(.red) Text("上传失败") .font(.caption) .foregroundColor(.red) } } } .padding() .background(Color(.systemBackground)) .cornerRadius(12) .shadow(radius: 2) } } .padding() } // 上传按钮 if !selectedImages.isEmpty && !isUploading { Button(action: uploadImages) { Text("开始上传 (\(selectedImages.count)张)") .font(.headline) .foregroundColor(.white) .padding() .frame(maxWidth: .infinity) .background(Color.blue) .cornerRadius(10) } .padding(.horizontal) .padding(.bottom) } } .background(Color(.systemGroupedBackground)) } private func uploadImages() { guard !isUploading else { return } isUploading = true let group = DispatchGroup() var results = selectedImages.map { image in UploadResult(fileId: "", previewFileId: "", image: image, status: .uploading(progress: 0)) } // 更新UI显示上传中的状态 uploadResults = results // 创建并发的DispatchQueue let uploadQueue = DispatchQueue(label: "com.wake.uploadQueue", attributes: .concurrent) let semaphore = DispatchSemaphore(value: 3) // 限制并发数为3 for (index, image) in selectedImages.enumerated() { semaphore.wait() group.enter() uploadQueue.async { let resultId = results[index].id // 更新状态为上传中 DispatchQueue.main.async { if let idx = results.firstIndex(where: { $0.id == resultId }) { results[idx].status = .uploading(progress: 0) uploadProgress[resultId] = 0 uploadResults = results } } // 上传原图 uploadService.uploadImage(image, progress: { progress in DispatchQueue.main.async { uploadProgress[resultId] = progress.progress if let idx = results.firstIndex(where: { $0.id == resultId }) { results[idx].status = .uploading(progress: progress.progress) uploadResults = results } } }) { uploadResult in defer { semaphore.signal() group.leave() } DispatchQueue.main.async { if let idx = results.firstIndex(where: { $0.id == resultId }) { switch uploadResult { case .success(let uploadResult): // 上传成功,更新结果 results[idx].fileId = uploadResult.fileId // 使用空字符串作为 previewFileId,因为 ImageUploaderGetID.UploadResult 没有这个属性 results[idx].previewFileId = "" results[idx].status = .success uploadResults = results logger.info("图片上传成功: \(uploadResult.fileId)") case .failure(let error): // 上传失败 results[idx].status = .failure(error) uploadResults = results logger.error("图片上传失败: \(error.localizedDescription)") } } } } } } // 所有上传任务完成后的处理 group.notify(queue: .main) { self.isUploading = false let successCount = results.filter { $0.status == .success }.count logger.info("上传完成,成功: \(successCount)/\(results.count)") self.onUploadComplete(results) } } } // MARK: - 图片选择器 @available(iOS 14.0, *) struct ImagePicker: UIViewControllerRepresentable { @Binding var images: [UIImage] var selectionLimit: Int = 10 var onDismiss: (() -> Void)? func makeUIViewController(context: Context) -> PHPickerViewController { var config = PHPickerConfiguration(photoLibrary: .shared()) config.filter = .images config.selectionLimit = selectionLimit config.preferredAssetRepresentationMode = .current let picker = PHPickerViewController(configuration: config) picker.delegate = context.coordinator return picker } func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, PHPickerViewControllerDelegate { let parent: ImagePicker init(_ parent: ImagePicker) { self.parent = parent } func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) let group = DispatchGroup() var newImages: [UIImage] = [] for result in results { group.enter() let itemProvider = result.itemProvider if itemProvider.canLoadObject(ofClass: UIImage.self) { itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in if let image = image as? UIImage { newImages.append(image) } group.leave() } } else { group.leave() } } group.notify(queue: .main) { self.parent.images = newImages self.parent.onDismiss?() } } } }