You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MyQRCode/docs/WIFI_FEATURE_ENHANCEMENT_RE...

12 KiB

WiFi功能增强实现文档

概述

基于Medium文章 Connect to WiFi from iOS App 的实现方法我们为MyQrCode应用添加了完整的WiFi自动连接功能。用户扫描WiFi二维码后可以一键复制密码并自动连接到WiFi网络。

功能特性

1. 扫描WiFi二维码

用户扫描包含WiFi信息的二维码后可以

  • 查看WiFi网络信息SSID、加密方式、密码状态
  • 一键复制WiFi密码
  • 智能WiFi设置
    • 使用NEHotspotConfiguration进行WiFi自动连接iOS 11+
    • 降级到系统WiFi设置页面
    • 最终显示详细的手动设置指导

2. 历史记录管理

在历史记录中查看WiFi二维码时

  • 重新获取WiFi密码
  • 智能WiFi设置支持NEHotspotConfiguration和降级方案
  • 管理收藏的WiFi信息

3. 便捷操作

  • 一键复制密码: 快速复制WiFi密码到剪贴板
  • 智能WiFi设置:
    • iOS 11+使用NEHotspotConfiguration进行WiFi自动连接
    • 降级方案跳转到系统WiFi设置页面
    • 最终降级:显示详细的手动设置指导,包含网络名称和密码
  • 用户反馈: 操作后显示确认提示

4. 用户体验

  • 视觉区分: 使用不同颜色的图标区分功能
  • 直观操作: 图标按钮功能一目了然
  • 智能降级: 根据系统版本和权限提供最佳的用户体验
  • 标准API: 使用iOS官方NEHotspotConfiguration API确保可靠性和兼容性
  • 详细指导: 提供完整的手动设置步骤和网络信息

技术实现

1. 数据结构扩展

WiFiDetails结构体

struct WiFiDetails: Codable {
    let ssid: String
    let password: String
    let encryption: String
}

ParsedQRData扩展

public struct ParsedQRData: NSSecureCoding {
    // ... 现有属性
    public let extraData: Data?  // 新增存储WiFi详细信息
    
    // 更新初始化方法以支持extraData
    public init(type: QRCodeType, content: String, extraData: Data? = nil) {
        self.type = type
        self.content = content
        self.extraData = extraData
    }
}

2. WiFi连接管理器

创建了专门的WiFiConnectionManager类来管理WiFi连接

class WiFiConnectionManager: ObservableObject {
    @Published var isConnecting = false
    @Published var connectionStatus: ConnectionStatus = .idle
    
    enum ConnectionStatus {
        case idle
        case connecting
        case connected
        case failed(String)
    }
    
    static let shared = WiFiConnectionManager()
    
    func connectToWiFi(ssid: String, password: String, completion: @escaping (Bool, String?) -> Void) {
        guard #available(iOS 11.0, *) else {
            completion(false, "iOS 11+ required for WiFi connection")
            return
        }
        
        DispatchQueue.main.async {
            self.isConnecting = true
            self.connectionStatus = .connecting
        }
        
        // 创建WiFi配置
        let configuration = NEHotspotConfiguration(ssid: ssid, passphrase: password, isWEP: false)
        configuration.joinOnce = true
        
        // 应用配置
        NEHotspotConfigurationManager.shared.apply(configuration) { [weak self] error in
            DispatchQueue.main.async {
                self?.isConnecting = false
                
                if let error = error {
                    let errorMessage = self?.handleWiFiError(error)
                    self?.connectionStatus = .failed(errorMessage ?? "Unknown error")
                    completion(false, errorMessage)
                } else {
                    self?.connectionStatus = .connected
                    completion(true, nil)
                }
            }
        }
    }
    
    private func handleWiFiError(_ error: Error) -> String {
        let errorCode = (error as NSError).code
        
        switch errorCode {
        case NEHotspotConfigurationError.userDenied.rawValue:
            return "wifi_user_denied".localized
        case NEHotspotConfigurationError.alreadyAssociated.rawValue:
            return "wifi_already_connected".localized
        case NEHotspotConfigurationError.invalidSSID.rawValue:
            return "wifi_invalid_ssid".localized
        case NEHotspotConfigurationError.invalidWPAPassphrase.rawValue:
            return "wifi_invalid_password".localized
        default:
            return "wifi_connection_failed".localized
        }
    }
}

智能降级策略

func connectWithFallback(ssid: String, password: String, completion: @escaping (Bool, String?) -> Void) {
    connectToWiFi(ssid: ssid, password: password) { [weak self] success, error in
        if success {
            completion(true, nil)
        } else {
            // 如果NEHotspotConfiguration失败尝试降级方案
            self?.tryFallbackConnection(ssid: ssid, password: password, completion: completion)
        }
    }
}

private func tryFallbackConnection(ssid: String, password: String, completion: @escaping (Bool, String?) -> Void) {
    // 尝试打开系统WiFi设置
    let systemWifiURLString = "App-Prefs:root=WIFI"
    if let url = URL(string: systemWifiURLString) {
        if UIApplication.shared.canOpenURL(url) {
            UIApplication.shared.open(url) { success in
                if success {
                    completion(false, "wifi_opened_settings".localized)
                } else {
                    completion(false, String(format: "wifi_manual_setup_instruction".localized, ssid, password))
                }
            }
            return
        }
    }
    
    // 最终降级方案:显示手动设置指导
    completion(false, String(format: "wifi_manual_setup_instruction".localized, ssid, password))
}

3. 解析器更新

QRCodeParser扩展

private func parseWiFi(_ content: String) -> ParsedQRData? {
    // WiFi格式: WIFI:S:<SSID>;T:<WPA|WEP|>;P:<password>;;
    let pattern = "WIFI:S:([^;]+);T:([^;]*);P:([^;]+);;"
    
    guard let regex = try? NSRegularExpression(pattern: pattern),
          let match = regex.firstMatch(in: content, range: NSRange(content.startIndex..., in: content)) else {
        return nil
    }
    
    let ssid = String(content[Range(match.range(at: 1), in: content)!])
    let encryption = String(content[Range(match.range(at: 2), in: content)!])
    let password = String(content[Range(match.range(at: 3), in: content)!])
    
    // 创建WiFi详细信息
    let wifiDetails = WiFiDetails(ssid: ssid, password: password, encryption: encryption)
    
    // 编码为Data存储
    let extraData = try? JSONEncoder().encode(wifiDetails)
    
    return ParsedQRData(type: .wifi, content: content, extraData: extraData)
}

4. UI界面更新

QRCodeDetailView增强

// MARK: - 设置WiFi
private func setupWiFi() {
    guard let wifiDetails = getWiFiDetails() else { return }
    
    // 使用WiFi连接管理器
    WiFiConnectionManager.shared.connectWithFallback(ssid: wifiDetails.ssid, password: wifiDetails.password) { [weak self] success, error in
        DispatchQueue.main.async {
            if success {
                self?.alertMessage = "wifi_connected_successfully".localized
            } else {
                self?.alertMessage = error ?? "wifi_connection_failed".localized
            }
            self?.showingAlert = true
        }
    }
}

// MARK: - 复制WiFi密码
private func copyWiFiPassword() {
    guard let wifiDetails = getWiFiDetails() else { return }
    
    UIPasteboard.general.string = wifiDetails.password
    alertMessage = "wifi_password_copied".localized
    showingAlert = true
}

// MARK: - 获取WiFi详情
private func getWiFiDetails() -> WiFiDetails? {
    guard let extraData = historyItem.parsedData?.extraData else { return nil }
    return try? JSONDecoder().decode(WiFiDetails.self, from: extraData)
}

条件显示WiFi按钮

// 在actionButtonsSection中条件显示WiFi按钮
if getQRCodeType() == .wifi {
    // 复制WiFi密码按钮
    Button(action: copyWiFiPassword) {
        Image(systemName: "doc.on.doc.fill")
            .font(.title2)
            .foregroundColor(.orange)
    }
    
    // 设置WiFi按钮
    Button(action: setupWiFi) {
        Image(systemName: "wifi.circle")
            .font(.title2)
            .foregroundColor(.purple)
    }
}

5. 本地化支持

新增本地化字符串

// 中文
"wifi_password_copied" = "WiFi密码已复制到剪贴板";
"wifi_connected_successfully" = "WiFi连接成功";
"wifi_connection_failed" = "WiFi连接失败";
"wifi_opened_settings" = "已打开系统WiFi设置";
"wifi_user_denied" = "用户拒绝了WiFi连接请求";
"wifi_already_connected" = "已经连接到该WiFi网络";
"wifi_invalid_ssid" = "无效的WiFi网络名称";
"wifi_invalid_password" = "WiFi密码格式无效";

// 英文
"wifi_password_copied" = "WiFi password copied to clipboard";
"wifi_connected_successfully" = "WiFi connected successfully!";
"wifi_connection_failed" = "WiFi connection failed";
"wifi_opened_settings" = "Opened system WiFi settings";
"wifi_user_denied" = "User denied WiFi connection request";
"wifi_already_connected" = "Already connected to this WiFi network";
"wifi_invalid_ssid" = "Invalid WiFi network name";
"wifi_invalid_password" = "Invalid WiFi password format";

// 泰语
"wifi_password_copied" = "รหัสผ่าน WiFi ถูกคัดลอกไปยังคลิปบอร์ดแล้ว";
"wifi_connected_successfully" = "เชื่อมต่อ WiFi สำเร็จแล้ว!";
"wifi_connection_failed" = "การเชื่อมต่อ WiFi ล้มเหลว";
"wifi_opened_settings" = "เปิดการตั้งค่า WiFi ของระบบแล้ว";
"wifi_user_denied" = "ผู้ใช้ปฏิเสธคำขอเชื่อมต่อ WiFi";
"wifi_already_connected" = "เชื่อมต่อกับเครือข่าย WiFi นี้แล้ว";
"wifi_invalid_ssid" = "ชื่อเครือข่าย WiFi ไม่ถูกต้อง";
"wifi_invalid_password" = "รูปแบบรหัสผ่าน WiFi ไม่ถูกต้อง";

实现亮点

1. 基于Medium文章的最佳实践

  • 使用NEHotspotConfiguration API进行WiFi自动连接
  • 实现完整的错误处理和用户反馈
  • 采用智能降级策略确保兼容性

2. 架构设计

  • 创建专门的WiFi连接管理器类
  • 使用ObservableObject进行状态管理
  • 实现单例模式便于全局访问

3. 用户体验优化

  • 实时连接状态反馈
  • 多语言错误提示
  • 智能降级策略
  • 直观的图标设计

4. 技术特性

  • iOS 11+版本兼容性检查
  • 完整的错误类型处理
  • 异步操作和主线程UI更新
  • 内存管理weak self

使用流程

  1. 扫描WiFi二维码: 用户扫描包含WiFi信息的二维码
  2. 查看详情: 在详情页面查看WiFi网络信息
  3. 复制密码: 点击橙色复制图标复制WiFi密码
  4. 自动连接: 点击紫色WiFi图标尝试自动连接
  5. 降级处理: 如果自动连接失败系统会尝试打开WiFi设置或显示手动设置指导

错误处理

NEHotspotConfiguration支持的错误类型

  • 用户拒绝: 用户拒绝了WiFi连接请求
  • 已连接: 已经连接到该WiFi网络
  • 无效SSID: 无效的WiFi网络名称
  • 无效密码: WiFi密码格式无效

总结

通过参考Medium文章的实现方法我们成功为MyQrCode应用添加了完整的WiFi自动连接功能。该实现具有以下优势

  1. 标准API: 使用iOS官方NEHotspotConfiguration API
  2. 智能降级: 多层降级策略确保所有用户都能使用
  3. 完整错误处理: 详细的错误类型处理和用户反馈
  4. 多语言支持: 支持中文、英文、泰语三种语言
  5. 用户体验: 直观的操作界面和实时状态反馈

这个实现为用户提供了便捷的WiFi连接体验同时确保了在各种情况下的可靠性和兼容性。