feat: 限制选择数量
This commit is contained in:
parent
4670b27942
commit
7e807f6a26
@ -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,16 +128,20 @@ 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 {
|
||||||
|
DispatchQueue.main.async {
|
||||||
processedMedia.append(media)
|
processedMedia.append(media)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
} else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
|
} else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
|
||||||
group.enter()
|
group.enter()
|
||||||
processVideo(itemProvider: itemProvider) { media in
|
processVideo(itemProvider: itemProvider) { media in
|
||||||
if let media = media {
|
if let media = media {
|
||||||
|
DispatchQueue.main.async {
|
||||||
processedMedia.append(media)
|
processedMedia.append(media)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
group.leave()
|
group.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,10 +149,11 @@ 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?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func processImage(itemProvider: NSItemProvider, completion: @escaping (MediaType?) -> Void) {
|
private func processImage(itemProvider: NSItemProvider, completion: @escaping (MediaType?) -> Void) {
|
||||||
itemProvider.loadObject(ofClass: UIImage.self) { (object, error) in
|
itemProvider.loadObject(ofClass: UIImage.self) { (object, error) in
|
||||||
@ -147,55 +198,26 @@ struct MediaPicker: UIViewControllerRepresentable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func printMediaInfo(media: [MediaType]) {
|
|
||||||
print("=== Selected Media Information ===")
|
|
||||||
for (index, media) in media.enumerated() {
|
|
||||||
print("\nItem \(index + 1):")
|
|
||||||
|
|
||||||
switch media {
|
|
||||||
case .image(let image):
|
|
||||||
print("Type: Image")
|
|
||||||
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, _):
|
func makeUIViewController(context: Context) -> PHPickerViewController {
|
||||||
print("Type: Video")
|
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
|
||||||
print("File Name: \(url.lastPathComponent)")
|
configuration.filter = .any(of: [.videos, .images])
|
||||||
print("File Path: \(url.path)")
|
configuration.selectionLimit = 0 // 设置为0表示不限制选择数量,我们在代码中处理
|
||||||
|
configuration.preferredAssetRepresentationMode = .current
|
||||||
|
|
||||||
if let attributes = try? FileManager.default.attributesOfItem(atPath: url.path),
|
let picker = PHPickerViewController(configuration: configuration)
|
||||||
let fileSize = attributes[.size] as? Int64 {
|
picker.delegate = context.coordinator
|
||||||
print("File Size: \(formatFileSize(fileSize))")
|
context.coordinator.currentPicker = picker
|
||||||
|
|
||||||
|
return picker
|
||||||
}
|
}
|
||||||
|
|
||||||
let asset = AVURLAsset(url: url)
|
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
|
||||||
let duration = asset.duration.seconds
|
// 更新视图控制器(如果需要)
|
||||||
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 {
|
func makeCoordinator() -> Coordinator {
|
||||||
let formatter = ByteCountFormatter()
|
Coordinator(self)
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
// 在预览中显示自定义限制
|
||||||
|
MediaUploadExample(
|
||||||
|
imageSelectionLimit: 5,
|
||||||
|
videoSelectionLimit: 2
|
||||||
|
)
|
||||||
.environmentObject(AuthState.shared)
|
.environmentObject(AuthState.shared)
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
// 当媒体选择器关闭时,如果有选中的媒体,开始上传
|
// 当媒体选择器关闭时,如果有选中的媒体,开始上传
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user