176 lines
6.7 KiB
Swift
176 lines
6.7 KiB
Swift
import SwiftUI
|
||
import PhotosUI
|
||
import os
|
||
|
||
@available(iOS 16.0, *)
|
||
public struct MultiImageUploader<Content: View>: View {
|
||
@State var selectedImages: [UIImage] = []
|
||
@State private var uploadResults: [UploadResult] = []
|
||
@State private var isUploading = false
|
||
@State private var showingImagePicker = false
|
||
@State private var uploadProgress: [UUID: Double] = [:] // 跟踪每个上传任务的进度
|
||
@State private var needsViewUpdate = false // Add this line to trigger view updates
|
||
|
||
private let maxSelection: Int
|
||
public var onUploadComplete: ([UploadResult]) -> Void
|
||
private let uploadService = ImageUploadService.shared
|
||
private let logger = Logger(subsystem: "com.yourapp.uploader", category: "MultiImageUploader")
|
||
|
||
// 自定义内容
|
||
private let content: ((_ isUploading: Bool, _ selectedCount: Int) -> Content)?
|
||
|
||
/// 控制是否显示图片选择器
|
||
@Binding var isImagePickerPresented: Bool
|
||
|
||
/// 选中的图片
|
||
@Binding var selectedImagesBinding: [UIImage]?
|
||
|
||
/// 控制是否显示图片预览
|
||
@State private var showingImagePreview = false
|
||
|
||
// 初始化方法 - 使用自定义视图
|
||
public init(
|
||
maxSelection: Int = 10,
|
||
isImagePickerPresented: Binding<Bool>,
|
||
selectedImagesBinding: Binding<[UIImage]?>,
|
||
@ViewBuilder content: @escaping (_ isUploading: Bool, _ selectedCount: Int) -> Content,
|
||
onUploadComplete: @escaping ([UploadResult]) -> Void
|
||
) {
|
||
self.maxSelection = maxSelection
|
||
self._isImagePickerPresented = isImagePickerPresented
|
||
self._selectedImagesBinding = selectedImagesBinding
|
||
self.content = content
|
||
self.onUploadComplete = onUploadComplete
|
||
}
|
||
|
||
// 初始化方法 - 使用默认按钮样式(向后兼容)
|
||
public init(
|
||
maxSelection: Int = 10,
|
||
isImagePickerPresented: Binding<Bool>,
|
||
selectedImagesBinding: Binding<[UIImage]?>,
|
||
onUploadComplete: @escaping ([UploadResult]) -> Void
|
||
) where Content == EmptyView {
|
||
self.maxSelection = maxSelection
|
||
self._isImagePickerPresented = isImagePickerPresented
|
||
self._selectedImagesBinding = selectedImagesBinding
|
||
self.content = nil
|
||
self.onUploadComplete = onUploadComplete
|
||
}
|
||
|
||
public var body: some View {
|
||
VStack(spacing: 16) {
|
||
// 自定义内容或默认按钮
|
||
if let content = content {
|
||
Button(action: {
|
||
showingImagePicker = true
|
||
}) {
|
||
content(isUploading, selectedImages.count)
|
||
}
|
||
.buttonStyle(PlainButtonStyle())
|
||
} else {
|
||
// 默认按钮样式
|
||
Button(action: {
|
||
showingImagePicker = true
|
||
}) {
|
||
Label(
|
||
!selectedImages.isEmpty ?
|
||
"已选择 \(selectedImages.count) 张图片" :
|
||
"选择图片",
|
||
systemImage: "photo.on.rectangle"
|
||
)
|
||
.font(.headline)
|
||
.foregroundColor(.white)
|
||
.padding()
|
||
.frame(maxWidth: .infinity)
|
||
.background(Color.blue)
|
||
.cornerRadius(10)
|
||
}
|
||
.padding(.horizontal)
|
||
}
|
||
}
|
||
.sheet(isPresented: $showingImagePicker) {
|
||
ImagePicker(images: $selectedImages, selectionLimit: maxSelection) {
|
||
// 当选择完成时,关闭选择器并开始上传
|
||
showingImagePicker = false
|
||
if !selectedImages.isEmpty {
|
||
Task {
|
||
_ = await startUpload()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.onChange(of: isImagePickerPresented) { _, newValue in
|
||
if newValue {
|
||
showingImagePicker = true
|
||
}
|
||
}
|
||
.onChange(of: showingImagePicker) { _, newValue in
|
||
if !newValue {
|
||
isImagePickerPresented = false
|
||
}
|
||
}
|
||
.onChange(of: selectedImages) { _, newValue in
|
||
selectedImagesBinding = newValue
|
||
}
|
||
.onChange(of: needsViewUpdate) { _, _ in
|
||
// Trigger view update
|
||
}
|
||
}
|
||
|
||
/// 上传图片方法,由父组件调用
|
||
@MainActor
|
||
public func startUpload() async -> [UploadResult] {
|
||
guard !isUploading && !selectedImages.isEmpty else { return [] }
|
||
|
||
isUploading = true
|
||
uploadResults = selectedImages.map {
|
||
UploadResult(image: $0, status: .uploading(progress: 0))
|
||
}
|
||
|
||
let group = DispatchGroup()
|
||
|
||
for (index, image) in selectedImages.enumerated() {
|
||
group.enter()
|
||
|
||
// 使用 ImageUploadService 上传图片
|
||
uploadService.uploadOriginalAndCompressedImage(
|
||
image,
|
||
compressionQuality: 0.7,
|
||
progress: { progress in
|
||
// 更新上传进度
|
||
DispatchQueue.main.async {
|
||
if index < self.uploadResults.count {
|
||
self.uploadResults[index].status = .uploading(progress: progress.progress)
|
||
self.needsViewUpdate.toggle() // Trigger view update
|
||
}
|
||
}
|
||
},
|
||
completion: { result in
|
||
DispatchQueue.main.async {
|
||
guard index < self.uploadResults.count else { return }
|
||
switch result {
|
||
case .success(let uploadResults):
|
||
self.uploadResults[index].status = .success
|
||
self.uploadResults[index].fileId = uploadResults.original.fileId
|
||
self.uploadResults[index].previewFileId = uploadResults.compressed.fileId
|
||
self.needsViewUpdate.toggle() // Trigger view update
|
||
case .failure(let error):
|
||
self.uploadResults[index].status = .failure(error)
|
||
self.needsViewUpdate.toggle() // Trigger view update
|
||
self.logger.error("图片上传失败: \(error.localizedDescription)")
|
||
}
|
||
group.leave()
|
||
}
|
||
}
|
||
)
|
||
}
|
||
|
||
return await withCheckedContinuation { continuation in
|
||
group.notify(queue: .main) {
|
||
self.isUploading = false
|
||
self.needsViewUpdate.toggle() // Trigger view update
|
||
continuation.resume(returning: self.uploadResults)
|
||
}
|
||
}
|
||
}
|
||
} |