feat: 限制选择类型
This commit is contained in:
parent
7e807f6a26
commit
06f7c1e367
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
// 当媒体选择器关闭时,如果有选中的媒体,开始上传
|
// 当媒体选择器关闭时,如果有选中的媒体,开始上传
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user