Refactor CreateCodeView to streamline data type selection and input handling; replace direct state management with parameters for improved modularity and user experience. Update navigation links in HistoryView for consistent code type selection flow.

main
v504 1 year ago
parent f048426f6b
commit 29ee4ba2f2

@ -39,7 +39,7 @@ struct ContentView: View {
// 功能卡片布局
VStack(spacing: 18) {
// 主功能 - 创建二维码(大卡片)
NavigationLink(destination: CreateCodeView()) {
NavigationLink(destination: CodeTypeSelectionView()) {
VStack(alignment: .leading, spacing: 20) {
HStack {
Image(systemName: "qrcode")

@ -0,0 +1,287 @@
import SwiftUI
// MARK: - 内容输入和预览组件
struct CodeContentInputView: View {
let selectedDataType: DataType
let selectedBarcodeType: BarcodeType
let selectedQRCodeType: QRCodeType
@Binding var content: String
@Binding var validationResult: BarcodeValidator.ValidationResult?
@FocusState.Binding var isContentFieldFocused: Bool
var body: some View {
VStack(spacing: 16) {
// 内容输入区域
contentInputSection
// 预览区域
if !content.isEmpty {
previewSection
}
}
}
// MARK: - 内容输入区域
private var contentInputSection: some View {
Section("内容") {
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
handleContentChange(newValue)
}
// 计数显示在输入框下方,右对齐
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
)
}
}
}
}
// MARK: - 预览区域
private var previewSection: some View {
Section("预览") {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: selectedDataType.icon)
Text(selectedDataType.displayName)
Spacer()
if selectedDataType == .barcode {
Text(selectedBarcodeType.displayName)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background(Color.green.opacity(0.1))
.foregroundColor(.green)
.cornerRadius(8)
} else {
Text(selectedQRCodeType.displayName)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background(Color.orange.opacity(0.1))
.foregroundColor(.orange)
.cornerRadius(8)
}
}
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)
}
}
// MARK: - 辅助方法
private func handleContentChange(_ newValue: String) {
// 针对有固定长度与数字要求的类型,进行输入规范化(仅保留数字并按最大长度截断)
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)
}
private func validateBarcodeContent(_ newContent: String) {
guard selectedDataType == .barcode else { return }
validationResult = BarcodeValidator.validateBarcode(newContent, type: selectedBarcodeType)
}
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
}
}
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
}
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字符"
}
}
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 "请输入内容"
}
}
private func playInputSound() {
// 使用系统触觉反馈作为输入提示音
let impactFeedback = UIImpactFeedbackGenerator(style: .light)
impactFeedback.impactOccurred()
}
private func isInputComplete() -> Bool {
if selectedDataType == .barcode {
// 条形码需要验证格式正确
return validationResult?.isValid == true
} else {
// 二维码只要有内容就算完成
return !content.isEmpty
}
}
}

@ -0,0 +1,134 @@
import SwiftUI
// MARK: - 数据类型选择界面
struct CodeTypeSelectionView: View {
@State private var selectedDataType: DataType = .qrcode
@State private var selectedBarcodeType: BarcodeType = .ean13
@State private var selectedQRCodeType: QRCodeType = .text
var body: some View {
VStack(spacing: 30) {
// 数据类型选择
VStack(spacing: 20) {
Text("数据类型")
.font(.headline)
.foregroundColor(.primary)
.frame(maxWidth: .infinity, alignment: .leading)
HStack(spacing: 0) {
ForEach(DataType.allCases, id: \.self) { type in
Button(action: {
selectedDataType = type
// 添加触觉反馈
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))
)
}
.buttonStyle(PlainButtonStyle())
}
}
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color(.systemGray6))
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color(.systemGray4), lineWidth: 0.5)
)
}
.padding(.horizontal, 20)
// 具体类型选择
VStack(spacing: 20) {
if selectedDataType == .barcode {
VStack(spacing: 12) {
Text("条形码类型")
.font(.headline)
.foregroundColor(.primary)
.frame(maxWidth: .infinity, alignment: .leading)
Picker("条形码类型", selection: $selectedBarcodeType) {
ForEach(BarcodeType.allCases, id: \.self) { type in
HStack {
Image(systemName: type.icon)
Text(type.displayName)
}
.tag(type)
}
}
.pickerStyle(WheelPickerStyle())
}
} else {
VStack(spacing: 12) {
Text("二维码类型")
.font(.headline)
.foregroundColor(.primary)
.frame(maxWidth: .infinity, alignment: .leading)
Picker("二维码类型", selection: $selectedQRCodeType) {
ForEach(QRCodeType.allCases, id: \.self) { type in
HStack {
Image(systemName: type.icon)
Text(type.displayName)
}
.tag(type)
}
}
.pickerStyle(WheelPickerStyle())
}
}
}
.padding(.horizontal, 20)
Spacer()
// 下一步按钮
NavigationLink(
destination: CreateCodeView(
selectedDataType: selectedDataType,
selectedBarcodeType: selectedBarcodeType,
selectedQRCodeType: selectedQRCodeType
)
) {
HStack(spacing: 8) {
Text("下一步")
.font(.system(size: 18, weight: .semibold))
.foregroundColor(.white)
Image(systemName: "arrow.right")
.font(.system(size: 16, weight: .medium))
.foregroundColor(.white)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 16)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(Color.blue)
)
}
.padding(.horizontal, 20)
.padding(.bottom, 30)
}
.navigationTitle("选择类型")
.navigationBarTitleDisplayMode(.inline)
}
}
#Preview {
CodeTypeSelectionView()
}

@ -5,233 +5,31 @@ struct CreateCodeView: View {
@Environment(\.dismiss) private var dismiss
@StateObject private var coreDataManager = CoreDataManager.shared
@State private var selectedDataType: DataType = .qrcode
@State private var selectedBarcodeType: BarcodeType = .ean13
@State private var selectedQRCodeType: QRCodeType = .text
// 从类型选择界面传入的参数
let selectedDataType: DataType
let selectedBarcodeType: BarcodeType
let selectedQRCodeType: QRCodeType
@State private var content = ""
@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 {
Form {
// 数据类型选择
Section("数据类型") {
HStack(spacing: 0) {
ForEach(DataType.allCases, id: \.self) { type in
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))
)
}
.buttonStyle(PlainButtonStyle())
}
}
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color(.systemGray6))
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color(.systemGray4), lineWidth: 0.5)
)
}
// 具体类型选择
if selectedDataType == .barcode {
Section("条形码类型") {
Picker("条形码类型", selection: $selectedBarcodeType) {
ForEach(BarcodeType.allCases, id: \.self) { type in
HStack {
Image(systemName: type.icon)
Text(type.displayName)
}
.tag(type)
}
}
.pickerStyle(WheelPickerStyle())
}
} else {
Section("二维码类型") {
Picker("二维码类型", selection: $selectedQRCodeType) {
ForEach(QRCodeType.allCases, id: \.self) { type in
HStack {
Image(systemName: type.icon)
Text(type.displayName)
}
.tag(type)
}
}
.pickerStyle(WheelPickerStyle())
}
}
// 内容输入
Section("内容") {
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: 12) {
HStack {
Image(systemName: selectedDataType.icon)
Text(selectedDataType.displayName)
Spacer()
if selectedDataType == .barcode {
Text(selectedBarcodeType.displayName)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background(Color.green.opacity(0.1))
.foregroundColor(.green)
.cornerRadius(8)
} else {
Text(selectedQRCodeType.displayName)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 2)
.background(Color.orange.opacity(0.1))
.foregroundColor(.orange)
.cornerRadius(8)
}
}
if selectedDataType == .barcode && !content.isEmpty && isInputComplete() {
// 条形码图片预览(仅在输入完成时显示)
BarcodePreviewView(
content: content,
barcodeType: selectedBarcodeType
// 内容输入和预览组件
CodeContentInputView(
selectedDataType: selectedDataType,
selectedBarcodeType: selectedBarcodeType,
selectedQRCodeType: selectedQRCodeType,
content: $content,
validationResult: $validationResult,
isContentFieldFocused: $isContentFieldFocused
)
.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)
@ -250,140 +48,12 @@ struct CreateCodeView: View {
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 }
// 如果是条形码,验证格式
@ -411,10 +81,17 @@ struct CreateCodeView: View {
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()
CreateCodeView(
selectedDataType: .barcode,
selectedBarcodeType: .ean13,
selectedQRCodeType: .text
)
}

@ -5,7 +5,7 @@ struct HistoryView: View {
@StateObject private var coreDataManager = CoreDataManager.shared
@State private var searchText = ""
@State private var selectedFilter: HistoryFilter = .all
@State private var showingCreateSheet = false
@State private var itemToDelete: HistoryItem?
@State private var showingDeleteAlert = false
@State private var showingClearConfirmSheet = false
@ -154,18 +154,14 @@ struct HistoryView: View {
}
}
Button(action: {
showingCreateSheet = true
}) {
NavigationLink(destination: CodeTypeSelectionView()) {
Image(systemName: "plus")
}
}
}
}
}
.sheet(isPresented: $showingCreateSheet) {
CreateCodeView()
}
.sheet(isPresented: $showingClearConfirmSheet) {
ClearHistoryConfirmView(
isPresented: $showingClearConfirmSheet,
@ -378,9 +374,7 @@ struct HistoryView: View {
.foregroundColor(.gray)
.multilineTextAlignment(.center)
Button(action: {
showingCreateSheet = true
}) {
NavigationLink(destination: CodeTypeSelectionView()) {
HStack {
Image(systemName: "plus.circle.fill")
Text("创建第一个记录")

Loading…
Cancel
Save