feat: 预览图片细节

This commit is contained in:
jinyaqiu 2025-08-25 20:10:53 +08:00
parent 2b5ab92068
commit 9a432f65ac

View File

@ -14,6 +14,7 @@ struct MediaUploadView: View {
@State private var selectedMedia: MediaType? = nil
///
@State private var selectedIndices: Set<Int> = []
@State private var mediaPickerSelection: [MediaType] = [] //
// MARK: -
@ -48,9 +49,6 @@ struct MediaUploadView: View {
//
mediaPickerView
}
.onChange(of: uploadManager.selectedMedia) { [oldMedia = uploadManager.selectedMedia] newMedia in
handleMediaChange(newMedia, oldMedia: oldMedia)
}
}
// MARK: -
@ -132,14 +130,51 @@ struct MediaUploadView: View {
///
private var mediaPickerView: some View {
MediaPicker(
selectedMedia: $uploadManager.selectedMedia,
imageSelectionLimit: 20,
videoSelectionLimit: 5,
selectedMedia: Binding(
get: { mediaPickerSelection },
set: { newSelections in
//
let newItems = newSelections.filter { newItem in
!uploadManager.selectedMedia.contains { $0.id == newItem.id }
}
if !newItems.isEmpty {
//
let newMedia = newItems + uploadManager.selectedMedia
uploadManager.selectedMedia = newMedia
//
if selectedIndices.isEmpty {
selectedIndices = [0] //
selectedMedia = newItems.first
}
//
uploadManager.startUpload()
}
//
mediaPickerSelection = []
}
),
imageSelectionLimit: max(0, 20 - uploadManager.selectedMedia.filter {
if case .image = $0 { return true }
return false
}.count),
videoSelectionLimit: max(0, 5 - uploadManager.selectedMedia.filter {
if case .video = $0 { return true }
return false
}.count),
selectionMode: .multiple,
onDismiss: handleMediaPickerDismiss,
onUploadProgress: { index, progress in
print("文件 \(index) 上传进度: \(progress * 100)%")
}
)
.onAppear {
//
mediaPickerSelection = []
}
}
// MARK: -
@ -151,7 +186,7 @@ struct MediaUploadView: View {
//
if !uploadManager.selectedMedia.isEmpty {
uploadManager.startUpload()
// handleMediaChange
}
}
@ -169,9 +204,7 @@ struct MediaUploadView: View {
}
// 线
DispatchQueue.global(qos: .userInitiated).async {
let startTime = Date()
DispatchQueue.global(qos: .userInitiated).async { [self] in
// newMediaoldMedia
let newItems = newMedia.filter { newItem in
!oldMedia.contains { $0.id == newItem.id }
@ -183,28 +216,26 @@ struct MediaUploadView: View {
if !newItems.isEmpty {
print("准备添加\(newItems.count)个新项...")
// 线UI
DispatchQueue.main.async {
// 线UI
DispatchQueue.main.async { [self] in
//
var updatedMedia = uploadManager.selectedMedia
updatedMedia.append(contentsOf: newItems)
//
uploadManager.selectedMedia = updatedMedia
//
if self.selectedIndices.isEmpty && !newItems.isEmpty {
self.selectedIndices = [self.uploadManager.selectedMedia.count] //
self.selectedMedia = newItems.first
if selectedIndices.isEmpty && !newItems.isEmpty {
selectedIndices = [oldMedia.count] //
selectedMedia = newItems.first
}
//
self.uploadManager.startUpload()
print("媒体添加完成,总数量: \(self.uploadManager.selectedMedia.count)")
}
} else if newMedia.isEmpty {
//
DispatchQueue.main.async {
self.selectedIndices = []
self.selectedMedia = nil
print("媒体已清空,重置选择状态")
uploadManager.startUpload()
print("媒体添加完成,总数量: \(uploadManager.selectedMedia.count)")
}
}
print("媒体变化处理完成,总耗时: \(String(format: "%.3f", Date().timeIntervalSince(startTime)))s")
}
}
@ -264,11 +295,6 @@ struct MainUploadArea: View {
Group {
if !uploadManager.selectedMedia.isEmpty {
VStack(spacing: 8) {
//
Text("已选择 \(uploadManager.selectedMedia.count) 个文件")
.font(.subheadline)
.foregroundColor(.gray)
//
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 10) {
@ -295,22 +321,86 @@ struct MainUploadArea: View {
/// - index:
/// - Returns:
private func mediaItemView(for media: MediaType, at index: Int) -> some View {
VStack(spacing: 4) {
//
MediaPreview(media: media, uploadManager: uploadManager)
.frame(width: 80, height: 80)
.cornerRadius(8)
.shadow(radius: 1)
.onTapGesture {
//
selectedIndices = [index]
selectedMedia = media
}
ZStack(alignment: .topTrailing) {
VStack(spacing: 4) {
//
MediaPreview(media: media, uploadManager: uploadManager)
.frame(width: 80, height: 80)
.cornerRadius(8)
.shadow(radius: 1)
.overlay(
//
ZStack {
Path { path in
let radius: CGFloat = 4
let width: CGFloat = 28
let height: CGFloat = 18
//
path.move(to: CGPoint(x: 0, y: radius))
path.addQuadCurve(to: CGPoint(x: radius, y: 0),
control: CGPoint(x: 0, y: 0))
//
path.addLine(to: CGPoint(x: width, y: 0))
//
path.addLine(to: CGPoint(x: width, y: height - radius))
//
path.addQuadCurve(to: CGPoint(x: width - radius, y: height),
control: CGPoint(x: width, y: height))
//
path.addLine(to: CGPoint(x: 0, y: height))
//
path.closeSubpath()
}
.fill(Color.white.opacity(0.5))
// Text
Text("\(index + 1)")
.font(.system(size: 12, weight: .bold))
.foregroundColor(.black)
.frame(width: 28, height: 18)
.offset(x: 0, y: -1)
}
.frame(width: 28, height: 18)
.offset(x: 0, y: 0)
.padding([.top, .leading], 0),
alignment: .topLeading
)
.onTapGesture {
//
selectedIndices = [index]
selectedMedia = media
}
}
//
uploadStatusView(for: index)
//
Button(action: {
//
uploadManager.selectedMedia.remove(at: index)
//
if selectedIndices.contains(index) {
selectedIndices = []
selectedMedia = nil
}
}) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 20))
.foregroundColor(.white.opacity(0.5))
.background(
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 20, height: 20)
)
}
.offset(x: 6, y: -6) //
}
.padding(4)
.contentShape(Rectangle())
}
///
@ -320,18 +410,18 @@ struct MainUploadArea: View {
private func uploadStatusView(for index: Int) -> some View {
if let status = uploadManager.uploadStatus["\(index)"] {
switch status {
// case .uploading(let progress):
// //
// VStack(alignment: .center, spacing: 2) {
// Text("\(Int(progress * 100))%")
// .font(.caption2)
// .foregroundColor(.gray)
// ProgressView(value: progress, total: 1.0)
// .progressViewStyle(LinearProgressViewStyle())
// .frame(height: 3)
// .tint(Color.themePrimary)
// }
// .frame(width: 60)
case .uploading(let progress):
//
VStack(alignment: .center, spacing: 2) {
Text("\(Int(progress * 100))%")
.font(.caption2)
.foregroundColor(.gray)
ProgressView(value: progress, total: 1.0)
.progressViewStyle(LinearProgressViewStyle())
.frame(height: 3)
.tint(Color.themePrimary)
}
.frame(width: 60)
case .completed:
//
@ -439,6 +529,37 @@ struct MediaPreview: View {
///
private struct ImageCache {
static let shared = NSCache<NSString, UIImage>()
private static var cacheKeys = Set<String>()
static func setImage(_ image: UIImage, forKey key: String) {
shared.setObject(image, forKey: key as NSString)
cacheKeys.insert(key)
}
static func getImage(forKey key: String) -> UIImage? {
return shared.object(forKey: key as NSString)
}
static func clearCache() {
cacheKeys.forEach { key in
shared.removeObject(forKey: key as NSString)
}
cacheKeys.removeAll()
}
}
// MARK: -
init(media: MediaType, uploadManager: MediaUploadManager) {
self.media = media
self.uploadManager = uploadManager
//
let cacheKey = "\(media.id)" as NSString
if let cachedImage = ImageCache.shared.object(forKey: cacheKey) {
self._image = State(initialValue: cachedImage)
self._loadState = State(initialValue: .success(cachedImage))
}
}
// MARK: -
@ -481,11 +602,6 @@ struct MediaPreview: View {
if case .video = media {
playButton
}
// //
// if isUploading || uploadProgress > 0 {
// loadingOverlay
// }
} else if case .failure(let error) = loadState {
//
errorView(error: error)
@ -561,46 +677,6 @@ struct MediaPreview: View {
.cornerRadius(8)
}
///
private var loadingOverlay: some View {
ZStack {
//
Color.black.opacity(0.3)
//
VStack {
Spacer()
//
ZStack {
Circle()
.stroke(
Color.white.opacity(0.3),
lineWidth: 4
)
Circle()
.trim(from: 0.0, to: uploadProgress)
.stroke(
Color.white,
style: StrokeStyle(
lineWidth: 4,
lineCap: .round
)
)
.rotationEffect(.degrees(-90))
Text("\(Int(uploadProgress * 100))%")
.font(.system(size: 12, weight: .bold))
.foregroundColor(.white)
}
.frame(width: 40, height: 40)
.padding(.bottom, 8)
}
}
.cornerRadius(8)
}
// MARK: -
///
@ -608,9 +684,9 @@ struct MediaPreview: View {
let cacheKey = "\(media.id)" as NSString
//
if let cachedImage = ImageCache.shared.object(forKey: cacheKey) {
self.image = cachedImage
self.loadState = .success(cachedImage)
if let cachedImage = ImageCache.getImage(forKey: cacheKey as String) {
image = cachedImage
loadState = .success(cachedImage)
return
}
@ -619,7 +695,7 @@ struct MediaPreview: View {
do {
let imageToCache: UIImage
switch self.media {
switch media {
case .image(let uiImage):
imageToCache = uiImage
@ -635,20 +711,20 @@ struct MediaPreview: View {
}
//
ImageCache.shared.setObject(imageToCache, forKey: cacheKey)
ImageCache.setImage(imageToCache, forKey: cacheKey as String)
// UI
DispatchQueue.main.async {
withAnimation(.easeInOut(duration: 0.2)) {
self.image = imageToCache
self.loadState = .success(imageToCache)
image = imageToCache
loadState = .success(imageToCache)
}
}
} catch {
print("图片加载失败: \(error.localizedDescription)")
DispatchQueue.main.async {
self.loadState = .failure(error)
loadState = .failure(error)
}
}
}