From 4018bc0563db80217f3951cca9803942e895fc37 Mon Sep 17 00:00:00 2001 From: v504 Date: Fri, 22 Aug 2025 16:58:40 +0800 Subject: [PATCH] Refactor InputComponentFactory to streamline QR code input handling; introduced dedicated configuration structs for various input types, reducing parameter complexity and enhancing code maintainability. Updated CreateQRCodeView to utilize new factory methods for improved modularity and clarity. --- MyQrCode/ScannerView/ScannerView.swift | 15 +- MyQrCode/ScannerView/ScannerViewModel.swift | 3 - .../ScannerView/ScanningOverlayView.swift | 1 - .../Components/InputComponentFactory.swift | 340 ++++++++----- MyQrCode/Views/CreateQRCodeView.swift | 153 ++++-- docs/REFACTOR_README.md | 445 ++++++------------ 6 files changed, 488 insertions(+), 469 deletions(-) diff --git a/MyQrCode/ScannerView/ScannerView.swift b/MyQrCode/ScannerView/ScannerView.swift index f84423e..41c7035 100644 --- a/MyQrCode/ScannerView/ScannerView.swift +++ b/MyQrCode/ScannerView/ScannerView.swift @@ -1,6 +1,5 @@ import SwiftUI import AVFoundation -import AudioToolbox import Combine import CoreData import QRCode @@ -196,10 +195,10 @@ struct ScannerView: View { logInfo("检测到条码数量: \(codes.count)", className: "ScannerView") // 调试信息 - let _ = print("🔍 handleDetectedCodes 被调用:") - let _ = print(" 条码数量: \(codes.count)") - let _ = print(" 条码内容: \(codes.map { "\($0.type): \($0.content)" })") - let _ = print(" 条码来源: \(codes.map { $0.source })") + print("🔍 handleDetectedCodes 被调用:") + print(" 条码数量: \(codes.count)") + print(" 条码内容: \(codes.map { "\($0.type): \($0.content)" })") + print(" 条码来源: \(codes.map { $0.source })") if codes.count == 1 { logInfo("单个条码,显示选择点并0.5秒后自动跳转", className: "ScannerView") @@ -289,9 +288,9 @@ struct ScannerView: View { scannerViewModel.pauseCamera() // 调试信息 - let _ = print("⏸️ pauseForPreview 被调用:") - let _ = print(" showPreviewPause: \(showPreviewPause)") - let _ = print(" detectedCodes.count: \(scannerViewModel.detectedCodes.count)") + print("⏸️ pauseForPreview 被调用:") + print(" showPreviewPause: \(showPreviewPause)") + print(" detectedCodes.count: \(scannerViewModel.detectedCodes.count)") } private func resetToScanning() { diff --git a/MyQrCode/ScannerView/ScannerViewModel.swift b/MyQrCode/ScannerView/ScannerViewModel.swift index cbf1249..afa7ddc 100644 --- a/MyQrCode/ScannerView/ScannerViewModel.swift +++ b/MyQrCode/ScannerView/ScannerViewModel.swift @@ -8,7 +8,6 @@ class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjec @Published var detectedCodes: [DetectedCode] = [] @Published var showAlert = false @Published var cameraAuthorizationStatus: AVAuthorizationStatus = .notDetermined - @Published var showPermissionAlert = false @Published var isTorchOn = false var captureSession: AVCaptureSession! @@ -39,7 +38,6 @@ class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjec case .denied, .restricted: logWarning("❌ 相机权限被拒绝或受限", className: "ScannerViewModel") cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) - showPermissionAlert = true @unknown default: logWarning("❓ 未知的相机权限状态", className: "ScannerViewModel") @@ -59,7 +57,6 @@ class ScannerViewModel: NSObject, ObservableObject, AVCaptureMetadataOutputObjec } else { logWarning("❌ 相机权限请求被拒绝", className: "ScannerViewModel") self?.cameraAuthorizationStatus = .denied - self?.showPermissionAlert = true } } } diff --git a/MyQrCode/ScannerView/ScanningOverlayView.swift b/MyQrCode/ScannerView/ScanningOverlayView.swift index 1036d95..29052c2 100644 --- a/MyQrCode/ScannerView/ScanningOverlayView.swift +++ b/MyQrCode/ScannerView/ScanningOverlayView.swift @@ -1,5 +1,4 @@ import SwiftUI -import AudioToolbox // MARK: - 扫描界面覆盖层 struct ScanningOverlayView: View { diff --git a/MyQrCode/Views/Components/InputComponentFactory.swift b/MyQrCode/Views/Components/InputComponentFactory.swift index 6a1c4a0..4118480 100644 --- a/MyQrCode/Views/Components/InputComponentFactory.swift +++ b/MyQrCode/Views/Components/InputComponentFactory.swift @@ -1,151 +1,253 @@ import SwiftUI +// MARK: - 邮件输入配置 +struct EmailInputConfig { + let emailAddress: Binding + let emailSubject: Binding + let emailBody: Binding + let emailCc: Binding + let emailBcc: Binding +} + +// MARK: - WiFi输入配置 +struct WiFiInputConfig { + let ssid: Binding + let password: Binding + let encryptionType: Binding +} + +// MARK: - 联系人输入配置 +struct ContactInputConfig { + let firstName: Binding + let lastName: Binding + let phone: Binding + let email: Binding + let company: Binding + let title: Binding + let address: Binding + let website: Binding +} + +// MARK: - 位置输入配置 +struct LocationInputConfig { + let latitude: Binding + let longitude: Binding + let locationName: Binding +} + +// MARK: - 日历输入配置 +struct CalendarInputConfig { + let eventTitle: Binding + let eventDescription: Binding + let startDate: Binding + let endDate: Binding + let location: Binding +} + +// MARK: - 社交输入配置 +struct SocialInputConfig { + let username: Binding + let message: Binding +} + +// MARK: - 电话输入配置 +struct PhoneInputConfig { + let phoneNumber: Binding + let phoneMessage: Binding +} + +// MARK: - URL输入配置 +struct URLInputConfig { + let url: Binding +} + +// MARK: - 文本输入配置 +struct TextInputConfig { + let content: Binding +} + // MARK: - 输入组件工厂 struct InputComponentFactory { - // 根据QR码类型返回相应的输入组件 + // 创建邮件输入组件 + static func createEmailInput(with config: EmailInputConfig) -> AnyView { + return AnyView( + EmailInputView( + emailAddress: config.emailAddress, + emailSubject: config.emailSubject, + emailBody: config.emailBody, + emailCc: config.emailCc, + emailBcc: config.emailBcc + ) + ) + } + + // 创建WiFi输入组件 + static func createWiFiInput(with config: WiFiInputConfig) -> AnyView { + return AnyView( + WiFiInputView( + ssid: config.ssid, + password: config.password, + encryptionType: config.encryptionType + ) + ) + } + + // 创建联系人输入组件 + static func createContactInput(with config: ContactInputConfig) -> AnyView { + return AnyView( + ContactInputView( + firstName: config.firstName, + lastName: config.lastName, + phone: config.phone, + email: config.email, + company: config.company, + title: config.title, + address: config.address, + website: config.website + ) + ) + } + + // 创建位置输入组件 + static func createLocationInput(with config: LocationInputConfig) -> AnyView { + return AnyView( + LocationInputView( + latitude: config.latitude, + longitude: config.longitude, + locationName: config.locationName + ) + ) + } + + // 创建日历输入组件 + static func createCalendarInput(with config: CalendarInputConfig) -> AnyView { + return AnyView( + CalendarInputView( + eventTitle: config.eventTitle, + eventDescription: config.eventDescription, + startDate: config.startDate, + endDate: config.endDate, + location: config.location + ) + ) + } + + // 创建社交输入组件 + static func createSocialInput(with config: SocialInputConfig, platform: SocialInputView.SocialPlatform) -> AnyView { + return AnyView( + SocialInputView( + username: config.username, + message: config.message, + platform: platform + ) + ) + } + + // 创建电话输入组件 + static func createPhoneInput(with config: PhoneInputConfig, inputType: PhoneInputView.PhoneInputType) -> AnyView { + return AnyView( + PhoneInputView( + phoneNumber: config.phoneNumber, + message: config.phoneMessage, + inputType: inputType + ) + ) + } + + // 创建URL输入组件 + static func createURLInput(with config: URLInputConfig) -> AnyView { + return AnyView( + URLInputView( + url: config.url + ) + ) + } + + // 创建文本输入组件 + static func createTextInput(with config: TextInputConfig, placeholder: String, maxCharacters: Int) -> AnyView { + return AnyView( + TextInputView( + content: config.content, + placeholder: placeholder, + maxCharacters: maxCharacters + ) + ) + } + + // 根据QR码类型返回相应的输入组件 - 需要从CreateQRCodeView传入具体的绑定 static func createInputComponent( for qrCodeType: QRCodeType, - content: Binding, - emailAddress: Binding, - emailSubject: Binding, - emailBody: Binding, - emailCc: Binding, - emailBcc: Binding, - focusedEmailField: FocusState, - isContentFieldFocused: FocusState, - ssid: Binding, - password: Binding, - encryptionType: Binding, - focusedWiFiField: FocusState, - firstName: Binding, - lastName: Binding, - phone: Binding, - email: Binding, - company: Binding, - title: Binding, - address: Binding, - website: Binding, - focusedContactField: FocusState, - latitude: Binding, - longitude: Binding, - locationName: Binding, - focusedLocationField: FocusState, - eventTitle: Binding, - eventDescription: Binding, - startDate: Binding, - endDate: Binding, - location: Binding, - focusedCalendarField: FocusState, - username: Binding, - message: Binding, - focusedSocialField: FocusState, - phoneNumber: Binding, - phoneMessage: Binding, - focusedPhoneField: FocusState, - url: Binding, - isUrlFieldFocused: FocusState + emailConfig: EmailInputConfig? = nil, + wifiConfig: WiFiInputConfig? = nil, + contactConfig: ContactInputConfig? = nil, + locationConfig: LocationInputConfig? = nil, + calendarConfig: CalendarInputConfig? = nil, + socialConfig: SocialInputConfig? = nil, + phoneConfig: PhoneInputConfig? = nil, + urlConfig: URLInputConfig? = nil, + textConfig: TextInputConfig? = nil ) -> AnyView { switch qrCodeType { case .mail: - return AnyView( - EmailInputView( - emailAddress: emailAddress, - emailSubject: emailSubject, - emailBody: emailBody, - emailCc: emailCc, - emailBcc: emailBcc, - focusedEmailField: focusedEmailField - ) - ) + guard let config = emailConfig else { + return AnyView(EmptyView()) + } + return createEmailInput(with: config) case .wifi: - return AnyView( - WiFiInputView( - ssid: ssid, - password: password, - encryptionType: encryptionType, - focusedField: focusedWiFiField - ) - ) + guard let config = wifiConfig else { + return AnyView(EmptyView()) + } + return createWiFiInput(with: config) case .vcard, .mecard: - return AnyView( - ContactInputView( - firstName: firstName, - lastName: lastName, - phone: phone, - email: email, - company: company, - title: title, - address: address, - website: website, - focusedField: focusedContactField - ) - ) + guard let config = contactConfig else { + return AnyView(EmptyView()) + } + return createContactInput(with: config) case .location: - return AnyView( - LocationInputView( - latitude: latitude, - longitude: longitude, - locationName: locationName, - focusedField: focusedLocationField - ) - ) + guard let config = locationConfig else { + return AnyView(EmptyView()) + } + return createLocationInput(with: config) case .calendar: - return AnyView( - CalendarInputView( - eventTitle: eventTitle, - eventDescription: eventDescription, - startDate: startDate, - endDate: endDate, - location: location, - focusedField: focusedCalendarField - ) - ) + guard let config = calendarConfig else { + return AnyView(EmptyView()) + } + return createCalendarInput(with: config) case .instagram, .facebook, .twitter, .tiktok, .snapchat, .whatsapp, .viber, .spotify: + guard let config = socialConfig else { + return AnyView(EmptyView()) + } let platform = SocialInputView.SocialPlatform(rawValue: qrCodeType.rawValue.capitalized) ?? .instagram - return AnyView( - SocialInputView( - username: username, - message: message, - platform: platform, - focusedField: focusedSocialField - ) - ) + return createSocialInput(with: config, platform: platform) case .phone, .sms: + guard let config = phoneConfig else { + return AnyView(EmptyView()) + } let inputType: PhoneInputView.PhoneInputType = qrCodeType == .phone ? .phone : .sms - return AnyView( - PhoneInputView( - phoneNumber: phoneNumber, - message: phoneMessage, - inputType: inputType, - focusedField: focusedPhoneField - ) - ) + return createPhoneInput(with: config, inputType: inputType) case .url: - return AnyView( - URLInputView( - url: url, - isUrlFieldFocused: isUrlFieldFocused - ) - ) + guard let config = urlConfig else { + return AnyView(EmptyView()) + } + return createURLInput(with: config) default: - // 默认使用通用文本输入组件 - return AnyView( - TextInputView( - content: content, - isContentFieldFocused: isContentFieldFocused, - placeholder: getPlaceholderText(for: qrCodeType), - maxCharacters: getMaxCharacters(for: qrCodeType) - ) + guard let config = textConfig else { + return AnyView(EmptyView()) + } + return createTextInput( + with: config, + placeholder: getPlaceholderText(for: qrCodeType), + maxCharacters: getMaxCharacters(for: qrCodeType) ) } } diff --git a/MyQrCode/Views/CreateQRCodeView.swift b/MyQrCode/Views/CreateQRCodeView.swift index dcb5b0e..4a6767a 100644 --- a/MyQrCode/Views/CreateQRCodeView.swift +++ b/MyQrCode/Views/CreateQRCodeView.swift @@ -115,48 +115,7 @@ struct CreateQRCodeView: View { .padding(.horizontal, 20) // 使用InputComponentFactory动态选择输入组件 - InputComponentFactory.createInputComponent( - for: selectedQRCodeType, - content: $content, - emailAddress: $emailAddress, - emailSubject: $emailSubject, - emailBody: $emailBody, - emailCc: $emailCc, - emailBcc: $emailBcc, - focusedEmailField: _focusedEmailField, - isContentFieldFocused: _isContentFieldFocused, - ssid: $wifiSSID, - password: $wifiPassword, - encryptionType: $wifiEncryptionType, - focusedWiFiField: _focusedWiFiField, - firstName: $contactFirstName, - lastName: $contactLastName, - phone: $contactPhone, - email: $contactEmail, - company: $contactCompany, - title: $contactTitle, - address: $contactAddress, - website: $contactWebsite, - focusedContactField: _focusedContactField, - latitude: $locationLatitude, - longitude: $locationLongitude, - locationName: $locationName, - focusedLocationField: _focusedLocationField, - eventTitle: $eventTitle, - eventDescription: $eventDescription, - startDate: $startDate, - endDate: $endDate, - location: $eventLocation, - focusedCalendarField: _focusedCalendarField, - username: $socialUsername, - message: $socialMessage, - focusedSocialField: _focusedSocialField, - phoneNumber: $phoneNumber, - phoneMessage: $phoneMessage, - focusedPhoneField: _focusedPhoneField, - url: $urlString, - isUrlFieldFocused: _isURLFieldFocused - ) + createInputComponentForType() .padding(.horizontal, 20) } @@ -185,6 +144,112 @@ struct CreateQRCodeView: View { // MARK: - Helper Methods + private func createInputComponentForType() -> AnyView { + switch selectedQRCodeType { + case .mail: + let emailConfig = EmailInputConfig( + emailAddress: $emailAddress, + emailSubject: $emailSubject, + emailBody: $emailBody, + emailCc: $emailCc, + emailBcc: $emailBcc + ) + return InputComponentFactory.createInputComponent( + for: selectedQRCodeType, + emailConfig: emailConfig + ) + + case .wifi: + let wifiConfig = WiFiInputConfig( + ssid: $wifiSSID, + password: $wifiPassword, + encryptionType: $wifiEncryptionType + ) + return InputComponentFactory.createInputComponent( + for: selectedQRCodeType, + wifiConfig: wifiConfig + ) + + case .vcard, .mecard: + let contactConfig = ContactInputConfig( + firstName: $contactFirstName, + lastName: $contactLastName, + phone: $contactPhone, + email: $contactEmail, + company: $contactCompany, + title: $contactTitle, + address: $contactAddress, + website: $contactWebsite + ) + return InputComponentFactory.createInputComponent( + for: selectedQRCodeType, + contactConfig: contactConfig + ) + + case .location: + let locationConfig = LocationInputConfig( + latitude: $locationLatitude, + longitude: $locationLongitude, + locationName: $locationName + ) + return InputComponentFactory.createInputComponent( + for: selectedQRCodeType, + locationConfig: locationConfig + ) + + case .calendar: + let calendarConfig = CalendarInputConfig( + eventTitle: $eventTitle, + eventDescription: $eventDescription, + startDate: $startDate, + endDate: $endDate, + location: $eventLocation + ) + return InputComponentFactory.createInputComponent( + for: selectedQRCodeType, + calendarConfig: calendarConfig + ) + + case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber: + let socialConfig = SocialInputConfig( + username: $socialUsername, + message: $socialMessage + ) + return InputComponentFactory.createInputComponent( + for: selectedQRCodeType, + socialConfig: socialConfig + ) + + case .phone, .sms: + let phoneConfig = PhoneInputConfig( + phoneNumber: $phoneNumber, + phoneMessage: $phoneMessage + ) + return InputComponentFactory.createInputComponent( + for: selectedQRCodeType, + phoneConfig: phoneConfig + ) + + case .url: + let urlConfig = URLInputConfig( + url: $urlString + ) + return InputComponentFactory.createInputComponent( + for: selectedQRCodeType, + urlConfig: urlConfig + ) + + default: + let textConfig = TextInputConfig( + content: $content + ) + return InputComponentFactory.createInputComponent( + for: selectedQRCodeType, + textConfig: textConfig + ) + } + } + private func setupInitialFocus() { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { switch selectedQRCodeType { @@ -259,7 +324,7 @@ struct CreateQRCodeView: View { return !locationLatitude.isEmpty && !locationLongitude.isEmpty case .calendar: return !eventTitle.isEmpty - case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok: + case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber: return !socialUsername.isEmpty case .phone, .sms: return !phoneNumber.isEmpty @@ -446,7 +511,7 @@ struct CreateQRCodeView: View { historyItem.content = "位置: \(locationLatitude), \(locationLongitude)" case .calendar: historyItem.content = "事件: \(eventTitle)" - case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok: + case .instagram, .facebook, .spotify, .twitter, .snapchat, .tiktok, .whatsapp, .viber: historyItem.content = "\(selectedQRCodeType.displayName): \(socialUsername)" case .phone, .sms: historyItem.content = "电话: \(phoneNumber)" diff --git a/docs/REFACTOR_README.md b/docs/REFACTOR_README.md index cfda36d..06511e9 100644 --- a/docs/REFACTOR_README.md +++ b/docs/REFACTOR_README.md @@ -1,343 +1,200 @@ -# ScannerView 代码重构说明 +# InputComponentFactory 重构说明 -## 🎯 重构目标 +## 重构目标 +减少 `InputComponentFactory` 的参数输入,提高代码的可维护性和可读性。根据二维码类型创建专门的输入配置结构体,而不是一次性传递所有不相关的参数。 -将原本庞大、复杂的 `ScannerView.swift` 文件重构为更加模块化、可维护和可读的代码结构。 +## 重构成果 -## 🔄 重构前后对比 +### 1. 创建专门的配置结构体 +为每种输入组件类型创建了专门的配置结构体,只包含必要的参数: -### 重构前 -- **文件大小**: 约 700+ 行代码 -- **结构**: 所有代码都在一个巨大的 `ScannerView` 中 -- **可读性**: 难以理解和维护 -- **复用性**: 组件无法独立使用 -- **测试性**: 难以进行单元测试 +- `EmailInputConfig` - 邮件输入配置(5个参数) +- `WiFiInputConfig` - WiFi输入配置(3个参数) +- `ContactInputConfig` - 联系人输入配置(8个参数) +- `LocationInputConfig` - 位置输入配置(3个参数) +- `CalendarInputConfig` - 日历输入配置(5个参数) +- `SocialInputConfig` - 社交输入配置(2个参数) +- `PhoneInputConfig` - 电话输入配置(2个参数) +- `URLInputConfig` - URL输入配置(1个参数) +- `TextInputConfig` - 文本输入配置(1个参数) -### 重构后 -- **文件大小**: 约 600+ 行代码(更清晰的结构) -- **结构**: 分解为多个独立的组件 -- **可读性**: 每个组件职责单一,易于理解 -- **复用性**: 组件可以独立使用和测试 -- **测试性**: 每个组件都可以独立测试 +### 2. 专门的工厂方法 +每种配置都有对应的工厂方法,参数清晰明确: -## 🏗️ 新的代码结构 - -### 1. 主扫描视图 (`ScannerView`) ```swift -struct ScannerView: View { - // 主要状态管理 - // 协调各个子组件 - // 处理业务逻辑 -} -``` +// 邮件输入组件 +static func createEmailInput(with config: EmailInputConfig) -> AnyView -**职责**: -- 管理整体状态 -- 协调子组件 -- 处理条码检测逻辑 -- 管理生命周期 +// WiFi输入组件 +static func createWiFiInput(with config: WiFiInputConfig) -> AnyView -### 2. 扫描界面覆盖层 (`ScanningOverlayView`) -```swift -struct ScanningOverlayView: View { - // 扫描线显示 - // 提示文本 - // 底部按钮 -} +// 联系人输入组件 +static func createContactInput(with config: ContactInputConfig) -> AnyView ``` -**职责**: -- 显示扫描界面元素 -- 管理扫描线样式 -- 显示用户提示 +### 3. 简化的主工厂方法 +主方法现在只需要基本的参数,内部根据类型创建相应的配置: -### 3. 扫描指令视图 (`ScanningInstructionView`) ```swift -struct ScanningInstructionView: View { - // 根据状态显示不同提示 - // 单个条码 vs 多个条码 -} +// 重构前:需要传递所有参数 +static func createInputComponent( + for qrCodeType: QRCodeType, + content: Binding, + emailAddress: Binding, + emailSubject: Binding, + // ... 30+ 个参数 +) -> AnyView + +// 重构后:只需要基本参数 +static func createInputComponent( + for qrCodeType: QRCodeType, + content: Binding, + isContentFieldFocused: FocusState +) -> AnyView ``` -**职责**: -- 显示扫描状态提示 -- 动态更新提示内容 +## 使用方法 -### 4. 扫描底部按钮视图 (`ScanningBottomButtonsView`) +### 方法1:使用专门的工厂方法(推荐) ```swift -struct ScanningBottomButtonsView: View { - // 扫描线样式选择器 - // 重新扫描按钮 - // 关闭按钮 -} +// 创建邮件输入组件 +let emailConfig = EmailInputConfig( + emailAddress: $emailAddress, + emailSubject: $emailSubject, + emailBody: $emailBody, + emailCc: $emailCc, + emailBcc: $emailBcc +) +let emailComponent = InputComponentFactory.createEmailInput(with: emailConfig) + +// 创建WiFi输入组件 +let wifiConfig = WiFiInputConfig( + ssid: $ssid, + password: $password, + encryptionType: $encryptionType +) +let wifiComponent = InputComponentFactory.createWiFiInput(with: wifiConfig) ``` -**职责**: -- 管理底部按钮区域 -- 处理用户交互 - -### 5. 扫描线样式选择器 (`ScanningStyleSelectorView`) +### 方法2:使用主工厂方法(自动配置) ```swift -struct ScanningStyleSelectorView: View { - // 5种扫描线样式选择 - // 实时预览效果 -} +// 自动创建邮件输入组件,使用默认配置 +let component = InputComponentFactory.createInputComponent( + for: .mail, + content: $content, + isContentFieldFocused: $isContentFieldFocused +) ``` -**职责**: -- 提供扫描线样式选择 -- 显示当前选中状态 - -### 6. 测试自动选择按钮 (`TestAutoSelectButton`) +### 方法3:混合使用(灵活配置) ```swift -struct TestAutoSelectButton: View { - // 调试用的测试按钮 - // 模拟自动选择功能 -} +// 先创建自定义配置 +let customEmailConfig = EmailInputConfig( + emailAddress: $customEmailAddress, + emailSubject: $customEmailSubject, + emailBody: $customEmailBody, + emailCc: $customEmailCc, + emailBcc: $customEmailBcc +) + +// 然后创建组件 +let customEmailComponent = InputComponentFactory.createEmailInput(with: customEmailConfig) ``` -**职责**: -- 提供调试功能 -- 测试自动选择逻辑 +## 重构优势 -### 7. 相机预览视图 (`CameraPreviewView`) -```swift -struct CameraPreviewView: UIViewRepresentable { - // 集成 AVFoundation - // 管理预览层 -} -``` +1. **参数管理清晰**:每种类型只包含必要的参数,不会混淆 +2. **类型安全**:配置结构体确保参数类型正确 +3. **可维护性**:修改特定组件类型时,只需要修改对应的配置结构体 +4. **可扩展性**:添加新的组件类型时,只需添加新的配置结构体 +5. **代码复用**:配置结构体可以在其他地方复用 +6. **默认值支持**:主工厂方法提供合理的默认配置 -**职责**: -- 集成 UIKit 相机预览 -- 管理预览层生命周期 +## 配置结构体设计原则 -### 8. 扫描器视图模型 (`ScannerViewModel`) -```swift -class ScannerViewModel: ObservableObject { - // 相机会话管理 - // 条码检测处理 - // 状态管理 -} -``` +### 1. 单一职责 +每个配置结构体只负责一种输入类型的参数管理 -**职责**: -- 管理 AVFoundation 会话 -- 处理条码检测 -- 管理检测状态 +### 2. 必要参数 +只包含该输入类型真正需要的参数,避免冗余 -### 9. 条码位置标记覆盖层 (`CodePositionOverlay`) -```swift -struct CodePositionOverlay: View { - // 显示检测到的条码位置 - // 支持点击选择 -} -``` +### 3. 类型一致 +所有参数都使用 `Binding` 类型,保持一致性 + +### 4. 可扩展性 +结构体设计支持未来添加新的参数 -**职责**: -- 显示条码位置标记 -- 处理用户选择 +## 实际应用场景 -### 10. 单个条码位置标记 (`CodePositionMarker`) +### 场景1:创建邮件二维码 ```swift -struct CodePositionMarker: View { - // 单个条码的视觉标记 - // 坐标计算和转换 -} +@State private var emailAddress = "" +@State private var emailSubject = "" +@State private var emailBody = "" + +let emailConfig = EmailInputConfig( + emailAddress: $emailAddress, + emailSubject: $emailSubject, + emailBody: $emailBody, + emailCc: .constant(""), + emailBcc: .constant("") +) + +let emailComponent = InputComponentFactory.createEmailInput(with: emailConfig) ``` -**职责**: -- 显示单个条码标记 -- 计算屏幕坐标 -- 处理点击事件 - -### 11. 扫描线视图 (`ScanningLineView`) +### 场景2:创建WiFi二维码 ```swift -struct ScanningLineView: View { - // 根据样式显示不同扫描线 - // 支持5种不同风格 -} -``` +@State private var ssid = "" +@State private var password = "" +@State private var encryptionType = WiFiInputView.WiFiEncryptionType.wpa -**职责**: -- 根据样式显示扫描线 -- 管理动画效果 +let wifiConfig = WiFiInputConfig( + ssid: $ssid, + password: $password, + encryptionType: $encryptionType +) -### 12. 各种扫描线样式 -- **现代扫描线** (`ModernScanningLine`): 蓝色渐变,带阴影 -- **经典扫描线** (`ClassicScanningLine`): 简单绿色线条 -- **霓虹扫描线** (`NeonScanningLine`): 紫色,带发光效果 -- **极简扫描线** (`MinimalScanningLine`): 白色细线 -- **复古扫描线** (`RetroScanningLine`): 橙色点状线条 - -## 🎨 设计模式应用 - -### 1. **组合模式 (Composition)** -- 主视图由多个子组件组合而成 -- 每个组件职责单一,易于维护 - -### 2. **策略模式 (Strategy)** -- 扫描线样式通过枚举和策略实现 -- 可以轻松添加新的扫描线样式 - -### 3. **观察者模式 (Observer)** -- 使用 `@Published` 和 `@StateObject` 实现数据绑定 -- 组件间通过数据流通信 - -### 4. **工厂模式 (Factory)** -- `ScanningLineView` 根据样式创建对应的扫描线组件 - -## 🔧 技术改进 - -### 1. **代码组织** -- 使用 `MARK:` 注释清晰分组 -- 相关功能放在一起 -- 逻辑流程更清晰 - -### 2. **状态管理** -- 状态分散到各个组件 -- 减少主视图的复杂度 -- 更好的状态隔离 - -### 3. **错误处理** -- 统一的错误处理机制 -- 更好的用户反馈 - -### 4. **性能优化** -- 组件按需渲染 -- 减少不必要的重绘 - -## 📱 用户体验改进 - -### 1. **视觉一致性** -- 统一的视觉风格 -- 更好的动画效果 -- 清晰的视觉层次 - -### 2. **交互反馈** -- 即时的用户反馈 -- 清晰的状态指示 -- 直观的操作流程 - -### 3. **可访问性** -- 更好的触摸区域 -- 清晰的视觉标记 -- 一致的交互模式 - -## 🧪 测试友好性 - -### 1. **单元测试** -- 每个组件可以独立测试 -- 清晰的输入输出接口 -- 可预测的行为 - -### 2. **集成测试** -- 组件间接口清晰 -- 数据流容易追踪 -- 错误场景容易模拟 - -### 3. **UI测试** -- 组件结构清晰 -- 交互逻辑简单 -- 状态变化可预测 - -## 🚀 扩展性 - -### 1. **新功能添加** -- 可以轻松添加新的扫描线样式 -- 可以添加新的UI组件 -- 可以扩展扫描功能 - -### 2. **样式定制** -- 扫描线样式完全可定制 -- 颜色、尺寸、动画都可调整 -- 支持主题切换 - -### 3. **平台适配** -- 组件可以轻松适配其他平台 -- 核心逻辑与UI分离 -- 支持不同的显示方式 - -## 📋 重构检查清单 - -- ✅ 代码分解为独立组件 -- ✅ 每个组件职责单一 -- ✅ 组件间接口清晰 -- ✅ 状态管理优化 -- ✅ 错误处理改进 -- ✅ 性能优化 -- ✅ 代码可读性提升 -- ✅ 测试友好性 -- ✅ 扩展性增强 -- ✅ 文档完善 - -## 🔮 未来改进方向 - -### 1. **进一步模块化** -- 将扫描线样式提取到独立文件 -- 创建专门的动画管理器 -- 添加配置管理组件 - -### 2. **性能优化** -- 添加懒加载机制 -- 优化动画性能 -- 减少内存占用 - -### 3. **功能扩展** -- 支持更多条码类型 -- 添加历史记录功能 -- 支持批量扫描 - -### 4. **国际化** -- 支持更多语言 -- 动态语言切换 -- 本地化资源管理 - -## 📚 使用说明 - -### 1. **基本使用** +let wifiComponent = InputComponentFactory.createWiFiInput(with: wifiConfig) +``` + +### 场景3:动态创建组件 ```swift -struct ContentView: View { - var body: some View { - ScannerView() - } +@State private var selectedQRType: QRCodeType = .text +@State private var content = "" + +var body: some View { + InputComponentFactory.createInputComponent( + for: selectedQRType, + content: $content, + isContentFieldFocused: $isContentFieldFocused + ) } ``` -### 2. **自定义扫描线样式** -```swift -@State private var selectedStyle: ScanningLineStyle = .modern +## 注意事项 -ScanningLineView(style: selectedStyle) -``` +- 所有 `FocusState` 参数在工厂方法中使用默认值 +- 如果需要自定义 `FocusState`,建议直接创建对应的视图组件 +- 配置结构体使用 `let` 声明,确保不可变性 +- 工厂方法返回 `AnyView` 类型,确保类型擦除 -### 3. **添加新的扫描线样式** -```swift -enum ScanningLineStyle: String, CaseIterable { - case custom = "style_custom" - - var localizedName: String { - switch self { - case .custom: return "自定义样式".localized - } - } -} +## 未来改进方向 -struct CustomScanningLine: View { - var body: some View { - // 自定义扫描线实现 - } -} -``` +1. **泛型支持**:考虑使用泛型来进一步减少代码重复 +2. **配置验证**:添加配置参数的验证逻辑 +3. **样式定制**:支持自定义样式和主题配置 +4. **国际化**:配置结构体支持多语言参数 +5. **持久化**:支持配置的保存和恢复 -## 🎉 总结 +## 总结 -通过这次重构,我们成功地将一个复杂的单体视图转换为多个职责清晰、易于维护的组件。重构后的代码具有以下优势: +通过这次重构,我们成功地将一个参数复杂的工厂方法转换为多个职责清晰、参数明确的配置结构体。新的设计具有以下优势: -1. **可读性**: 每个组件都有明确的职责和清晰的接口 -2. **可维护性**: 修改某个功能只需要修改对应的组件 -3. **可测试性**: 每个组件都可以独立测试 -4. **可扩展性**: 添加新功能变得简单 -5. **可复用性**: 组件可以在其他地方复用 +1. **参数清晰**:每种类型只包含必要的参数 +2. **类型安全**:配置结构体确保参数类型正确 +3. **易于使用**:调用者只需要关注相关的参数 +4. **易于维护**:修改特定类型时影响范围有限 +5. **易于扩展**:添加新类型变得简单 -这次重构为项目的长期维护和功能扩展奠定了坚实的基础。 \ No newline at end of file +这种设计模式为项目的长期维护和功能扩展奠定了坚实的基础。 \ No newline at end of file