feat: 限制选择数量

This commit is contained in:
jinyaqiu 2025-08-21 11:08:21 +08:00
parent 4670b27942
commit 7e807f6a26
3 changed files with 127 additions and 75 deletions

View File

@ -38,29 +38,14 @@ public enum MediaType: Equatable {
struct MediaPicker: UIViewControllerRepresentable { struct MediaPicker: UIViewControllerRepresentable {
@Binding var selectedMedia: [MediaType] @Binding var selectedMedia: [MediaType]
let selectionLimit: Int let imageSelectionLimit: Int
let videoSelectionLimit: Int
let onDismiss: (() -> Void)? let onDismiss: (() -> Void)?
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
configuration.filter = .any(of: [.videos, .images])
configuration.selectionLimit = selectionLimit
configuration.preferredAssetRepresentationMode = .current
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
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
init(_ parent: MediaPicker) { init(_ parent: MediaPicker) {
self.parent = parent self.parent = parent
@ -72,7 +57,68 @@ struct MediaPicker: UIViewControllerRepresentable {
return return
} }
var processedMedia: [MediaType] = [] //
var currentImageCount = 0
var currentVideoCount = 0
for media in parent.selectedMedia {
switch media {
case .image: currentImageCount += 1
case .video: currentVideoCount += 1
}
}
//
var newImages = 0
var newVideos = 0
for result in results {
let itemProvider = result.itemProvider
if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
newImages += 1
} else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
newVideos += 1
}
}
//
if (currentImageCount + newImages > parent.imageSelectionLimit) ||
(currentVideoCount + newVideos > parent.videoSelectionLimit) {
//
var message = "选择超出限制:\n"
var limits: [String] = []
if currentImageCount + newImages > parent.imageSelectionLimit {
limits.append("图片最多选择\(parent.imageSelectionLimit)")
}
if currentVideoCount + newVideos > parent.videoSelectionLimit {
limits.append("视频最多选择\(parent.videoSelectionLimit)")
}
message += limits.joined(separator: "\n")
//
let alert = UIAlertController(
title: "提示",
message: message,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "好的", style: .default) { _ in
//
})
//
picker.present(alert, animated: true)
return
}
//
processSelectedMedia(results: results, picker: picker)
}
private func processSelectedMedia(results: [PHPickerResult], picker: PHPickerViewController) {
var processedMedia = parent.selectedMedia
let group = DispatchGroup() let group = DispatchGroup()
for result in results { for result in results {
@ -82,7 +128,9 @@ struct MediaPicker: UIViewControllerRepresentable {
group.enter() group.enter()
processImage(itemProvider: itemProvider) { media in processImage(itemProvider: itemProvider) { media in
if let media = media { if let media = media {
processedMedia.append(media) DispatchQueue.main.async {
processedMedia.append(media)
}
} }
group.leave() group.leave()
} }
@ -90,7 +138,9 @@ struct MediaPicker: UIViewControllerRepresentable {
group.enter() group.enter()
processVideo(itemProvider: itemProvider) { media in processVideo(itemProvider: itemProvider) { media in
if let media = media { if let media = media {
processedMedia.append(media) DispatchQueue.main.async {
processedMedia.append(media)
}
} }
group.leave() group.leave()
} }
@ -99,8 +149,9 @@ struct MediaPicker: UIViewControllerRepresentable {
group.notify(queue: .main) { group.notify(queue: .main) {
self.parent.selectedMedia = processedMedia self.parent.selectedMedia = processedMedia
self.printMediaInfo(media: processedMedia) picker.dismiss(animated: true) {
self.parent.onDismiss?() self.parent.onDismiss?()
}
} }
} }
@ -147,55 +198,26 @@ struct MediaPicker: UIViewControllerRepresentable {
} }
} }
} }
}
private func printMediaInfo(media: [MediaType]) { func makeUIViewController(context: Context) -> PHPickerViewController {
print("=== Selected Media Information ===") var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
for (index, media) in media.enumerated() { configuration.filter = .any(of: [.videos, .images])
print("\nItem \(index + 1):") configuration.selectionLimit = 0 // 0
configuration.preferredAssetRepresentationMode = .current
switch media { let picker = PHPickerViewController(configuration: configuration)
case .image(let image): picker.delegate = context.coordinator
print("Type: Image") context.coordinator.currentPicker = picker
print("Dimensions: \(Int(image.size.width))x\(Int(image.size.height))")
if let data = image.jpegData(compressionQuality: 1.0) {
print("File Size: \(formatFileSize(Int64(data.count)))")
}
case .video(let url, _): return picker
print("Type: Video") }
print("File Name: \(url.lastPathComponent)")
print("File Path: \(url.path)")
if let attributes = try? FileManager.default.attributesOfItem(atPath: url.path), func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
let fileSize = attributes[.size] as? Int64 { //
print("File Size: \(formatFileSize(fileSize))") }
}
let asset = AVURLAsset(url: url) func makeCoordinator() -> Coordinator {
let duration = asset.duration.seconds Coordinator(self)
print("Duration: \(formatTimeInterval(duration))")
if let track = asset.tracks(withMediaType: .video).first {
let size = track.naturalSize
print("Video Dimensions: \(Int(size.width))x\(Int(size.height))")
}
}
}
print("================================\n")
}
private func formatFileSize(_ bytes: Int64) -> String {
let formatter = ByteCountFormatter()
formatter.allowedUnits = [.useBytes, .useKB, .useMB, .useGB]
formatter.countStyle = .file
return formatter.string(fromByteCount: bytes)
}
private func formatTimeInterval(_ interval: TimeInterval) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.zeroFormattingBehavior = .pad
return formatter.string(from: interval) ?? "00:00"
}
} }
} }

View File

@ -151,6 +151,15 @@ struct MediaUploadExample: View {
@StateObject private var uploadManager = MediaUploadManager() @StateObject private var uploadManager = MediaUploadManager()
@State private var showMediaPicker = false @State private var showMediaPicker = false
//
let imageSelectionLimit: Int
let videoSelectionLimit: Int
init(imageSelectionLimit: Int = 10, videoSelectionLimit: Int = 10) {
self.imageSelectionLimit = imageSelectionLimit
self.videoSelectionLimit = videoSelectionLimit
}
var body: some View { var body: some View {
NavigationView { NavigationView {
VStack(spacing: 20) { VStack(spacing: 20) {
@ -166,6 +175,21 @@ struct MediaUploadExample: View {
} }
.padding(.horizontal) .padding(.horizontal)
//
VStack(alignment: .leading, spacing: 4) {
Text("选择限制:")
.font(.subheadline)
.foregroundColor(.secondary)
Text("• 最多选择 \(imageSelectionLimit) 张图片")
.font(.caption)
.foregroundColor(.secondary)
Text("• 最多选择 \(videoSelectionLimit) 个视频")
.font(.caption)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
// //
MediaSelectionView(uploadManager: uploadManager) MediaSelectionView(uploadManager: uploadManager)
@ -188,7 +212,8 @@ struct MediaUploadExample: View {
.sheet(isPresented: $showMediaPicker) { .sheet(isPresented: $showMediaPicker) {
MediaPicker( MediaPicker(
selectedMedia: $uploadManager.selectedMedia, selectedMedia: $uploadManager.selectedMedia,
selectionLimit: 5, imageSelectionLimit: imageSelectionLimit,
videoSelectionLimit: videoSelectionLimit,
onDismiss: { showMediaPicker = false } onDismiss: { showMediaPicker = false }
) )
} }
@ -293,6 +318,10 @@ private struct MediaThumbnailView: View {
} }
#Preview { #Preview {
MediaUploadExample() //
.environmentObject(AuthState.shared) MediaUploadExample(
imageSelectionLimit: 5,
videoSelectionLimit: 2
)
.environmentObject(AuthState.shared)
} }

View File

@ -25,7 +25,8 @@ struct MediaUploadDemo: View {
.sheet(isPresented: $showMediaPicker) { .sheet(isPresented: $showMediaPicker) {
MediaPicker( MediaPicker(
selectedMedia: $uploadManager.selectedMedia, selectedMedia: $uploadManager.selectedMedia,
selectionLimit: 10, imageSelectionLimit: 2,
videoSelectionLimit: 2,
onDismiss: { onDismiss: {
showMediaPicker = false showMediaPicker = false
// //