From f048426f6b0de5f0958d9a80b762eb93d0756e22 Mon Sep 17 00:00:00 2001 From: v504 Date: Thu, 21 Aug 2025 18:29:34 +0800 Subject: [PATCH] Refactor CreateCodeView to enhance barcode input validation and user experience; implement dynamic input handling, visual feedback, and improved layout for barcode type selection and content entry. --- MyQrCode/Models/BarcodeValidator.swift | 275 ++++++++++++++ MyQrCode/Models/HistoryEnums.swift | 13 +- MyQrCode/Views/BarcodeCharacterHintView.swift | 95 +++++ MyQrCode/Views/BarcodePreviewView.swift | 94 +++++ .../Views/BarcodeValidationInfoView.swift | 124 +++++++ MyQrCode/Views/CreateCodeView.swift | 346 +++++++++++++++--- 6 files changed, 893 insertions(+), 54 deletions(-) create mode 100644 MyQrCode/Models/BarcodeValidator.swift create mode 100644 MyQrCode/Views/BarcodeCharacterHintView.swift create mode 100644 MyQrCode/Views/BarcodePreviewView.swift create mode 100644 MyQrCode/Views/BarcodeValidationInfoView.swift diff --git a/MyQrCode/Models/BarcodeValidator.swift b/MyQrCode/Models/BarcodeValidator.swift new file mode 100644 index 0000000..c7092ad --- /dev/null +++ b/MyQrCode/Models/BarcodeValidator.swift @@ -0,0 +1,275 @@ +import Foundation + +// MARK: - 条形码验证器 +class BarcodeValidator { + + // MARK: - 验证结果 + struct ValidationResult { + let isValid: Bool + let formattedContent: String + let errorMessage: String? + let expectedLength: Int? + let allowedCharacters: String? + } + + // MARK: - 条形码格式验证规则 + static func validateBarcode(_ content: String, type: BarcodeType) -> ValidationResult { + let cleanedContent = content.replacingOccurrences(of: " ", with: "") + + switch type { + case .ean13: + return validateEAN13(cleanedContent) + case .ean8: + return validateEAN8(cleanedContent) + case .upce: + return validateUPCE(cleanedContent) + case .code39: + return validateCode39(cleanedContent) + case .code128: + return validateCode128(cleanedContent) + case .itf14: + return validateITF14(cleanedContent) + case .pdf417: + return validatePDF417(cleanedContent) + } + } + + // MARK: - EAN-13 验证 + private static func validateEAN13(_ content: String) -> ValidationResult { + // EAN-13: 13位数字 + let pattern = "^[0-9]{13}$" + let isValid = content.range(of: pattern, options: .regularExpression) != nil + + if isValid { + return ValidationResult( + isValid: true, + formattedContent: formatEAN13(content), + errorMessage: nil, + expectedLength: 13, + allowedCharacters: "数字 (0-9)" + ) + } else { + return ValidationResult( + isValid: false, + formattedContent: content, + errorMessage: "EAN-13必须是13位数字", + expectedLength: 13, + allowedCharacters: "数字 (0-9)" + ) + } + } + + // MARK: - EAN-8 验证 + private static func validateEAN8(_ content: String) -> ValidationResult { + // EAN-8: 8位数字 + let pattern = "^[0-9]{8}$" + let isValid = content.range(of: pattern, options: .regularExpression) != nil + + if isValid { + return ValidationResult( + isValid: true, + formattedContent: formatEAN8(content), + errorMessage: nil, + expectedLength: 8, + allowedCharacters: "数字 (0-9)" + ) + } else { + return ValidationResult( + isValid: false, + formattedContent: content, + errorMessage: "EAN-8必须是8位数字", + expectedLength: 8, + allowedCharacters: "数字 (0-9)" + ) + } + } + + // MARK: - UPC-E 验证 + private static func validateUPCE(_ content: String) -> ValidationResult { + // UPC-E: 8位数字 + let pattern = "^[0-9]{8}$" + let isValid = content.range(of: pattern, options: .regularExpression) != nil + + if isValid { + return ValidationResult( + isValid: true, + formattedContent: formatUPCE(content), + errorMessage: nil, + expectedLength: 8, + allowedCharacters: "数字 (0-9)" + ) + } else { + return ValidationResult( + isValid: false, + formattedContent: content, + errorMessage: "UPC-E必须是8位数字", + expectedLength: 8, + allowedCharacters: "数字 (0-9)" + ) + } + } + + // MARK: - Code 39 验证 + private static func validateCode39(_ content: String) -> ValidationResult { + // Code 39: 字母、数字、空格和特殊字符 + let pattern = "^[A-Z0-9\\s\\-\\+\\.\\/\\$\\(\\)\\%\\s]+$" + let isValid = content.range(of: pattern, options: .regularExpression) != nil + + if isValid { + return ValidationResult( + isValid: true, + formattedContent: formatCode39(content), + errorMessage: nil, + expectedLength: nil, + allowedCharacters: "字母 (A-Z)、数字 (0-9)、空格、特殊字符 (- + . / $ ( ) %)" + ) + } else { + return ValidationResult( + isValid: false, + formattedContent: content, + errorMessage: "Code 39只能包含字母、数字、空格和特殊字符", + expectedLength: nil, + allowedCharacters: "字母 (A-Z)、数字 (0-9)、空格、特殊字符 (- + . / $ ( ) %)" + ) + } + } + + + + // MARK: - Code 128 验证 + private static func validateCode128(_ content: String) -> ValidationResult { + // Code 128: 支持所有ASCII字符 + let pattern = "^[\\x00-\\x7F]+$" + let isValid = content.range(of: pattern, options: .regularExpression) != nil + + if isValid { + return ValidationResult( + isValid: true, + formattedContent: formatCode128(content), + errorMessage: nil, + expectedLength: nil, + allowedCharacters: "所有ASCII字符 (0-127)" + ) + } else { + return ValidationResult( + isValid: false, + formattedContent: content, + errorMessage: "Code 128只能包含ASCII字符", + expectedLength: nil, + allowedCharacters: "所有ASCII字符 (0-127)" + ) + } + } + + // MARK: - ITF-14 验证 + private static func validateITF14(_ content: String) -> ValidationResult { + // ITF-14: 14位数字 + let pattern = "^[0-9]{14}$" + let isValid = content.range(of: pattern, options: .regularExpression) != nil + + if isValid { + return ValidationResult( + isValid: true, + formattedContent: formatITF14(content), + errorMessage: nil, + expectedLength: 14, + allowedCharacters: "数字 (0-9)" + ) + } else { + return ValidationResult( + isValid: false, + formattedContent: content, + errorMessage: "ITF-14必须是14位数字", + expectedLength: 14, + allowedCharacters: "数字 (0-9)" + ) + } + } + + // MARK: - PDF417 验证 + private static func validatePDF417(_ content: String) -> ValidationResult { + // PDF417: 支持所有ASCII字符 + let pattern = "^[\\x00-\\x7F]+$" + let isValid = content.range(of: pattern, options: .regularExpression) != nil + + if isValid { + return ValidationResult( + isValid: true, + formattedContent: formatPDF417(content), + errorMessage: nil, + expectedLength: nil, + allowedCharacters: "所有ASCII字符 (0-127)" + ) + } else { + return ValidationResult( + isValid: false, + formattedContent: content, + errorMessage: "PDF417只能包含ASCII字符", + expectedLength: nil, + allowedCharacters: "所有ASCII字符 (0-127)" + ) + } + } + + // MARK: - 格式化方法 + private static func formatEAN13(_ content: String) -> String { + // EAN-13: 添加空格分隔符 + let formatted = content.enumerated().map { index, char in + if index == 0 || index == 6 || index == 12 { + return " \(char)" + } + return String(char) + }.joined() + return formatted.trimmingCharacters(in: .whitespaces) + } + + private static func formatEAN8(_ content: String) -> String { + // EAN-8: 添加空格分隔符 + let formatted = content.enumerated().map { index, char in + if index == 0 || index == 4 { + return " \(char)" + } + return String(char) + }.joined() + return formatted.trimmingCharacters(in: .whitespaces) + } + + private static func formatUPCE(_ content: String) -> String { + // UPC-E: 添加空格分隔符 + let formatted = content.enumerated().map { index, char in + if index == 0 || index == 4 { + return " \(char)" + } + return String(char) + }.joined() + return formatted.trimmingCharacters(in: .whitespaces) + } + + private static func formatCode39(_ content: String) -> String { + // Code 39: 保持原样,但去除多余空格 + return content.trimmingCharacters(in: .whitespaces) + } + + + + private static func formatCode128(_ content: String) -> String { + // Code 128: 保持原样 + return content.trimmingCharacters(in: .whitespaces) + } + + private static func formatITF14(_ content: String) -> String { + // ITF-14: 添加空格分隔符 + let formatted = content.enumerated().map { index, char in + if index == 0 || index == 5 || index == 10 { + return " \(char)" + } + return String(char) + }.joined() + return formatted.trimmingCharacters(in: .whitespaces) + } + + private static func formatPDF417(_ content: String) -> String { + // PDF417: 保持原样 + return content.trimmingCharacters(in: .whitespaces) + } +} \ No newline at end of file diff --git a/MyQrCode/Models/HistoryEnums.swift b/MyQrCode/Models/HistoryEnums.swift index 6ca7c07..f486ac5 100644 --- a/MyQrCode/Models/HistoryEnums.swift +++ b/MyQrCode/Models/HistoryEnums.swift @@ -7,11 +7,6 @@ public enum BarcodeType: String, CaseIterable { case ean8 = "EAN-8" case upce = "UPC-E" case code39 = "Code 39" - case code39Checksum = "Code 39 (Checksum)" - case code39FullASCII = "Code 39 (Full ASCII)" - case code39FullASCIIChecksum = "Code 39 (Full ASCII + Checksum)" - case code93 = "Code 93" - case code93i = "Code 93i" case code128 = "Code 128" case itf14 = "ITF-14" case pdf417 = "PDF417" @@ -22,14 +17,10 @@ public enum BarcodeType: String, CaseIterable { var icon: String { switch self { - case .ean13, .ean8, .upce: + case .ean13, .ean8, .upce, .itf14: return "barcode" - case .code39, .code39Checksum, .code39FullASCII, .code39FullASCIIChecksum: + case .code39, .code128: return "barcode.viewfinder" - case .code93, .code93i, .code128: - return "barcode.viewfinder" - case .itf14: - return "barcode" case .pdf417: return "qrcode.viewfinder" } diff --git a/MyQrCode/Views/BarcodeCharacterHintView.swift b/MyQrCode/Views/BarcodeCharacterHintView.swift new file mode 100644 index 0000000..f3e8614 --- /dev/null +++ b/MyQrCode/Views/BarcodeCharacterHintView.swift @@ -0,0 +1,95 @@ +import SwiftUI + +// MARK: - 条形码字符提示组件 +struct BarcodeCharacterHintView: View { + let barcodeType: BarcodeType + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Text("字符类型:") + .font(.caption) + .foregroundColor(.secondary) + + LazyVGrid(columns: [ + GridItem(.adaptive(minimum: 80), spacing: 8) + ], spacing: 6) { + ForEach(getCharacterTypes(), id: \.self) { charType in + HStack(spacing: 4) { + Image(systemName: charType.icon) + .font(.caption2) + .foregroundColor(charType.color) + + Text(charType.name) + .font(.caption2) + .foregroundColor(charType.color) + } + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background( + RoundedRectangle(cornerRadius: 6) + .fill(charType.color.opacity(0.1)) + ) + } + } + } + } + + private func getCharacterTypes() -> [CharacterType] { + switch barcodeType { + case .ean13, .ean8, .upce, .itf14: + return [ + CharacterType(name: "数字", icon: "number", color: .blue) + ] + + case .code39: + return [ + CharacterType(name: "字母", icon: "character", color: .green), + CharacterType(name: "数字", icon: "number", color: .blue), + CharacterType(name: "特殊字符", icon: "textformat", color: .orange) + ] + + case .code128: + return [ + CharacterType(name: "字母", icon: "character", color: .green), + CharacterType(name: "数字", icon: "number", color: .blue), + CharacterType(name: "符号", icon: "textformat", color: .purple), + CharacterType(name: "控制字符", icon: "command", color: .red) + ] + + case .pdf417: + return [ + CharacterType(name: "字母", icon: "character", color: .green), + CharacterType(name: "数字", icon: "number", color: .blue), + CharacterType(name: "符号", icon: "textformat", color: .purple), + CharacterType(name: "所有ASCII", icon: "globe", color: .indigo) + ] + } + } +} + +// MARK: - 字符类型结构 +struct CharacterType: Hashable { + let name: String + let icon: String + let color: Color + + // 实现 Hashable 协议 + func hash(into hasher: inout Hasher) { + hasher.combine(name) + hasher.combine(icon) + } + + static func == (lhs: CharacterType, rhs: CharacterType) -> Bool { + return lhs.name == rhs.name && lhs.icon == rhs.icon + } +} + +#Preview { + VStack(spacing: 20) { + BarcodeCharacterHintView(barcodeType: .ean13) + BarcodeCharacterHintView(barcodeType: .code39) + BarcodeCharacterHintView(barcodeType: .code128) + BarcodeCharacterHintView(barcodeType: .pdf417) + } + .padding() +} \ No newline at end of file diff --git a/MyQrCode/Views/BarcodePreviewView.swift b/MyQrCode/Views/BarcodePreviewView.swift new file mode 100644 index 0000000..20543ef --- /dev/null +++ b/MyQrCode/Views/BarcodePreviewView.swift @@ -0,0 +1,94 @@ +import SwiftUI +import CoreImage +import CoreImage.CIFilterBuiltins + +// MARK: - 条形码预览组件 +struct BarcodePreviewView: View { + let content: String + let barcodeType: BarcodeType + + var body: some View { + Group { + if let barcodeImage = generateBarcodeImage() { + Image(uiImage: barcodeImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: .infinity) + .background(Color.white) + .cornerRadius(8) + .shadow(color: .black.opacity(0.1), radius: 2, x: 0, y: 1) + } else { + // 生成失败时的占位符 + VStack(spacing: 8) { + Image(systemName: "barcode.viewfinder") + .font(.system(size: 32)) + .foregroundColor(.gray) + + Text("无法生成条形码") + .font(.caption) + .foregroundColor(.gray) + + Text("请检查输入内容格式") + .font(.caption2) + .foregroundColor(.gray) + } + .frame(maxWidth: .infinity) + .frame(height: 120) + .background(Color(.systemGray5)) + .cornerRadius(8) + } + } + } + + // MARK: - 生成条形码图片 + private func generateBarcodeImage() -> UIImage? { + guard !content.isEmpty else { return nil } + + let context = CIContext() + let filter: CIFilter? + + // 根据条形码类型选择相应的滤镜 + switch barcodeType { + case .ean13, .ean8, .upce, .itf14: + filter = CIFilter.code128BarcodeGenerator() + filter?.setValue(content.data(using: .utf8), forKey: "inputMessage") + + case .code39: + // Code 39 使用 Code 128 滤镜,因为 Core Image 不直接支持 Code 39 + filter = CIFilter.code128BarcodeGenerator() + filter?.setValue(content.data(using: .utf8), forKey: "inputMessage") + + case .code128: + filter = CIFilter.code128BarcodeGenerator() + filter?.setValue(content.data(using: .utf8), forKey: "inputMessage") + + case .pdf417: + filter = CIFilter.pdf417BarcodeGenerator() + filter?.setValue(content.data(using: .utf8), forKey: "inputMessage") + } + + guard let filter = filter, + let outputImage = filter.outputImage else { return nil } + + // 设置条形码的尺寸和缩放 + let scale = CGAffineTransform(scaleX: 3, y: 3) + let scaledImage = outputImage.transformed(by: scale) + + // 转换为UIImage + guard let cgImage = context.createCGImage(scaledImage, from: scaledImage.extent) else { + return nil + } + + return UIImage(cgImage: cgImage) + } +} + +#Preview { + VStack(spacing: 20) { + BarcodePreviewView(content: "1234567890123", barcodeType: .ean13) + BarcodePreviewView(content: "12345678", barcodeType: .ean8) + BarcodePreviewView(content: "CODE39", barcodeType: .code39) + BarcodePreviewView(content: "123456789012345678901234567890", barcodeType: .pdf417) + } + .padding() +} \ No newline at end of file diff --git a/MyQrCode/Views/BarcodeValidationInfoView.swift b/MyQrCode/Views/BarcodeValidationInfoView.swift new file mode 100644 index 0000000..183d94d --- /dev/null +++ b/MyQrCode/Views/BarcodeValidationInfoView.swift @@ -0,0 +1,124 @@ +import SwiftUI + +// MARK: - 条形码验证信息显示组件 +struct BarcodeValidationInfoView: View { + let validationResult: BarcodeValidator.ValidationResult? + let barcodeType: BarcodeType + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + if let result = validationResult { + // 验证状态指示器 + HStack(spacing: 8) { + Image(systemName: result.isValid ? "checkmark.circle.fill" : "xmark.circle.fill") + .foregroundColor(result.isValid ? .green : .red) + + Text(result.isValid ? "格式正确" : "格式错误") + .font(.caption) + .foregroundColor(result.isValid ? .green : .red) + .fontWeight(.medium) + + Spacer() + } + + // 错误信息 + if let errorMessage = result.errorMessage { + Text(errorMessage) + .font(.caption) + .foregroundColor(.red) + .padding(.leading, 24) + } + + // 格式要求信息 + VStack(alignment: .leading, spacing: 4) { + if let expectedLength = result.expectedLength { + HStack { + Image(systemName: "number") + .foregroundColor(.blue) + .font(.caption) + Text("长度要求: \(expectedLength) 位") + .font(.caption) + .foregroundColor(.blue) + } + } + + if let allowedCharacters = result.allowedCharacters { + HStack { + Image(systemName: "character") + .foregroundColor(.blue) + .font(.caption) + Text("允许字符: \(allowedCharacters)") + .font(.caption) + .foregroundColor(.blue) + } + } + } + .padding(.leading, 24) + + // 格式化后的内容 + if result.isValid && result.formattedContent != "" { + HStack { + Image(systemName: "textformat") + .foregroundColor(.green) + .font(.caption) + Text("格式化: \(result.formattedContent)") + .font(.caption) + .foregroundColor(.green) + .fontWeight(.medium) + } + .padding(.leading, 24) + } + } else { + // 默认提示信息 + HStack { + Image(systemName: "info.circle") + .foregroundColor(.blue) + .font(.caption) + Text("请输入符合 \(barcodeType.displayName) 格式的内容") + .font(.caption) + .foregroundColor(.blue) + } + } + } + .padding(12) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color(.systemGray6)) + ) + } +} + +#Preview { + VStack(spacing: 20) { + // 有效格式示例 + BarcodeValidationInfoView( + validationResult: BarcodeValidator.ValidationResult( + isValid: true, + formattedContent: "123 4567 8901 2", + errorMessage: nil, + expectedLength: 13, + allowedCharacters: "数字 (0-9)" + ), + barcodeType: .ean13 + ) + + // 无效格式示例 + BarcodeValidationInfoView( + validationResult: BarcodeValidator.ValidationResult( + isValid: false, + formattedContent: "12345", + errorMessage: "EAN-13必须是13位数字", + expectedLength: 13, + allowedCharacters: "数字 (0-9)" + ), + barcodeType: .ean13 + ) + + // 默认状态示例 + BarcodeValidationInfoView( + validationResult: nil, + barcodeType: .code39 + ) + } + .padding() +} \ No newline at end of file diff --git a/MyQrCode/Views/CreateCodeView.swift b/MyQrCode/Views/CreateCodeView.swift index c77784c..0b8c5e2 100644 --- a/MyQrCode/Views/CreateCodeView.swift +++ b/MyQrCode/Views/CreateCodeView.swift @@ -12,21 +12,53 @@ struct CreateCodeView: View { @State private var showingAlert = false @State private var alertMessage = "" + // 条形码验证相关状态 + @State private var validationResult: BarcodeValidator.ValidationResult? + @State private var showValidationInfo = false + + // 输入焦点,确保进入页面自动弹出键盘 + @FocusState private var isContentFieldFocused: Bool + var body: some View { - NavigationView { - Form { + Form { // 数据类型选择 Section("数据类型") { - Picker("数据类型", selection: $selectedDataType) { + HStack(spacing: 0) { ForEach(DataType.allCases, id: \.self) { type in - HStack { - Image(systemName: type.icon) - Text(type.displayName) + Button(action: { + selectedDataType = type + isContentFieldFocused = true + // 添加触觉反馈 + let impactFeedback = UIImpactFeedbackGenerator(style: .light) + impactFeedback.impactOccurred() + }) { + HStack(spacing: 6) { + Image(systemName: type.icon) + .font(.system(size: 16, weight: .medium)) + .foregroundColor(selectedDataType == type ? .white : .primary) + + Text(type.displayName) + .font(.system(size: 15, weight: .medium)) + .foregroundColor(selectedDataType == type ? .white : .primary) + } + .frame(maxWidth: .infinity) + .padding(.vertical, 12) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(selectedDataType == type ? Color.blue : Color(.systemGray6)) + ) } - .tag(type) + .buttonStyle(PlainButtonStyle()) } } - .pickerStyle(SegmentedPickerStyle()) + .background( + RoundedRectangle(cornerRadius: 8) + .fill(Color(.systemGray6)) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(Color(.systemGray4), lineWidth: 0.5) + ) } // 具体类型选择 @@ -60,14 +92,101 @@ struct CreateCodeView: View { // 内容输入 Section("内容") { - TextField("请输入内容", text: $content) - .frame(minHeight: 80) + VStack(alignment: .leading, spacing: 12) { + // 输入框和计数布局 + VStack(alignment: .trailing, spacing: 4) { + // 条形码格式提示 + if selectedDataType == .barcode { + HStack { + Image(systemName: "info.circle") + .font(.caption) + .foregroundColor(.blue) + + Text(getBarcodeFormatHint()) + .font(.caption) + .foregroundColor(.secondary) + .lineLimit(2) + + Spacer() + } + .padding(.horizontal, 4) + .padding(.vertical, 6) + .background( + RoundedRectangle(cornerRadius: 6) + .fill(Color.blue.opacity(0.1)) + ) + } + + TextField(getPlaceholderText(), text: $content) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .keyboardType(getKeyboardType()) + .textInputAutocapitalization(getAutocapitalization()) + .disableAutocorrection(true) + .focused($isContentFieldFocused) + .toolbar { + ToolbarItemGroup(placement: .keyboard) { + Spacer() + Button("完成") { + isContentFieldFocused = false + } + .foregroundColor(.blue) + .font(.system(size: 16, weight: .medium)) + } + } + .foregroundColor(.black) + .onChange(of: content) { newValue in + // 针对有固定长度与数字要求的类型,进行输入规范化(仅保留数字并按最大长度截断) + if selectedDataType == .barcode, let maxLen = getMaxLength(), requiresDigitsOnly() { + let digits = newValue.filter { $0.isNumber } + let truncated = String(digits.prefix(maxLen)) + if truncated != newValue { + content = truncated + // 播放输入提示音 + playInputSound() + } + } else if selectedDataType == .barcode { + // 播放输入提示音 + playInputSound() + } + validateBarcodeContent(content) + } + + // 计数显示在输入框下方,右对齐 + if let maxLen = getMaxLength(), selectedDataType == .barcode { + Text("\(normalizedInputCount())/\(maxLen)") + .font(.caption2) + .foregroundColor(.blue) + .fontWeight(.medium) + } + } + + // 输入统计与验证状态 + HStack { + if let result = validationResult, result.isValid { + Text("✓ 格式正确") + .font(.caption2) + .foregroundColor(.green) + } else if !content.isEmpty { + Text("⚠ 格式检查中...") + .font(.caption2) + .foregroundColor(.orange) + } + } + + // 条形码验证信息(仅在格式不正确时显示) + if selectedDataType == .barcode && !content.isEmpty && (validationResult == nil || !validationResult!.isValid) { + BarcodeValidationInfoView( + validationResult: validationResult, + barcodeType: selectedBarcodeType + ) + } + } } // 预览 if !content.isEmpty { Section("预览") { - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: 12) { HStack { Image(systemName: selectedDataType.icon) Text(selectedDataType.displayName) @@ -91,70 +210,211 @@ struct CreateCodeView: View { } } - Text(content) - .font(.body) - .foregroundColor(.secondary) + if selectedDataType == .barcode && !content.isEmpty && isInputComplete() { + // 条形码图片预览(仅在输入完成时显示) + BarcodePreviewView( + content: content, + barcodeType: selectedBarcodeType + ) + .frame(height: 120) + .frame(maxWidth: .infinity) + } else { + // 二维码或文本预览 + Text(content) + .font(.body) + .foregroundColor(.secondary) + .padding() + .frame(maxWidth: .infinity, alignment: .leading) + } } .padding() .background(Color(.systemGray6)) .cornerRadius(8) } } - } - .navigationTitle("创建\(selectedDataType.displayName)") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .navigationBarLeading) { - Button("取消") { - dismiss() - } - } - - ToolbarItem(placement: .navigationBarTrailing) { - Button("创建") { - createCode() - } + } + .navigationTitle("创建\(selectedDataType.displayName)") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("创建") { createCode() } .disabled(content.isEmpty) - } } - .alert("提示", isPresented: $showingAlert) { - Button("确定") { } - } message: { - Text(alertMessage) + } + .alert("提示", isPresented: $showingAlert) { + Button("确定") { } + } message: { Text(alertMessage) } + .onAppear { + // 稍延迟以确保进入页面时自动聚焦 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + isContentFieldFocused = true } } + .onChange(of: selectedDataType) { _ in + isContentFieldFocused = true + } + .onChange(of: selectedBarcodeType) { _ in + isContentFieldFocused = true + } + .onTapGesture { + // 点击外部关闭键盘 + isContentFieldFocused = false + } + } + + // MARK: - 条形码验证方法 + private func validateBarcodeContent(_ newContent: String) { + guard selectedDataType == .barcode else { return } + validationResult = BarcodeValidator.validateBarcode(newContent, type: selectedBarcodeType) + // 如果验证通过,自动格式化内容 + if let result = validationResult, result.isValid { + content = result.formattedContent + } + } + + // MARK: - 键盘配置方法 + private func getKeyboardType() -> UIKeyboardType { + guard selectedDataType == .barcode else { return .default } + switch selectedBarcodeType { + case .ean13, .ean8, .upce, .itf14: + return .numberPad + case .code39, .code128, .pdf417: + return .asciiCapable + } + } + + private func getAutocapitalization() -> TextInputAutocapitalization { + guard selectedDataType == .barcode else { return .sentences } + switch selectedBarcodeType { + case .ean13, .ean8, .upce, .itf14: + return .never + case .code39, .code128, .pdf417: + return .characters + } + } + + // MARK: - 计数/规则 + private func getMaxLength() -> Int? { + guard selectedDataType == .barcode else { return nil } + switch selectedBarcodeType { + case .ean13: return 13 + case .ean8: return 8 + case .upce: return 8 + case .itf14: return 14 + default: return nil + } + } + + private func requiresDigitsOnly() -> Bool { + guard selectedDataType == .barcode else { return false } + switch selectedBarcodeType { + case .ean13, .ean8, .upce, .itf14: + return true + default: + return false + } + } + + private func normalizedInputCount() -> Int { + if requiresDigitsOnly() { + return content.filter { $0.isNumber }.count + } + return content.count + } + + // MARK: - 条形码格式提示 + private func getBarcodeFormatHint() -> String { + switch selectedBarcodeType { + case .ean13: + return "请输入13位数字,如:1234567890123" + case .ean8: + return "请输入8位数字,如:12345678" + case .upce: + return "请输入8位数字,如:12345678" + case .code39: + return "请输入字母、数字、空格和特殊字符" + case .code128: + return "请输入任意ASCII字符" + case .itf14: + return "请输入14位数字,如:12345678901234" + case .pdf417: + return "请输入任意ASCII字符" + } + } + + // MARK: - 输入框占位符文本 + private func getPlaceholderText() -> String { + if selectedDataType == .barcode { + switch selectedBarcodeType { + case .ean13: + return "输入13位数字" + case .ean8: + return "输入8位数字" + case .upce: + return "输入8位数字" + case .code39: + return "输入字母、数字等" + case .code128: + return "输入任意字符" + case .itf14: + return "输入14位数字" + case .pdf417: + return "输入任意字符" + } + } else { + return "请输入内容" + } + } + + // MARK: - 播放输入提示音 + private func playInputSound() { + // 使用系统触觉反馈作为输入提示音 + let impactFeedback = UIImpactFeedbackGenerator(style: .light) + impactFeedback.impactOccurred() + } + + // MARK: - 判断输入是否完成 + private func isInputComplete() -> Bool { + if selectedDataType == .barcode { + // 条形码需要验证格式正确 + return validationResult?.isValid == true + } else { + // 二维码只要有内容就算完成 + return !content.isEmpty + } } private func createCode() { guard !content.isEmpty else { return } - + // 如果是条形码,验证格式 + if selectedDataType == .barcode { + let validation = BarcodeValidator.validateBarcode(content, type: selectedBarcodeType) + if !validation.isValid { + alertMessage = validation.errorMessage ?? "条形码格式不正确" + showingAlert = true + return + } + } let context = coreDataManager.container.viewContext let historyItem = HistoryItem(context: context) - historyItem.id = UUID() historyItem.content = content historyItem.dataType = selectedDataType.rawValue historyItem.dataSource = DataSource.created.rawValue historyItem.createdAt = Date() historyItem.isFavorite = false - if selectedDataType == .barcode { historyItem.barcodeType = selectedBarcodeType.rawValue } else { historyItem.qrCodeType = selectedQRCodeType.rawValue } - coreDataManager.addHistoryItem(historyItem) alertMessage = "\(selectedDataType.displayName)创建成功!" showingAlert = true - - // 延迟关闭 - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - dismiss() - } + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { dismiss() } } } #Preview { CreateCodeView() -} \ No newline at end of file +}