From 6323b1d2c91d0d03f5739a7c61d3285ca7883ad0 Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Thu, 26 Jun 2025 17:36:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8C=81=E4=B9=85=E5=8C=96=E7=94=A8=E6=88=B7id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dimensionhub/Core/KeychainHelper.swift | 97 +++++++++++++++++++ dimensionhub/Core/Uitls.swift | 8 -- dimensionhub/Views/Detail/DetailView.swift | 2 +- .../Views/FollowList/FollowListView.swift | 2 +- dimensionhub/Views/Index/DateNavView.swift | 2 +- dimensionhub/Views/Index/IndexMainView.swift | 2 +- dimensionhub/Views/List/ListView.swift | 2 +- dimensionhub/Views/Search/SearchView.swift | 60 +++++++++--- dimensionhub/dimensionhubApp.swift | 36 ++++--- 9 files changed, 169 insertions(+), 42 deletions(-) create mode 100644 dimensionhub/Core/KeychainHelper.swift diff --git a/dimensionhub/Core/KeychainHelper.swift b/dimensionhub/Core/KeychainHelper.swift new file mode 100644 index 0000000..ae6a57f --- /dev/null +++ b/dimensionhub/Core/KeychainHelper.swift @@ -0,0 +1,97 @@ +// +// KeychainHelper.swift +// dimensionhub +// +// Created by 安礼成 on 2025/6/26. +// +import Foundation +import Security +import UIKit + +struct KeychainHelper { + + // MARK: - 设备标识符获取 + static func getPersistentUserId() -> String { + let keychainAccount = "com.jihe.dimensionhub.deviceUUID" + + // 1. 尝试从Keychain读取 + if let uuid = load(key: keychainAccount) { + return uuid + } + + // 2. 生成新的UUID + let newUUID = generatorUserId() + + // 3. 尝试保存到Keychain + let data = newUUID.data(using: .utf8)! + if !save(key: keychainAccount, data: data) { + NSLog("save uuid to keychain error") + } + + return newUUID + } + + /// 从 Keychain 加载字符串 + private static func load(key: String) -> String? { + if let data = loadFromSecurity(key: key) { + return String(data: data, encoding: .utf8) + } + return nil + } + + /// 删除 Keychain 中的项 + static func delete(key: String) -> Bool { + let query = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key + ] as [String: Any] + + let status = SecItemDelete(query as CFDictionary) + return status == errSecSuccess || status == errSecItemNotFound + } + + // MARK: - 私有方法 + + private static func save(key: String, data: Data) -> Bool { + let query = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, + kSecValueData as String: data + ] as [String: Any] + + // 先删除已存在的项 + SecItemDelete(query as CFDictionary) + + // 添加新项 + let status = SecItemAdd(query as CFDictionary, nil) + return status == errSecSuccess + } + + private static func loadFromSecurity(key: String) -> Data? { + let query = [ + kSecClass as String: kSecClassGenericPassword, + kSecAttrAccount as String: key, + kSecReturnData as String: kCFBooleanTrue!, + kSecMatchLimit as String: kSecMatchLimitOne + ] as [String: Any] + + var dataTypeRef: AnyObject? + let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef) + + if status == errSecSuccess { + return dataTypeRef as? Data + } + return nil + } + + // 生成uuid + private static func generatorUserId() -> String { + if let uuid = UIDevice.current.identifierForVendor?.uuidString { + return uuid.lowercased() + } else { + return UUID().uuidString.lowercased() + } + } + +} diff --git a/dimensionhub/Core/Uitls.swift b/dimensionhub/Core/Uitls.swift index 26f4e6f..89f105c 100644 --- a/dimensionhub/Core/Uitls.swift +++ b/dimensionhub/Core/Uitls.swift @@ -26,12 +26,4 @@ struct Utils { return attributedString.string } - static func defaultUserId() -> String { - if let uuid = UIDevice.current.identifierForVendor?.uuidString { - return uuid.lowercased() - } else { - return UUID().uuidString.lowercased() - } - } - } diff --git a/dimensionhub/Views/Detail/DetailView.swift b/dimensionhub/Views/Detail/DetailView.swift index c9484fd..e012024 100644 --- a/dimensionhub/Views/Detail/DetailView.swift +++ b/dimensionhub/Views/Detail/DetailView.swift @@ -7,7 +7,7 @@ import SwiftUI struct DetailView: View { - @AppStorage("userId") private var userId: String = Utils.defaultUserId() + @Environment(\.userId) private var userId @State var detailModel = DetailModel() @State var showAllSummary: Bool = false diff --git a/dimensionhub/Views/FollowList/FollowListView.swift b/dimensionhub/Views/FollowList/FollowListView.swift index 12ebb8a..140a396 100644 --- a/dimensionhub/Views/FollowList/FollowListView.swift +++ b/dimensionhub/Views/FollowList/FollowListView.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI struct FollowListView: View { - @AppStorage("userId") private var userId: String = Utils.defaultUserId() + @Environment(\.userId) private var userId @State var followModel = FollowListModel() @State var isMoreLoading: Bool = false diff --git a/dimensionhub/Views/Index/DateNavView.swift b/dimensionhub/Views/Index/DateNavView.swift index 2416bfe..bfab5d5 100644 --- a/dimensionhub/Views/Index/DateNavView.swift +++ b/dimensionhub/Views/Index/DateNavView.swift @@ -62,7 +62,7 @@ final class DateNavModel { } struct DateNavView: View { - @AppStorage("userId") private var userId: String = Utils.defaultUserId() + @Environment(\.userId) private var userId @State var navModel = DateNavModel() @Binding var selectGroupId: String diff --git a/dimensionhub/Views/Index/IndexMainView.swift b/dimensionhub/Views/Index/IndexMainView.swift index 6ab8ccb..54ca239 100644 --- a/dimensionhub/Views/Index/IndexMainView.swift +++ b/dimensionhub/Views/Index/IndexMainView.swift @@ -11,7 +11,7 @@ import Combine struct IndexMainView: View { @Environment(\.modelContext) private var modelContext @EnvironmentObject var appNavigation: AppNavigation - @AppStorage("userId") private var userId: String = Utils.defaultUserId() + @Environment(\.userId) private var userId @State var indexModel = IndexModel() @State private var scrollID: IndexModel.ScrollTarget? = nil diff --git a/dimensionhub/Views/List/ListView.swift b/dimensionhub/Views/List/ListView.swift index 9f3c748..a39dc57 100644 --- a/dimensionhub/Views/List/ListView.swift +++ b/dimensionhub/Views/List/ListView.swift @@ -7,7 +7,7 @@ import SwiftUI struct ListView: View { - @AppStorage("userId") private var userId: String = Utils.defaultUserId() + @Environment(\.userId) private var userId @State var detailModel = ListModel() let id: Int diff --git a/dimensionhub/Views/Search/SearchView.swift b/dimensionhub/Views/Search/SearchView.swift index f164411..913c91e 100644 --- a/dimensionhub/Views/Search/SearchView.swift +++ b/dimensionhub/Views/Search/SearchView.swift @@ -10,7 +10,7 @@ import SwiftData struct SearchView: View { @Environment(\.modelContext) var modelContext - @AppStorage("userId") private var userId: String = Utils.defaultUserId() + @Environment(\.userId) private var userId @Environment(\.dismiss) var dismiss @State var searchText: String = "" @@ -19,6 +19,12 @@ struct SearchView: View { @FocusState private var isFocused: Bool @State private var isSearching = false + // 是否显示搜索结果 + @State private var showSearchResult: Bool = false + + // 控制是否需要显示键盘 + @State private var showKeyboard: Bool = true + var body: some View { VStack(spacing: 12) { HStack(spacing: 12) { @@ -74,22 +80,36 @@ struct SearchView: View { .frame(height: 50) .padding(.top, 12) - if searchModel.dramaGroups.isEmpty { - Spacer() - Text("什么都没有找到") - .font(.system(size: 18)) - .foregroundColor(Color(hex: "#333333")) - Spacer() - } else { - ScrollView(.vertical, showsIndicators: false) { - LazyVStack(alignment: .center, spacing: 10) { - ForEach(searchModel.dramaGroups, id: \.group_id) { group in - SearchDramaGroupView(group: group) + if showSearchResult { + if searchModel.dramaGroups.isEmpty { + Spacer() + Text("什么都没有找到") + .font(.system(size: 18)) + .foregroundColor(Color(hex: "#333333")) + Spacer() + } else { + ScrollView(.vertical, showsIndicators: false) { + LazyVStack(alignment: .center, spacing: 10) { + ForEach(searchModel.dramaGroups, id: \.group_id) { group in + SearchDramaGroupView(group: group) + } } + .padding(.top, 8) } - .padding(.top, 8) + .onTapGesture { + isFocused = false + } + .simultaneousGesture( + DragGesture().onChanged{ _ in + isFocused = false + } + ) } + } else { + // 预留用来显示热搜词 + Spacer() } + } .navigationTitle("") .navigationBarBackButtonHidden(true) @@ -97,8 +117,10 @@ struct SearchView: View { .ignoresSafeArea(.keyboard, edges: .bottom) // 避免键盘遮挡 .onAppear { // 避免过早触发系统输入错误 - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - isFocused = true + if showKeyboard { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + isFocused = true + } } } } @@ -108,7 +130,15 @@ struct SearchView: View { if !trimmed.isEmpty { Task.detached { await searchModel.search(userId: userId, name: trimmed) + DispatchQueue.main.async { + // 显示搜索结果 + self.showSearchResult = true + } } + + // 其他情况下不自动弹出键盘 + self.showKeyboard = false + // 可选:添加历史记录 // let history = SearchHistory(keyword: trimmed, timestamp: Date()) // modelContext.insert(history) diff --git a/dimensionhub/dimensionhubApp.swift b/dimensionhub/dimensionhubApp.swift index 432a2c9..f7b2041 100644 --- a/dimensionhub/dimensionhubApp.swift +++ b/dimensionhub/dimensionhubApp.swift @@ -33,11 +33,8 @@ struct dimensionhubApp: App { }() init() { - if let userId = UserDefaults.standard.string(forKey: "userId") { - print("user_id is: \(userId)") - } else { - UserDefaults.standard.set(Utils.defaultUserId(), forKey: "userId") - } + let userId = KeychainHelper.getPersistentUserId() + print("user_id is: \(userId)") } var body: some Scene { @@ -54,6 +51,7 @@ struct dimensionhubApp: App { SearchView() } } + .environment(\.userId, KeychainHelper.getPersistentUserId()) } .navigationViewStyle(.stack) .tint(.black) @@ -79,20 +77,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD UNUserNotificationCenter.current().delegate = self // 请求通知权限 UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) {granted, error in - print("通知权限 granted: \(granted)") + NSLog("通知权限 granted: \(granted)") guard granted else { return } // 获取通知设置 UNUserNotificationCenter.current().getNotificationSettings { settings in - print("通知设置: \(settings)") + NSLog("通知设置: \(settings)") guard settings.authorizationStatus == .authorized else { return } - print("通知设置: authorized!!!!") + NSLog("通知设置: authorized!!!!") DispatchQueue.main.async { // 注册远程通知 @@ -108,12 +106,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } let token = tokenParts.joined() - print("Device Token: \(token)") + NSLog("Device Token: \(token)") // 将 deviceToken 发送给你的服务器 Task { - guard let userId = UserDefaults.standard.string(forKey: "userId") else { - return - } + let userId = KeychainHelper.getPersistentUserId() let sendResult = await API.sendDeviceTokenToServer(userId: userId, token: token, as: String.self) switch sendResult { @@ -127,7 +123,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD // 注册远程通知失败 func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { - print("注册远程通知失败: \(error)") + NSLog("注册远程通知失败: \(error)") } // 收到远程通知(App 在前台) @@ -191,8 +187,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD private func handleDeepLink(_ link: String) { // 实现深度链接处理逻辑 - print("处理深度链接: \(link)") + NSLog("处理深度链接: \(link)") } } +// userId通过环境变量传递 + +struct UserId: EnvironmentKey { + static let defaultValue: String = KeychainHelper.getPersistentUserId() +} + +extension EnvironmentValues { + var userId: String { + get { self[UserId.self] } + set { self[UserId.self] = newValue } + } +}