diff --git a/wake/View/Components/Upload/MediaPicker.swift b/wake/View/Components/Upload/MediaPicker.swift index e10dc01..9153f20 100644 --- a/wake/View/Components/Upload/MediaPicker.swift +++ b/wake/View/Components/Upload/MediaPicker.swift @@ -36,16 +36,64 @@ public enum MediaType: Equatable { } } +enum MediaTypeFilter { + case imagesOnly + case videosOnly + case all + + var pickerFilter: PHPickerFilter { + switch self { + case .imagesOnly: return .images + case .videosOnly: return .videos + case .all: return .any(of: [.videos, .images]) + } + } +} + struct MediaPicker: UIViewControllerRepresentable { @Binding var selectedMedia: [MediaType] let imageSelectionLimit: Int let videoSelectionLimit: Int let onDismiss: (() -> Void)? + let allowedMediaTypes: MediaTypeFilter + + init(selectedMedia: Binding<[MediaType]>, + imageSelectionLimit: Int = 10, + videoSelectionLimit: Int = 10, + allowedMediaTypes: MediaTypeFilter = .all, + onDismiss: (() -> Void)? = nil) { + self._selectedMedia = selectedMedia + self.imageSelectionLimit = imageSelectionLimit + self.videoSelectionLimit = videoSelectionLimit + self.allowedMediaTypes = allowedMediaTypes + self.onDismiss = onDismiss + } + + func makeUIViewController(context: Context) -> PHPickerViewController { + var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) + configuration.filter = allowedMediaTypes.pickerFilter + configuration.selectionLimit = 0 // 设置为0表示不限制选择数量,我们在代码中处理 + configuration.preferredAssetRepresentationMode = .current + + let picker = PHPickerViewController(configuration: configuration) + picker.delegate = context.coordinator + context.coordinator.currentPicker = picker + + return picker + } + + func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { + // 更新视图控制器(如果需要) + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } class Coordinator: NSObject, PHPickerViewControllerDelegate { let parent: MediaPicker private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.example.app", category: "MediaPicker") - internal var currentPicker: PHPickerViewController? // 将 private 修改为 internal + internal var currentPicker: PHPickerViewController? init(_ parent: MediaPicker) { self.parent = parent @@ -75,8 +123,12 @@ struct MediaPicker: UIViewControllerRepresentable { for result in results { let itemProvider = result.itemProvider if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) { + // 检查是否允许选择图片 + guard parent.allowedMediaTypes != .videosOnly else { continue } newImages += 1 } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) { + // 检查是否允许选择视频 + guard parent.allowedMediaTypes != .imagesOnly else { continue } newVideos += 1 } } @@ -89,10 +141,10 @@ struct MediaPicker: UIViewControllerRepresentable { var message = "选择超出限制:\n" var limits: [String] = [] - if currentImageCount + newImages > parent.imageSelectionLimit { + if currentImageCount + newImages > parent.imageSelectionLimit && parent.allowedMediaTypes != .videosOnly { limits.append("图片最多选择\(parent.imageSelectionLimit)张") } - if currentVideoCount + newVideos > parent.videoSelectionLimit { + if currentVideoCount + newVideos > parent.videoSelectionLimit && parent.allowedMediaTypes != .imagesOnly { limits.append("视频最多选择\(parent.videoSelectionLimit)个") } @@ -113,7 +165,7 @@ struct MediaPicker: UIViewControllerRepresentable { return } - // 如果没有超出限制,处理选择的媒体 + // 处理选择的媒体 processSelectedMedia(results: results, picker: picker) } @@ -125,6 +177,9 @@ struct MediaPicker: UIViewControllerRepresentable { let itemProvider = result.itemProvider if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) { + // 检查是否允许选择图片 + guard parent.allowedMediaTypes != .videosOnly else { continue } + group.enter() processImage(itemProvider: itemProvider) { media in if let media = media { @@ -135,6 +190,9 @@ struct MediaPicker: UIViewControllerRepresentable { group.leave() } } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) { + // 检查是否允许选择视频 + guard parent.allowedMediaTypes != .imagesOnly else { continue } + group.enter() processVideo(itemProvider: itemProvider) { media in if let media = media { @@ -183,14 +241,10 @@ struct MediaPicker: UIViewControllerRepresentable { } try FileManager.default.copyItem(at: videoURL, to: targetURL) - MediaUtils.extractFirstFrame(from: targetURL) { result in - switch result { - case .success(let thumbnail): - completion(.video(targetURL, thumbnail)) - case .failure(let error): - self.logger.error("Failed to extract video thumbnail: \(error.localizedDescription)") - completion(.video(targetURL, nil)) - } + if let thumbnail = self.generateThumbnail(for: targetURL) { + completion(.video(targetURL, thumbnail)) + } else { + completion(.video(targetURL, nil)) } } catch { self.logger.error("Failed to copy video file: \(error.localizedDescription)") @@ -198,26 +252,19 @@ struct MediaPicker: UIViewControllerRepresentable { } } } - } - - func makeUIViewController(context: Context) -> PHPickerViewController { - var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) - configuration.filter = .any(of: [.videos, .images]) - configuration.selectionLimit = 0 // 设置为0表示不限制选择数量,我们在代码中处理 - configuration.preferredAssetRepresentationMode = .current - let picker = PHPickerViewController(configuration: configuration) - picker.delegate = context.coordinator - context.coordinator.currentPicker = picker - - return picker - } - - func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) { - // 更新视图控制器(如果需要) - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) + private func generateThumbnail(for videoURL: URL) -> UIImage? { + let asset = AVAsset(url: videoURL) + let imageGenerator = AVAssetImageGenerator(asset: asset) + imageGenerator.appliesPreferredTrackTransform = true + + do { + let cgImage = try imageGenerator.copyCGImage(at: CMTime(seconds: 0, preferredTimescale: 1), actualTime: nil) + return UIImage(cgImage: cgImage) + } catch { + logger.error("Failed to generate thumbnail: \(error.localizedDescription)") + return nil + } + } } } diff --git a/wake/View/Examples/MediaDemo.swift b/wake/View/Examples/MediaDemo.swift index a53bbed..a7578d6 100644 --- a/wake/View/Examples/MediaDemo.swift +++ b/wake/View/Examples/MediaDemo.swift @@ -25,8 +25,9 @@ struct MediaUploadDemo: View { .sheet(isPresented: $showMediaPicker) { MediaPicker( selectedMedia: $uploadManager.selectedMedia, - imageSelectionLimit: 2, - videoSelectionLimit: 2, + imageSelectionLimit: 1, + videoSelectionLimit: 0, + allowedMediaTypes: .imagesOnly, onDismiss: { showMediaPicker = false // 当媒体选择器关闭时,如果有选中的媒体,开始上传