import SwiftUI import os.log class MediaUploader: ObservableObject { @Published var selectedMedia: [MediaType] = [] @Published private(set) var isUploading = false @Published private(set) var uploadProgress: [Int: Double] = [:] // 跟踪每个上传任务的进度 @Published var showError = false @Published private(set) var errorMessage = "" @Published var showMediaPicker = false let maxSelection: Int var onUploadComplete: ([(MediaType, URL)]?) -> Void var uploadFunction: (MediaType, @escaping (Double) -> Void) async throws -> URL private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.example.app", category: "MediaUploader") init(maxSelection: Int, onUploadComplete: @escaping ([(MediaType, URL)]?) -> Void, uploadFunction: @escaping (MediaType, @escaping (Double) -> Void) async throws -> URL) { self.maxSelection = maxSelection self.onUploadComplete = onUploadComplete self.uploadFunction = uploadFunction } // 显示媒体选择器 func showPicker() { DispatchQueue.main.async { [weak self] in self?.showMediaPicker = true } } // 公开的方法供外部调用 func startUpload() async -> [(MediaType, URL)]? { guard !selectedMedia.isEmpty else { return nil } isUploading = true uploadProgress.removeAll() do { var uploadedResults: [(MediaType, URL)] = [] for (index, media) in selectedMedia.enumerated() { let url = try await uploadFunction(media) { [weak self] progress in DispatchQueue.main.async { self?.uploadProgress[index] = progress } } uploadedResults.append((media, url)) } DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.isUploading = false self.onUploadComplete(uploadedResults) self.selectedMedia.removeAll() } return uploadedResults } catch { logger.error("上传失败: \(error.localizedDescription)") DispatchQueue.main.async { [weak self] in guard let self = self else { return } self.errorMessage = "上传失败: \(error.localizedDescription)" self.showError = true self.isUploading = false } return nil } } } struct MediaUploaderView: View { @ObservedObject var mediaUploader: MediaUploader var body: some View { VStack(alignment: .leading, spacing: 16) { // 显示已选媒体数量 if !mediaUploader.selectedMedia.isEmpty { Text("已选择 \(mediaUploader.selectedMedia.count) 个文件") .foregroundColor(.secondary) } } .padding() .background(Color(.systemBackground)) .cornerRadius(12) .shadow(radius: 2) .fullScreenCover(isPresented: $mediaUploader.showMediaPicker) { MediaPicker( selectedMedia: $mediaUploader.selectedMedia, selectionLimit: mediaUploader.maxSelection ) { mediaUploader.showMediaPicker = false } } .alert("错误", isPresented: $mediaUploader.showError) { Button("确定", role: .cancel) {} } message: { Text(mediaUploader.errorMessage) } } } // MARK: - 预览 struct MediaUploader_Previews: PreviewProvider { static var previews: some View { Group { MediaUploaderView( mediaUploader: MediaUploader( maxSelection: 5, onUploadComplete: { _ in }, uploadFunction: { _, _ in // 模拟上传 try await Task.sleep(nanoseconds: 1_000_000_000) return URL(string: "https://example.com/uploaded")! } ) ) .padding() .previewLayout(.sizeThatFits) MediaUploaderView( mediaUploader: MediaUploader( maxSelection: 5, onUploadComplete: { _ in }, uploadFunction: { _, _ in // 模拟上传 try await Task.sleep(nanoseconds: 1_000_000_000) return URL(string: "https://example.com/uploaded")! } ) ) .padding() .previewLayout(.sizeThatFits) .preferredColorScheme(.dark) } } }