import SwiftUI import UIKit /// 带缓存、并发控制、图片解密、双指选择效果和分享功能的异步图片视图 struct AsyncImageView: View { let url: String let nums: [Int] let index: Int @State private var image: UIImage? @State private var isLoading = false @State private var error: Error? @State private var isSelecting = false // 是否处于选择状态 @State private var pressStartTime: Date? @State private var longPressActive = false @State private var showShareSheet = false // 控制分享面板显示 private static var currentlyLoadingIndices: Set = [] private static let maxConcurrentLoads = 4 private static let longPressThreshold: TimeInterval = 0.1 // 长按阈值(秒) var body: some View { Group { if let image = image { ZStack(alignment: .center) { // 原始图片 Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) // 选择状态的视觉效果 if isSelecting { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) .colorMultiply(.white) .opacity(0.7) .overlay( RoundedRectangle(cornerRadius: 4) .stroke(Color.blue, lineWidth: 3) // 改为蓝色边框更醒目 ) .blendMode(.lighten) // 分享按钮(仅在选择状态显示) VStack { Spacer() Button(action: { showShareSheet = true }) { Image(systemName: "square.and.arrow.up") .foregroundColor(.white) .padding() .background(Color.blue) .clipShape(Circle()) .shadow(radius: 4) } .padding() } } } // 双指手势识别 .gesture( MagnificationGesture(minimumScaleDelta: 0) .onChanged { _ in handleTwoFingerGesture() } .onEnded { _ in // 只有长按超过阈值才保持选择状态 let shouldKeepSelection = pressStartTime.map { Date().timeIntervalSince($0) >= Self.longPressThreshold } ?? false withAnimation(.easeOut) { isSelecting = shouldKeepSelection } if !shouldKeepSelection { pressStartTime = nil longPressActive = false } } ) // 分享视图 .sheet(isPresented: $showShareSheet) { ShareSheet(activityItems: [image]) { completed in if completed { withAnimation(.easeOut) { isSelecting = false } pressStartTime = nil longPressActive = false } } } } else if isLoading { ProgressView() .frame(height: 200) } else if error != nil { VStack(spacing: 8) { Image(systemName: "exclamationmark.triangle") .foregroundColor(.red) Text(error?.localizedDescription ?? "未知错误") .font(.caption) .foregroundColor(.gray) } .frame(height: 200) } else { Color.gray.opacity(0.3) .frame(height: 200) } } .onAppear { loadImage() } .onDisappear { Self.currentlyLoadingIndices.remove(index) } } // 处理双指手势 private func handleTwoFingerGesture() { if pressStartTime == nil { pressStartTime = Date() } else { // 检查是否超过长按阈值 let pressDuration = Date().timeIntervalSince(pressStartTime!) if pressDuration >= Self.longPressThreshold && !longPressActive { longPressActive = true // 显示选择效果 withAnimation(.easeIn) { isSelecting = true } } } } // 图片加载核心逻辑 private func loadImage() { let cacheKey = extractCacheKey(from: url) // 1. 先检查缓存,如果有直接使用 if let cachedImage = ImageCacheManager.shared.getImage(for: cacheKey) { image = cachedImage print("从缓存加载 - 键: \(cacheKey)") return } // 2. 检查并发加载限制 guard !Self.currentlyLoadingIndices.contains(index), Self.currentlyLoadingIndices.count < Self.maxConcurrentLoads else { return } // 3. 开始加载流程 Self.currentlyLoadingIndices.insert(index) isLoading = true error = nil // 4. 验证解密参数 guard let decodeNum = nums[safe: index] else { error = NSError(domain: "数据错误", code: 1, userInfo: [NSLocalizedDescriptionKey: "图片参数不匹配"]) isLoading = false Self.currentlyLoadingIndices.remove(index) return } print("调试信息:加载图片 - URL: \(url), 缓存键: \(cacheKey), 解密参数: \(decodeNum), 索引: \(index)") // 5. 验证URL有效性 guard let imageUrl = URL(string: url) else { error = NSError(domain: "URL错误", code: 2, userInfo: [NSLocalizedDescriptionKey: "无效的图片URL"]) isLoading = false Self.currentlyLoadingIndices.remove(index) return } // 6. 网络请求加载图片 URLSession.shared.dataTask(with: imageUrl) { data, response, error in DispatchQueue.main.async { // 清理加载状态 Self.currentlyLoadingIndices.remove(index) self.isLoading = false // 处理网络错误 if let error = error { self.error = error print("图片加载失败(网络错误): \(error.localizedDescription)") return } // 验证响应和数据 guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode), let data = data, !data.isEmpty else { self.error = NSError(domain: "响应错误", code: 3, userInfo: [NSLocalizedDescriptionKey: "无效的服务器响应"]) print("图片加载失败(无效响应)") return } // 解码图片并应用解密 guard let loadedImage = UIImage(data: data) else { self.error = NSError(domain: "解码错误", code: 4, userInfo: [NSLocalizedDescriptionKey: "无法解析图片数据"]) print("图片加载失败(数据无效)") return } // 解密并缓存图片(使用简化键) let decodedImage = loadedImage.decodeImage(num: decodeNum) self.image = decodedImage ImageCacheManager.shared.saveImage(decodedImage, for: cacheKey) print("图片加载成功(已缓存) - 键: \(cacheKey)") } }.resume() } /// 从URL中提取简化的缓存键 private func extractCacheKey(from urlString: String) -> String { guard let url = URL(string: urlString) else { return urlString.hashValue.description } let pathComponents = url.pathComponents if let photosIndex = pathComponents.firstIndex(of: "photos"), photosIndex + 1 < pathComponents.count, photosIndex + 2 < pathComponents.count { let idComponent = pathComponents[photosIndex + 1] var fileComponent = pathComponents[photosIndex + 2] if let dotIndex = fileComponent.lastIndex(of: ".") { fileComponent = String(fileComponent[.. Void func makeUIViewController(context: Context) -> UIActivityViewController { let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) controller.completionWithItemsHandler = { (activityType, completed, returnedItems, error) in completion(completed) } return controller } func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} } // 数组安全访问扩展 extension Array { subscript(safe index: Int) -> Element? { return indices.contains(index) ? self[index] : nil } }