// // KeychainDemo.swift // SecureStorgageSample // // Demonstrates Keychain storage with LocalData package. // import SwiftUI import LocalData struct KeychainDemo: View { @State private var username = "" @State private var password = "" @State private var storedCredential = "" @State private var statusMessage = "" @State private var isLoading = false @State private var selectedAccessibility: KeychainAccessibility = .afterFirstUnlock @State private var selectedAccessControl: KeychainAccessControl? = nil @FocusState private var focusedField: Bool var body: some View { Form { Section { Text("Keychain provides hardware-backed secure storage for sensitive data like passwords, tokens, and keys.") .font(.caption) .foregroundStyle(.secondary) } Section("Credentials") { TextField("Username", text: $username) .focused($focusedField) SecureField("Password", text: $password) .focused($focusedField) } Section("Accessibility") { Picker("When Accessible", selection: $selectedAccessibility) { ForEach(KeychainAccessibility.allCases, id: \.self) { option in Text(option.displayName).tag(option) } } .pickerStyle(.menu) } Section("Access Control (Optional)") { Picker("Require Authentication", selection: $selectedAccessControl) { Text("None").tag(nil as KeychainAccessControl?) ForEach(KeychainAccessControl.allCases, id: \.self) { option in Text(option.displayName).tag(option as KeychainAccessControl?) } } .pickerStyle(.menu) } Section("Actions") { Button(action: saveCredentials) { HStack { Image(systemName: "lock.fill") Text("Save to Keychain") } } .disabled(username.isEmpty || password.isEmpty || isLoading) Button(action: loadCredentials) { HStack { Image(systemName: "key.fill") Text("Retrieve from Keychain") } } .disabled(isLoading) Button(action: deleteCredentials) { HStack { Image(systemName: "trash") Text("Delete from Keychain") } } .foregroundStyle(.red) .disabled(isLoading) } if !storedCredential.isEmpty { Section("Retrieved Data") { Text(storedCredential) .font(.system(.body, design: .monospaced)) } } if !statusMessage.isEmpty { Section { Text(statusMessage) .font(.caption) .foregroundStyle(statusMessage.contains("Error") ? .red : .green) } } Section("Example Key Configuration") { LabeledContent("Domain", value: "Keychain") LabeledContent("Security", value: "Keychain Policy") LabeledContent("Serializer", value: "JSON") LabeledContent("Platform", value: "Phone Only") } } .navigationTitle("Keychain") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItemGroup(placement: .keyboard) { Spacer() Button("Done") { focusedField = false } } } } private func saveCredentials() { isLoading = true Task { do { let key = StorageKeys.CredentialsKey( accessibility: selectedAccessibility, accessControl: selectedAccessControl ) let credential = Credential(username: username, password: password) try await StorageRouter.shared.set(credential, for: key) statusMessage = "✓ Saved to Keychain securely" } catch { statusMessage = "Error: \(error.localizedDescription)" } isLoading = false } } private func loadCredentials() { isLoading = true Task { do { let key = StorageKeys.CredentialsKey( accessibility: selectedAccessibility, accessControl: selectedAccessControl ) let credential = try await StorageRouter.shared.get(key) storedCredential = "Username: \(credential.username)\nPassword: ****" statusMessage = "✓ Retrieved from Keychain" } catch StorageError.notFound { storedCredential = "" statusMessage = "No credentials stored" } catch { statusMessage = "Error: \(error.localizedDescription)" } isLoading = false } } private func deleteCredentials() { isLoading = true Task { do { let key = StorageKeys.CredentialsKey( accessibility: selectedAccessibility, accessControl: selectedAccessControl ) try await StorageRouter.shared.remove(key) storedCredential = "" statusMessage = "✓ Deleted from Keychain" } catch { statusMessage = "Error: \(error.localizedDescription)" } isLoading = false } } } #Preview { NavigationStack { KeychainDemo() } }