268 lines
12 KiB
Swift
268 lines
12 KiB
Swift
import SwiftUI
|
||
import PhotosUI
|
||
|
||
// MARK: - Data Models
|
||
|
||
/// 上传进度信息
|
||
public struct UploadProgress {
|
||
public let current: Int
|
||
public let total: Int
|
||
public let progress: Double
|
||
public let isOriginal: Bool
|
||
|
||
public init(current: Int, total: Int, progress: Double, isOriginal: Bool) {
|
||
self.current = current
|
||
self.total = total
|
||
self.progress = progress
|
||
self.isOriginal = isOriginal
|
||
}
|
||
}
|
||
|
||
/// 上传结果,包含原图和压缩图的上传信息
|
||
public struct UploadResults {
|
||
public let original: ImageUploaderGetID.UploadResult
|
||
public let compressed: ImageUploaderGetID.UploadResult
|
||
|
||
public init(original: ImageUploaderGetID.UploadResult,
|
||
compressed: ImageUploaderGetID.UploadResult) {
|
||
self.original = original
|
||
self.compressed = compressed
|
||
}
|
||
}
|
||
|
||
// MARK: - Photo Picker
|
||
|
||
/// 照片选择器,封装了系统相册选择功能
|
||
struct PhotoPicker: UIViewControllerRepresentable {
|
||
// MARK: - Properties
|
||
@Binding var selectedImages: [UIImage]
|
||
let selectionLimit: Int
|
||
let filter: PHPickerFilter
|
||
var onImageUploaded: ((Result<UploadResults, Error>) -> Void)?
|
||
var onUploadProgress: ((UploadProgress) -> Void)?
|
||
|
||
// MARK: - Initialization
|
||
init(selectedImages: Binding<[UIImage]>,
|
||
selectionLimit: Int = 1,
|
||
filter: PHPickerFilter = .images,
|
||
onImageUploaded: ((Result<UploadResults, Error>) -> Void)? = nil,
|
||
onUploadProgress: ((UploadProgress) -> Void)? = nil) {
|
||
self._selectedImages = selectedImages
|
||
self.selectionLimit = selectionLimit
|
||
self.filter = filter
|
||
self.onImageUploaded = onImageUploaded
|
||
self.onUploadProgress = onUploadProgress
|
||
}
|
||
|
||
// MARK: - UIViewControllerRepresentable
|
||
func makeUIViewController(context: Context) -> PHPickerViewController {
|
||
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
|
||
configuration.filter = filter
|
||
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)
|
||
}
|
||
|
||
// MARK: - Coordinator
|
||
class Coordinator: NSObject, PHPickerViewControllerDelegate {
|
||
let parent: PhotoPicker
|
||
private let uploader = ImageUploaderGetID()
|
||
|
||
init(_ parent: PhotoPicker) {
|
||
self.parent = parent
|
||
}
|
||
|
||
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
||
parent.selectedImages.removeAll()
|
||
let group = DispatchGroup()
|
||
var loadedImages: [Int: UIImage] = [:]
|
||
var uploadResults: [Int: (original: ImageUploaderGetID.UploadResult?,
|
||
compressed: ImageUploaderGetID.UploadResult?)] = [:]
|
||
|
||
for (index, result) in results.enumerated() {
|
||
group.enter()
|
||
|
||
if result.itemProvider.canLoadObject(ofClass: UIImage.self) {
|
||
result.itemProvider.loadObject(ofClass: UIImage.self) { [weak self] (image, error) in
|
||
guard let self = self, let image = image as? UIImage else {
|
||
group.leave()
|
||
return
|
||
}
|
||
|
||
loadedImages[index] = image
|
||
|
||
guard let compressedImage = image.jpegData(compressionQuality: 0.5).flatMap(UIImage.init(data:)) else {
|
||
group.leave()
|
||
return
|
||
}
|
||
|
||
self.uploader.uploadImage(
|
||
image,
|
||
progress: { [weak self] progress in
|
||
let progressInfo = UploadProgress(
|
||
current: Int(progress * 100),
|
||
total: 100,
|
||
progress: progress,
|
||
isOriginal: true
|
||
)
|
||
DispatchQueue.main.async {
|
||
self?.parent.onUploadProgress?(progressInfo)
|
||
}
|
||
print("📤 原图上传进度: \(Int(progress * 100))%")
|
||
},
|
||
completion: { [weak self] originalResult in
|
||
guard let self = self else {
|
||
group.leave()
|
||
return
|
||
}
|
||
|
||
switch originalResult {
|
||
case .success(let originalUploadResult):
|
||
self.uploader.uploadImage(
|
||
compressedImage,
|
||
progress: { [weak self] progress in
|
||
let progressInfo = UploadProgress(
|
||
current: Int(progress * 100),
|
||
total: 100,
|
||
progress: progress,
|
||
isOriginal: false
|
||
)
|
||
DispatchQueue.main.async {
|
||
self?.parent.onUploadProgress?(progressInfo)
|
||
}
|
||
print("📊 压缩图上传进度: \(Int(progress * 100))%")
|
||
},
|
||
completion: { compressedResult in
|
||
defer { group.leave() }
|
||
|
||
switch compressedResult {
|
||
case .success(let compressedUploadResult):
|
||
uploadResults[index] = (originalUploadResult, compressedUploadResult)
|
||
print("✅ 原图和压缩图上传成功!")
|
||
print("📂 原图信息:")
|
||
print(" - 文件ID: \(originalUploadResult.fileId)")
|
||
print(" - 文件大小: \(originalUploadResult.fileSize) 字节")
|
||
print("📦 压缩图信息:")
|
||
print(" - 文件ID: \(compressedUploadResult.fileId)")
|
||
print(" - 文件大小: \(compressedUploadResult.fileSize) 字节")
|
||
|
||
MaterialService.shared.uploadMaterialInfo(
|
||
fileId: originalUploadResult.fileId,
|
||
previewFileId: compressedUploadResult.fileId
|
||
) { success, errorMessage in
|
||
if success {
|
||
print("✅ 文件信息上传成功 素材上传成功!!!!!")
|
||
} else if let errorMessage = errorMessage {
|
||
print("❌ 文件信息上传失败: \(errorMessage)")
|
||
}
|
||
}
|
||
|
||
case .failure(let error):
|
||
print("❌ 压缩图上传失败: \(error.localizedDescription)")
|
||
uploadResults[index] = (originalUploadResult, nil)
|
||
}
|
||
}
|
||
)
|
||
|
||
case .failure(let error):
|
||
print("❌ 原图上传失败: \(error.localizedDescription)")
|
||
group.leave()
|
||
}
|
||
}
|
||
)
|
||
}
|
||
} else {
|
||
group.leave()
|
||
}
|
||
}
|
||
|
||
group.notify(queue: .main) { [weak self] in
|
||
guard let self = self else { return }
|
||
|
||
let sortedImages = loadedImages.sorted { $0.key < $1.key }.map { $0.value }
|
||
self.parent.selectedImages.append(contentsOf: sortedImages)
|
||
|
||
if let firstResult = uploadResults.first?.value,
|
||
let original = firstResult.original,
|
||
let compressed = firstResult.compressed {
|
||
let results = UploadResults(original: original, compressed: compressed)
|
||
self.parent.onImageUploaded?(.success(results))
|
||
} else {
|
||
self.parent.onImageUploaded?(.failure(NSError(
|
||
domain: "com.wake.upload",
|
||
code: -1,
|
||
userInfo: [NSLocalizedDescriptionKey: "上传过程中出现错误"]
|
||
)))
|
||
}
|
||
|
||
picker.dismiss(animated: true)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Avatar Uploader
|
||
|
||
/// 头像上传视图,提供头像选择功能
|
||
struct AvatarUploader: View {
|
||
@Binding var selectedImage: UIImage?
|
||
let size: CGFloat
|
||
var onUploadComplete: ((Result<UploadResults, Error>) -> Void)?
|
||
|
||
@State private var isImagePickerPresented = false
|
||
|
||
var body: some View {
|
||
Button(action: { isImagePickerPresented = true }) {
|
||
ZStack {
|
||
if let selectedImage = selectedImage {
|
||
Image(uiImage: selectedImage)
|
||
.resizable()
|
||
.scaledToFill()
|
||
.frame(width: size, height: size)
|
||
.clipShape(RoundedRectangle(cornerRadius: size * 0.1))
|
||
} else {
|
||
Color.gray.opacity(0.1)
|
||
.frame(width: size, height: size)
|
||
.overlay(
|
||
SVGImage(svgName: "Avatar")
|
||
.frame(width: size * 0.8, height: size * 0.8)
|
||
)
|
||
.clipShape(RoundedRectangle(cornerRadius: size * 0.1))
|
||
.overlay(
|
||
RoundedRectangle(cornerRadius: size * 0.1)
|
||
.stroke(Color.gray.opacity(0.3), lineWidth: 1)
|
||
)
|
||
}
|
||
}
|
||
.frame(width: size, height: size)
|
||
.contentShape(Rectangle())
|
||
}
|
||
.buttonStyle(PlainButtonStyle())
|
||
.sheet(isPresented: $isImagePickerPresented) {
|
||
PhotoPicker(
|
||
selectedImages: Binding(
|
||
get: { [selectedImage].compactMap { $0 } },
|
||
set: { images in
|
||
selectedImage = images.first
|
||
}
|
||
),
|
||
selectionLimit: 1,
|
||
onImageUploaded: { result in
|
||
onUploadComplete?(result)
|
||
},
|
||
onUploadProgress: { progress in
|
||
print("上传进度:\(progress.current)/\(progress.total),进度:\(progress.progress * 100)%")
|
||
}
|
||
)
|
||
}
|
||
}
|
||
} |