import SwiftUI import QRCode import CoreData import Photos import Combine internal import SwiftImageReadWrite #if canImport(PhotosUI) import PhotosUI #endif // MARK: - 标签类型枚举 enum TabType: String, CaseIterable { case colors = "colors" case dots = "dots" case eyes = "eyes" case logos = "logos" var displayName: String { switch self { case .colors: return "colors".localized case .dots: return "dot_types".localized case .eyes: return "eyes".localized case .logos: return "logo".localized } } var iconName: String { switch self { case .colors: return "paintpalette" case .dots: return "circle.grid.3x3" case .eyes: return "eye" case .logos: return "photo" } } } // MARK: - 自定义二维码样式界面 struct QRCodeStyleView: View { let qrCodeContent: String let qrCodeType: QRCodeType let existingStyleData: QRCodeStyleData? // 可选的现有样式数据 let historyItem: HistoryItem? // 可选的现有历史记录项 @Environment(\.dismiss) private var dismiss @EnvironmentObject var coreDataManager: CoreDataManager @EnvironmentObject var languageManager: LanguageManager // 颜色选择 @State private var selectedForegroundColor: QRCodeColor = .black @State private var selectedBackgroundColor: QRCodeColor = .white // 点类型选择 @State private var selectedDotType: QRCodeDotType = .square // 眼睛类型选择 @State private var selectedEyeType: QRCodeEyeType = .square // Logo选择 @State private var selectedLogo: QRCodeLogo? = nil @State private var customLogoImage: UIImage? = nil @State private var photoLibraryAccessGranted = false @State private var showingImagePicker = false // 加载状态 @State private var isLoading = false @State private var showingSavedView = false // 选中的标签类型 @State private var selectedTabType: TabType = .colors // 创建QRCode文档 private func createQRCodeDocument() -> QRCode.Document { let d = try! QRCode.Document(engine: QRCodeEngineExternal()) // 使用传入的二维码内容 d.utf8String = qrCodeContent // 设置背景色 d.design.backgroundColor(selectedBackgroundColor.cgColor) // 设置眼睛样式 d.design.style.eye = QRCode.FillStyle.Solid(selectedForegroundColor.cgColor) d.design.style.eyeBackground = selectedBackgroundColor.cgColor // 设置点样式 d.design.shape.onPixels = selectedDotType.pixelShape d.design.style.onPixels = QRCode.FillStyle.Solid(selectedForegroundColor.cgColor) d.design.style.onPixelsBackground = selectedBackgroundColor.cgColor d.design.shape.offPixels = selectedDotType.pixelShape d.design.style.offPixels = QRCode.FillStyle.Solid(selectedBackgroundColor.cgColor) d.design.style.offPixelsBackground = selectedBackgroundColor.cgColor // 设置眼睛形状 d.design.shape.eye = selectedEyeType.eyeShape // 如果有选择的Logo,设置Logo if let customLogoImage = customLogoImage, let cgImage = customLogoImage.cgImage { // 使用自定义Logo print("应用自定义Logo,CGImage大小: \(cgImage.width) x \(cgImage.height)") d.logoTemplate = QRCode.LogoTemplate.CircleCenter(image: cgImage, inset: 0) } else if let selectedLogo = selectedLogo, let logoImage = selectedLogo.image, let cgImage = logoImage.cgImage { // 使用预设Logo print("应用预设Logo: \(selectedLogo.displayName)") d.logoTemplate = QRCode.LogoTemplate.CircleCenter(image: cgImage) } else { print("没有设置任何Logo") } return d } var body: some View { VStack(spacing: 0) { // 二维码预览区域 qrCodePreviewSection // 样式选择区域 styleSelectionSection } .navigationTitle("custom_style".localized) .id(languageManager.refreshTrigger) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("save".localized) { saveQRCode() } .id(languageManager.refreshTrigger) .font(.system(size: 16, weight: .semibold)) } } .onAppear { checkPhotoLibraryPermission() initializeExistingStyle() } .sheet(isPresented: $showingImagePicker) { ImagePicker( onImageSelected: { image in customLogoImage = image selectedLogo = nil // 清除预设Logo选择 }, shouldProcessImage: true, targetSize: CGSize(width: 80, height: 80) ) } .background( NavigationLink( destination: QRCodeSavedView( qrCodeImage: generateQRCodeImage(), qrCodeContent: qrCodeContent, qrCodeType: qrCodeType, styleData: createStyleData(), historyItem: historyItem ), isActive: $showingSavedView ) { EmptyView() } ) } // MARK: - 二维码预览区域 private var qrCodePreviewSection: some View { VStack(spacing: 16) { QRCodeDocumentUIView(document: createQRCodeDocument()) .frame(width: 300, height: 300) } .padding() .background(Color(.systemBackground)) } // MARK: - 样式选择区域 private var styleSelectionSection: some View { VStack(spacing: 0) { // 标签类型选择 tabTypeSelection // 内容区域 contentArea } .background(Color(.systemGroupedBackground)) } // MARK: - 标签类型选择 private var tabTypeSelection: some View { HStack(spacing: 0) { ForEach(TabType.allCases, id: \.self) { tabType in Button(action: { selectedTabType = tabType }) { VStack(spacing: 4) { Image(systemName: tabType.iconName) .font(.system(size: 20)) .foregroundColor(selectedTabType == tabType ? .blue : .gray) Text(tabType.displayName) .font(.caption) .foregroundColor(selectedTabType == tabType ? .blue : .gray) } .frame(maxWidth: .infinity) .padding(.vertical, 12) .background( Rectangle() .fill(selectedTabType == tabType ? Color.blue.opacity(0.1) : Color.clear) ) } } } .background(Color(.systemBackground)) .overlay( Rectangle() .frame(height: 1) .foregroundColor(Color(.separator)), alignment: .bottom ) } // MARK: - 内容区域 private var contentArea: some View { Group { switch selectedTabType { case .colors: colorsContent case .dots: dotsContent case .eyes: eyesContent case .logos: logosContent } } .frame(maxHeight: 400) } // MARK: - 颜色内容 private var colorsContent: some View { ScrollView { VStack(spacing: 24) { // 前景色选择 colorSelectionSection( title: "foreground_color".localized, colors: QRCodeColor.foregroundColors, selectedColor: $selectedForegroundColor ) // 背景色选择 colorSelectionSection( title: "background_color".localized, colors: QRCodeColor.backgroundColors, selectedColor: $selectedBackgroundColor ) } .padding() } } // MARK: - 点类型内容 private var dotsContent: some View { ScrollView { VStack(spacing: 16) { LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 16) { ForEach(QRCodeDotType.allCases, id: \.self) { dotType in Button(action: { selectedDotType = dotType }) { VStack(spacing: 8) { if let image = loadImage(named: dotType.thumbnailName) { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 60, height: 60) .background(Color.white) .cornerRadius(12) } else { RoundedRectangle(cornerRadius: 12) .fill(Color.gray.opacity(0.3)) .frame(width: 60, height: 60) .overlay( Text("?") .font(.title2) .foregroundColor(.secondary) ) } Text(dotType.displayName) .font(.caption) .foregroundColor(.primary) .multilineTextAlignment(.center) } .padding(12) .background( RoundedRectangle(cornerRadius: 16) .fill(selectedDotType == dotType ? Color.blue.opacity(0.1) : Color.clear) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(selectedDotType == dotType ? Color.blue : Color.clear, lineWidth: 3) ) ) } } } .padding(.horizontal) } } } // MARK: - 眼睛类型内容 private var eyesContent: some View { ScrollView { VStack(spacing: 16) { Text("select_eye_type".localized) .font(.title2) .fontWeight(.bold) .padding(.top) .id(languageManager.refreshTrigger) LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 16) { ForEach(QRCodeEyeType.allCases, id: \.self) { eyeType in Button(action: { selectedEyeType = eyeType }) { VStack(spacing: 8) { if let image = loadImage(named: eyeType.thumbnailName) { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 60, height: 60) .background(Color.white) .cornerRadius(12) } else { RoundedRectangle(cornerRadius: 12) .fill(Color.gray.opacity(0.3)) .frame(width: 60, height: 60) .overlay( Text("?") .font(.title2) .foregroundColor(.secondary) ) } Text(eyeType.displayName) .font(.caption) .foregroundColor(.primary) .multilineTextAlignment(.center) } .padding(12) .background( RoundedRectangle(cornerRadius: 16) .fill(selectedEyeType == eyeType ? Color.blue.opacity(0.1) : Color.clear) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(selectedEyeType == eyeType ? Color.blue : Color.clear, lineWidth: 3) ) ) } } } .padding(.horizontal) } } } // MARK: - Logo内容 private var logosContent: some View { ScrollView { VStack(spacing: 16) { Text("select_logo".localized) .font(.title2) .fontWeight(.bold) .padding(.top) .id(languageManager.refreshTrigger) LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 16) { // 无Logo选项 Button(action: { selectedLogo = nil customLogoImage = nil }) { VStack(spacing: 8) { RoundedRectangle(cornerRadius: 12) .fill(Color.gray.opacity(0.3)) .frame(width: 60, height: 60) .overlay( Text("none".localized) .font(.title2) .foregroundColor(.secondary) .id(languageManager.refreshTrigger) ) Text("no_logo".localized) .font(.caption) .foregroundColor(.primary) .multilineTextAlignment(.center) .id(languageManager.refreshTrigger) } .padding(12) .background( RoundedRectangle(cornerRadius: 16) .fill(selectedLogo == nil && customLogoImage == nil ? Color.blue.opacity(0.1) : Color.clear) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(selectedLogo == nil && customLogoImage == nil ? Color.blue : Color.clear, lineWidth: 3) ) ) } // 自定义Logo选项 if photoLibraryAccessGranted { Button(action: { showingImagePicker = true }) { VStack(spacing: 8) { if let customLogoImage = customLogoImage { Image(uiImage: customLogoImage) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 60, height: 60) .background(Color.white) .cornerRadius(12) } else { RoundedRectangle(cornerRadius: 12) .fill(Color.blue.opacity(0.2)) .frame(width: 60, height: 60) .overlay( Image(systemName: "photo.badge.plus") .font(.title2) .foregroundColor(.blue) ) } Text("custom".localized) .font(.caption) .foregroundColor(.primary) .multilineTextAlignment(.center) .id(languageManager.refreshTrigger) } .padding(12) .background( RoundedRectangle(cornerRadius: 16) .fill(customLogoImage != nil ? Color.blue.opacity(0.1) : Color.clear) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(customLogoImage != nil ? Color.blue : Color.clear, lineWidth: 3) ) ) } } else { // 权限被拒绝时的处理 Button(action: { // 引导用户到设置页面 if let settingsUrl = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(settingsUrl) } }) { VStack(spacing: 8) { RoundedRectangle(cornerRadius: 12) .fill(Color.red.opacity(0.2)) .frame(width: 60, height: 60) .overlay( Image(systemName: "exclamationmark.triangle") .font(.title2) .foregroundColor(.red) ) Text("permission_required".localized) .font(.caption) .foregroundColor(.red) .multilineTextAlignment(.center) .id(languageManager.refreshTrigger) } .padding(12) .background( RoundedRectangle(cornerRadius: 16) .fill(Color.clear) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(Color.red.opacity(0.3), lineWidth: 1) ) ) } } // Logo选项 ForEach(QRCodeLogo.allCases, id: \.self) { logo in Button(action: { selectedLogo = logo customLogoImage = nil // 清除自定义Logo }) { VStack(spacing: 8) { if let image = loadImage(named: logo.thumbnailName) { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 60, height: 60) .background(Color.white) .cornerRadius(12) } else { RoundedRectangle(cornerRadius: 12) .fill(Color.gray.opacity(0.3)) .frame(width: 60, height: 60) .overlay( Text("?") .font(.title2) .foregroundColor(.secondary) ) } Text(logo.displayName) .font(.caption) .foregroundColor(.primary) .multilineTextAlignment(.center) } .padding(12) .background( RoundedRectangle(cornerRadius: 16) .fill(selectedLogo == logo ? Color.blue.opacity(0.1) : Color.clear) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(selectedLogo == logo ? Color.blue : Color.clear, lineWidth: 3) ) ) } } } .padding(.horizontal) } } } // MARK: - 颜色选择区域 private func colorSelectionSection( title: String, colors: [QRCodeColor], selectedColor: Binding ) -> some View { VStack(alignment: .leading, spacing: 12) { Text(title) .font(.headline) .foregroundColor(.primary) LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 6), spacing: 12) { ForEach(colors, id: \.self) { color in Button(action: { selectedColor.wrappedValue = color }) { RoundedRectangle(cornerRadius: 8) .fill(color.color) .frame(height: 40) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(selectedColor.wrappedValue == color ? Color.blue : Color.clear, lineWidth: 3) ) } } } } } // MARK: - 初始化现有样式 private func initializeExistingStyle() { guard let existingStyle = existingStyleData else { return } // 设置现有样式 if let foregroundColor = QRCodeColor(rawValue: existingStyle.foregroundColor) { selectedForegroundColor = foregroundColor } if let backgroundColor = QRCodeColor(rawValue: existingStyle.backgroundColor) { selectedBackgroundColor = backgroundColor } if let dotType = QRCodeDotType(rawValue: existingStyle.dotType) { selectedDotType = dotType } if let eyeType = QRCodeEyeType(rawValue: existingStyle.eyeType) { selectedEyeType = eyeType } // 设置Logo if existingStyle.hasCustomLogo { // 加载自定义Logo customLogoImage = existingStyle.customLogoImage } else if let logoString = existingStyle.logo, let logo = QRCodeLogo(rawValue: logoString) { selectedLogo = logo } } // MARK: - 保存二维码 private func saveQRCode() { // 只保存到历史记录,不保存到相册 if historyItem != nil { updateExistingHistory() } else { saveToHistory() } // 显示保存成功界面 showingSavedView = true } // MARK: - 生成二维码图片 private func generateQRCodeImage() -> UIImage { do { let imageData = try createQRCodeDocument().pngData(dimension: 600) return UIImage(data: imageData) ?? UIImage() } catch { print("生成二维码图片失败:\(error.localizedDescription)") return UIImage() } } // MARK: - 创建样式数据 private func createStyleData() -> QRCodeStyleData { var logoIdentifier: String? = nil var hasCustomLogo = false var customLogoFileName: String? = nil if let customLogo = customLogoImage { // 自定义Logo:保存到文件系统 let fileName = "custom_\(UUID().uuidString).png" logoIdentifier = "custom_\(UUID().uuidString)" hasCustomLogo = true customLogoFileName = fileName // 保存图片到文件系统 saveCustomLogoToFile(customLogo, fileName: fileName) print("🖼️ 自定义Logo已保存到文件:\(fileName)") } else if let selectedLogo = selectedLogo { // 预设Logo logoIdentifier = selectedLogo.rawValue hasCustomLogo = false print("🏷️ 使用预设Logo:\(selectedLogo.rawValue)") } return QRCodeStyleData( foregroundColor: selectedForegroundColor.rawValue, backgroundColor: selectedBackgroundColor.rawValue, dotType: selectedDotType.rawValue, eyeType: selectedEyeType.rawValue, logo: logoIdentifier, hasCustomLogo: hasCustomLogo, customLogoFileName: customLogoFileName ) } // MARK: - 保存到历史记录 private func saveToHistory() { // 确保在主线程上执行Core Data操作 DispatchQueue.main.async { do { let context = self.coreDataManager.container.viewContext let historyItem = HistoryItem(context: context) historyItem.id = UUID() historyItem.dataType = DataType.qrcode.rawValue historyItem.dataSource = DataSource.created.rawValue historyItem.createdAt = Date() historyItem.isFavorite = false historyItem.qrCodeType = self.qrCodeType.rawValue historyItem.content = self.qrCodeContent print("📝 创建历史记录项:\(self.qrCodeContent)") // 保存二维码样式数据 let styleData = self.createStyleData() print("🎨 样式数据创建成功:\(styleData.styleDescription)") // 将样式数据转换为JSON字符串 do { let jsonData = try JSONEncoder().encode(styleData) let jsonString = String(data: jsonData, encoding: .utf8) ?? "" historyItem.qrCodeStyleData = jsonString print("✅ 样式数据已转换为JSON并设置到历史记录项") print("📄 JSON字符串长度:\(jsonString.count)") } catch { print("❌ 样式数据JSON编码失败:\(error)") } // 验证数据完整性 if let savedJsonString = historyItem.qrCodeStyleData { print("✅ 样式数据验证成功:JSON字符串长度 \(savedJsonString.count)") } else { print("❌ 样式数据验证失败:数据未正确设置") } // 保存到Core Data try context.save() print("✅ 自定义二维码保存成功:\(self.qrCodeContent)") // 强制刷新历史记录 self.coreDataManager.objectWillChange.send() } catch { print("❌ Core Data保存失败:\(error.localizedDescription)") print("❌ 错误详情:\(error)") // 如果是NSError,打印更多信息 if let nsError = error as NSError? { print("❌ 错误域:\(nsError.domain)") print("❌ 错误代码:\(nsError.code)") print("❌ 用户信息:\(nsError.userInfo)") } } } } // MARK: - 更新现有历史记录 private func updateExistingHistory() { guard let existingHistoryItem = historyItem else { return } // 确保在主线程上执行Core Data操作 DispatchQueue.main.async { do { let context = self.coreDataManager.container.viewContext // 保存二维码样式数据 let styleData = self.createStyleData() print("🎨 样式数据更新成功:\(styleData.styleDescription)") // 将样式数据转换为JSON字符串 do { let jsonData = try JSONEncoder().encode(styleData) let jsonString = String(data: jsonData, encoding: .utf8) ?? "" existingHistoryItem.qrCodeStyleData = jsonString print("✅ 样式数据已更新到历史记录项") } catch { print("❌ 样式数据JSON编码失败:\(error)") } // 保存到Core Data try context.save() print("✅ 二维码样式更新成功") // 强制刷新历史记录 self.coreDataManager.objectWillChange.send() } catch { print("❌ Core Data更新失败:\(error.localizedDescription)") print("❌ 错误详情:\(error)") // 如果是NSError,打印更多信息 if let nsError = error as NSError? { print("❌ 错误域:\(nsError.domain)") print("❌ 错误代码:\(nsError.code)") print("❌ 用户信息:\(nsError.userInfo)") } } } } // MARK: - 权限检查 private func checkPhotoLibraryPermission() { let status = PHPhotoLibrary.authorizationStatus() print("相册权限状态: \(status.rawValue)") switch status { case .authorized, .limited: photoLibraryAccessGranted = true print("相册权限已授权") case .denied, .restricted: photoLibraryAccessGranted = false print("相册权限被拒绝") case .notDetermined: print("相册权限未确定,正在请求...") PHPhotoLibrary.requestAuthorization { newStatus in DispatchQueue.main.async { self.photoLibraryAccessGranted = (newStatus == .authorized || newStatus == .limited) print("权限请求结果: \(newStatus.rawValue), 授权状态: \(self.photoLibraryAccessGranted)") } } @unknown default: photoLibraryAccessGranted = false print("相册权限未知状态") } } // MARK: - 保存自定义Logo到文件 private func saveCustomLogoToFile(_ image: UIImage, fileName: String) { // 压缩图片 let maxSize: CGFloat = 200 // 限制图片最大尺寸 let resizedImage = resizeImage(image, to: CGSize(width: maxSize, height: maxSize)) guard let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { print("❌ 无法获取文档目录") return } let customLogosPath = documentsPath.appendingPathComponent("CustomLogos") // 创建CustomLogos目录(如果不存在) do { try FileManager.default.createDirectory(at: customLogosPath, withIntermediateDirectories: true, attributes: nil) } catch { print("❌ 创建CustomLogos目录失败:\(error)") return } // 保存图片 let imagePath = customLogosPath.appendingPathComponent(fileName) if let imageData = resizedImage.pngData() { do { try imageData.write(to: imagePath) print("✅ 自定义Logo保存成功:\(fileName)") } catch { print("❌ 保存自定义Logo失败:\(error)") } } else { print("❌ 转换图片数据失败") } } // MARK: - 图片缩放 private func resizeImage(_ image: UIImage, to size: CGSize) -> UIImage { let renderer = UIGraphicsImageRenderer(size: size) return renderer.image { context in image.draw(in: CGRect(origin: .zero, size: size)) } } // MARK: - 辅助函数 private func loadImage(named name: String) -> UIImage? { // 方法1: 尝试从Bundle中直接加载 if let image = UIImage(named: name) { return image } // 方法2: 尝试从Resources子目录加载 let subdirectories = ["dots", "eyes", "logos"] for subdirectory in subdirectories { if let path = Bundle.main.path(forResource: name, ofType: "png", inDirectory: "Resources/\(subdirectory)") { return UIImage(contentsOfFile: path) } } // 方法3: 尝试从Bundle的Resources目录加载 if Bundle.main.path(forResource: "Resources", ofType: nil) != nil { for subdirectory in subdirectories { if let imagePath = Bundle.main.path(forResource: name, ofType: "png", inDirectory: subdirectory) { return UIImage(contentsOfFile: imagePath) } } } // 方法4: 尝试从Assets.xcassets加载 if let image = UIImage(named: name, in: Bundle.main, with: nil) { return image } // 方法5: 尝试从Bundle根目录加载 if let path = Bundle.main.path(forResource: name, ofType: "png") { return UIImage(contentsOfFile: path) } return nil } } // MARK: - 图片选择器 struct ImagePicker: UIViewControllerRepresentable { let onImageSelected: (UIImage) -> Void let shouldProcessImage: Bool let targetSize: CGSize? init(onImageSelected: @escaping (UIImage) -> Void, shouldProcessImage: Bool = false, targetSize: CGSize? = nil) { self.onImageSelected = onImageSelected self.shouldProcessImage = shouldProcessImage self.targetSize = targetSize } func makeUIViewController(context: Context) -> UIImagePickerController { let picker = UIImagePickerController() picker.delegate = context.coordinator picker.sourceType = .photoLibrary picker.allowsEditing = false return picker } func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { let parent: ImagePicker init(_ parent: ImagePicker) { self.parent = parent } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let image = info[.originalImage] as? UIImage { let finalImage: UIImage if parent.shouldProcessImage, let targetSize = parent.targetSize { // 自动处理图片:截取中心正方形并缩放到指定大小 finalImage = processImageToSquare(image: image, targetSize: targetSize) // 打印最终图片的内存大小 let memorySize = calculateImageMemorySize(image: finalImage) print("📊 最终自定义图片内存大小: \(memorySize)") } else { // 不处理图片,直接使用原图 finalImage = image } parent.onImageSelected(finalImage) } picker.dismiss(animated: true) } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true) } // 处理图片为正方形并缩放到指定大小,同时压缩到5KB以下 private func processImageToSquare(image: UIImage, targetSize: CGSize) -> UIImage { let originalSize = image.size // 计算正方形边长(取较小的边) let squareSize = min(originalSize.width, originalSize.height) // 计算裁剪区域(居中裁剪) let cropX = (originalSize.width - squareSize) / 2 let cropY = (originalSize.height - squareSize) / 2 let cropRect = CGRect(x: cropX, y: cropY, width: squareSize, height: squareSize) // 裁剪图片 guard let cgImage = image.cgImage?.cropping(to: cropRect) else { return image } // 创建新的UIImage let croppedImage = UIImage(cgImage: cgImage) // 缩放到目标大小 let renderer = UIGraphicsImageRenderer(size: targetSize) let scaledImage = renderer.image { context in croppedImage.draw(in: CGRect(origin: .zero, size: targetSize)) } // 压缩图片到5KB以下 return compressImageToTargetSize(scaledImage, targetSizeInKB: 5.0) } // 压缩图片到指定大小以下 private func compressImageToTargetSize(_ image: UIImage, targetSizeInKB: Double) -> UIImage { let targetSizeInBytes = Int64(targetSizeInKB * 1024) // 尝试不同的压缩质量 let compressionQualities: [CGFloat] = [0.8, 0.6, 0.4, 0.2, 0.1, 0.05] for quality in compressionQualities { if let imageData = image.jpegData(compressionQuality: quality) { let dataSize = Int64(imageData.count) if dataSize <= targetSizeInBytes { // 如果压缩后的数据大小符合要求,重新创建UIImage if let compressedImage = UIImage(data: imageData) { print("✅ 图片压缩成功: \(dataSize) bytes (质量: \(quality))") return compressedImage } } } } // 如果JPEG压缩仍然太大,尝试进一步缩小尺寸 print("⚠️ JPEG压缩后仍超过目标大小,尝试缩小尺寸") return compressImageByReducingSize(image, targetSizeInBytes: targetSizeInBytes) } // 通过缩小尺寸来压缩图片 private func compressImageByReducingSize(_ image: UIImage, targetSizeInBytes: Int64) -> UIImage { let originalSize = image.size let originalWidth = originalSize.width let originalHeight = originalSize.height // 计算当前图片的内存大小 let currentMemorySize = Int64(originalWidth * originalHeight * 4) // 假设RGBA格式 // 计算需要的缩放比例 let scaleFactor = sqrt(Double(targetSizeInBytes) / Double(currentMemorySize)) let newWidth = max(originalWidth * CGFloat(scaleFactor), 40) // 最小40像素 let newHeight = max(originalHeight * CGFloat(scaleFactor), 40) let newSize = CGSize(width: newWidth, height: newHeight) // 重新渲染到新尺寸 let renderer = UIGraphicsImageRenderer(size: newSize) let resizedImage = renderer.image { context in image.draw(in: CGRect(origin: .zero, size: newSize)) } // 再次尝试JPEG压缩 if let imageData = resizedImage.jpegData(compressionQuality: 0.3) { let finalSize = Int64(imageData.count) print("✅ 通过缩小尺寸压缩成功: \(finalSize) bytes (新尺寸: \(newWidth) x \(newHeight))") if let finalImage = UIImage(data: imageData) { return finalImage } } // 如果还是太大,返回最小尺寸的图片 print("⚠️ 无法压缩到目标大小,返回最小尺寸图片") let minSize = CGSize(width: 40, height: 40) let rendererMin = UIGraphicsImageRenderer(size: minSize) return rendererMin.image { context in image.draw(in: CGRect(origin: .zero, size: minSize)) } } // 计算图片的内存大小 private func calculateImageMemorySize(image: UIImage) -> String { guard let cgImage = image.cgImage else { return "无法计算" } let width = cgImage.width let height = cgImage.height let bitsPerComponent = cgImage.bitsPerComponent let bytesPerRow = cgImage.bytesPerRow _ = cgImage.colorSpace // 计算内存大小(字节) let memorySizeInBytes = height * bytesPerRow // 转换为更易读的格式 let formatter = ByteCountFormatter() formatter.allowedUnits = [.useKB, .useMB] formatter.countStyle = .memory let memorySizeString = formatter.string(fromByteCount: Int64(memorySizeInBytes)) // 返回详细信息 return "\(memorySizeString) (\(width) x \(height), \(bitsPerComponent) bits/component, \(bytesPerRow) bytes/row)" } } } // MARK: - 预览 #Preview { QRCodeStyleView(qrCodeContent: "https://www.example.com", qrCodeType: .url, existingStyleData: nil, historyItem: nil) .environmentObject(CoreDataManager.shared) .environmentObject(LanguageManager.shared) }