wake-ios/wake/View/Examples/ImageUploadExampleView.swift
2025-08-20 14:11:16 +08:00

284 lines
9.8 KiB
Swift

import SwiftUI
import os.log
///
/// 使 MultiImageUploader
@available(iOS 16.0, *)
struct MultiImageUploadExampleView: View {
// MARK: -
@State private var uploadResults: [UploadResult] = []
@State private var isShowingUploader = false
@State private var showUploadAlert = false
@State private var alertMessage = ""
@State private var isUploading = false
private let logger = Logger(subsystem: "com.yourapp.uploader", category: "MultiImageUploadExample")
// MARK: -
var body: some View {
NavigationView {
VStack(spacing: 16) {
//
uploadButton
//
if !uploadResults.isEmpty {
uploadStatsView
}
//
uploadProgressList
Spacer()
}
.navigationTitle("多图上传示例")
.toolbar {
//
if !uploadResults.isEmpty && !isUploading {
Button("清空") {
withAnimation {
uploadResults.removeAll()
}
}
}
}
.sheet(isPresented: $isShowingUploader) {
//
MultiImageUploader(
maxSelection: 10
) { results in
processUploadResults(results)
}
}
.alert("上传结果", isPresented: $showUploadAlert) {
Button("确定", role: .cancel) { }
} message: {
Text(alertMessage)
}
}
}
// MARK: -
///
private var uploadButton: some View {
Button(action: { isShowingUploader = true }) {
Label("选择并上传图片", systemImage: "photo.on.rectangle.angled")
.font(.headline)
.foregroundColor(.white)
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(10)
}
.padding(.horizontal)
.padding(.top)
}
///
private var uploadStatsView: some View {
VStack(alignment: .leading, spacing: 8) {
let successCount = uploadResults.filter { $0.status == .success }.count
let inProgressCount = uploadResults.filter {
if case .uploading = $0.status { return true }
return false
}.count
let failedCount = uploadResults.count - successCount - inProgressCount
Text("上传统计")
.font(.headline)
HStack {
StatView(
value: "\(uploadResults.count)",
label: "总数量",
color: .blue
)
StatView(
value: "\(successCount)",
label: "成功",
color: .green
)
StatView(
value: "\(inProgressCount)",
label: "上传中",
color: .orange
)
StatView(
value: "\(failedCount)",
label: "失败",
color: .red
)
}
//
if inProgressCount > 0 {
let progress = uploadResults.reduce(0.0) { result, uploadResult in
if case .uploading(let progress) = uploadResult.status {
return result + progress
} else if uploadResult.status == .success {
return result + 1.0
}
return result
} / Double(uploadResults.count)
VStack(alignment: .leading, spacing: 4) {
Text("总进度: \(Int(progress * 100))%")
.font(.subheadline)
ProgressView(value: progress)
.progressViewStyle(LinearProgressViewStyle(tint: .blue))
}
.padding(.top, 8)
}
}
.padding()
.background(Color(.systemGray6))
.cornerRadius(10)
.padding(.horizontal)
}
///
private var uploadProgressList: some View {
List {
ForEach($uploadResults) { $result in
VStack(alignment: .leading, spacing: 8) {
//
HStack {
//
Image(uiImage: result.image)
.resizable()
.scaledToFill()
.frame(width: 60, height: 60)
.clipped()
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(borderColor(for: result.status), lineWidth: 1)
)
//
VStack(alignment: .leading, spacing: 4) {
Text("图片 \(result.id.uuidString.prefix(8))...")
.font(.subheadline)
switch result.status {
case .uploading(let progress):
VStack(alignment: .leading, spacing: 4) {
Text("上传中: \(Int(progress * 100))%")
.font(.caption)
ProgressView(value: progress)
.progressViewStyle(LinearProgressViewStyle())
}
case .success:
HStack {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(.green)
Text("上传成功")
.font(.caption)
.foregroundColor(.green)
}
case .failure(let error):
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.foregroundColor(.red)
Text("上传失败: \(error.localizedDescription)")
.font(.caption)
.foregroundColor(.red)
}
case .idle:
Text("等待上传...")
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
}
.padding(.vertical, 4)
}
.padding(.vertical, 8)
.listRowSeparator(.hidden)
}
}
.listStyle(PlainListStyle())
.animation(.easeInOut, value: uploadResults)
}
// MARK: -
///
private func borderColor(for status: UploadStatus) -> Color {
switch status {
case .success: return .green
case .failure: return .red
case .uploading: return .blue
case .idle: return .gray
}
}
///
private func processUploadResults(_ results: [UploadResult]) {
//
isUploading = results.contains { result in
if case .uploading = result.status { return true }
return false
}
//
withAnimation {
uploadResults = results
isShowingUploader = false
}
//
let allFinished = !results.contains { result in
if case .uploading = result.status { return true }
if case .idle = result.status { return true }
return false
}
if allFinished {
let successCount = results.filter { $0.status == .success }.count
let totalCount = results.count
alertMessage = "上传完成\n成功: \(successCount)/\(totalCount)"
showUploadAlert = true
isUploading = false
logger.info("上传完成,成功: \(successCount)/\(totalCount)")
}
}
}
// MARK: -
///
private struct StatView: View {
let value: String
let label: String
let color: Color
var body: some View {
VStack {
Text(value)
.font(.title3.bold())
.foregroundColor(color)
Text(label)
.font(.caption)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity)
}
}
// MARK: -
@available(iOS 16.0, *)
struct MultiImageUploadExampleView_Previews: PreviewProvider {
static var previews: some View {
MultiImageUploadExampleView()
}
}