feat: 限制选择类型

This commit is contained in:
jinyaqiu 2025-08-21 11:23:28 +08:00
parent 7e807f6a26
commit 06f7c1e367
2 changed files with 82 additions and 34 deletions

View File

@ -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 { struct MediaPicker: UIViewControllerRepresentable {
@Binding var selectedMedia: [MediaType] @Binding var selectedMedia: [MediaType]
let imageSelectionLimit: Int let imageSelectionLimit: Int
let videoSelectionLimit: Int let videoSelectionLimit: Int
let onDismiss: (() -> Void)? 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 { class Coordinator: NSObject, PHPickerViewControllerDelegate {
let parent: MediaPicker let parent: MediaPicker
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.example.app", category: "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) { init(_ parent: MediaPicker) {
self.parent = parent self.parent = parent
@ -75,8 +123,12 @@ struct MediaPicker: UIViewControllerRepresentable {
for result in results { for result in results {
let itemProvider = result.itemProvider let itemProvider = result.itemProvider
if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) { if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
//
guard parent.allowedMediaTypes != .videosOnly else { continue }
newImages += 1 newImages += 1
} else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) { } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
//
guard parent.allowedMediaTypes != .imagesOnly else { continue }
newVideos += 1 newVideos += 1
} }
} }
@ -89,10 +141,10 @@ struct MediaPicker: UIViewControllerRepresentable {
var message = "选择超出限制:\n" var message = "选择超出限制:\n"
var limits: [String] = [] var limits: [String] = []
if currentImageCount + newImages > parent.imageSelectionLimit { if currentImageCount + newImages > parent.imageSelectionLimit && parent.allowedMediaTypes != .videosOnly {
limits.append("图片最多选择\(parent.imageSelectionLimit)") limits.append("图片最多选择\(parent.imageSelectionLimit)")
} }
if currentVideoCount + newVideos > parent.videoSelectionLimit { if currentVideoCount + newVideos > parent.videoSelectionLimit && parent.allowedMediaTypes != .imagesOnly {
limits.append("视频最多选择\(parent.videoSelectionLimit)") limits.append("视频最多选择\(parent.videoSelectionLimit)")
} }
@ -113,7 +165,7 @@ struct MediaPicker: UIViewControllerRepresentable {
return return
} }
// //
processSelectedMedia(results: results, picker: picker) processSelectedMedia(results: results, picker: picker)
} }
@ -125,6 +177,9 @@ struct MediaPicker: UIViewControllerRepresentable {
let itemProvider = result.itemProvider let itemProvider = result.itemProvider
if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) { if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
//
guard parent.allowedMediaTypes != .videosOnly else { continue }
group.enter() group.enter()
processImage(itemProvider: itemProvider) { media in processImage(itemProvider: itemProvider) { media in
if let media = media { if let media = media {
@ -135,6 +190,9 @@ struct MediaPicker: UIViewControllerRepresentable {
group.leave() group.leave()
} }
} else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) { } else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
//
guard parent.allowedMediaTypes != .imagesOnly else { continue }
group.enter() group.enter()
processVideo(itemProvider: itemProvider) { media in processVideo(itemProvider: itemProvider) { media in
if let media = media { if let media = media {
@ -183,41 +241,30 @@ struct MediaPicker: UIViewControllerRepresentable {
} }
try FileManager.default.copyItem(at: videoURL, to: targetURL) try FileManager.default.copyItem(at: videoURL, to: targetURL)
MediaUtils.extractFirstFrame(from: targetURL) { result in if let thumbnail = self.generateThumbnail(for: targetURL) {
switch result {
case .success(let thumbnail):
completion(.video(targetURL, thumbnail)) completion(.video(targetURL, thumbnail))
case .failure(let error): } else {
self.logger.error("Failed to extract video thumbnail: \(error.localizedDescription)")
completion(.video(targetURL, nil)) completion(.video(targetURL, nil))
} }
}
} catch { } catch {
self.logger.error("Failed to copy video file: \(error.localizedDescription)") self.logger.error("Failed to copy video file: \(error.localizedDescription)")
completion(nil) completion(nil)
} }
} }
} }
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
} }
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)
} }
} }

View File

@ -25,8 +25,9 @@ struct MediaUploadDemo: View {
.sheet(isPresented: $showMediaPicker) { .sheet(isPresented: $showMediaPicker) {
MediaPicker( MediaPicker(
selectedMedia: $uploadManager.selectedMedia, selectedMedia: $uploadManager.selectedMedia,
imageSelectionLimit: 2, imageSelectionLimit: 1,
videoSelectionLimit: 2, videoSelectionLimit: 0,
allowedMediaTypes: .imagesOnly,
onDismiss: { onDismiss: {
showMediaPicker = false showMediaPicker = false
// //