import SwiftUI
import Security
struct SecureStorageView: View {
@State private var secretText = ""
@State private var loadedSecret = ""
@State private var showingAlert = false
@State private var alertMessage = ""
let service = "com.example.SecureStorage"
let account = "userSecret"
var body: some View {
VStack(spacing: 20) {
TextField("Enter secret", text: $secretText)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
HStack {
Button("Save Secret") {
do {
try saveSecret(secretText)
loadedSecret = ""
} catch {
alertMessage = "Failed to save secret: \(error.localizedDescription)"
showingAlert = true
}
}
Button("Load") {
do {
loadedSecret = try loadSecret() ?? "(No secret found)"
} catch {
alertMessage = "Failed to load secret: \(error.localizedDescription)"
showingAlert = true
}
}
}
.padding()
Text("Loaded Secret: \(loadedSecret)")
.padding()
}
.alert(isPresented: $showingAlert) {
Alert(title: Text("Error"), message: Text(alertMessage), dismissButton: .default(Text("OK")))
}
.navigationTitle("Secure Storage")
}
func saveSecret(_ secret: String) throws {
guard let data = secret.data(using: .utf8) else {
throw NSError(domain: "InvalidString", code: -1, userInfo: nil)
}
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account
]
let attributes: [String: Any] = [
kSecValueData as String: data
]
let status = SecItemCopyMatching(query as CFDictionary, nil)
if status == errSecSuccess {
// Item exists, update it
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
if updateStatus != errSecSuccess {
throw NSError(domain: "KeychainError", code: Int(updateStatus), userInfo: nil)
}
} else if status == errSecItemNotFound {
// Item does not exist, add it
var newItem = query
newItem[kSecValueData as String] = data
let addStatus = SecItemAdd(newItem as CFDictionary, nil)
if addStatus != errSecSuccess {
throw NSError(domain: "KeychainError", code: Int(addStatus), userInfo: nil)
}
} else {
throw NSError(domain: "KeychainError", code: Int(status), userInfo: nil)
}
}
func loadSecret() throws -> String? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
if status == errSecSuccess {
guard let data = item as? Data,
let secret = String(data: data, encoding: .utf8) else {
throw NSError(domain: "KeychainError", code: -2, userInfo: nil)
}
return secret
} else if status == errSecItemNotFound {
return nil
} else {
throw NSError(domain: "KeychainError", code: Int(status), userInfo: nil)
}
}
}
This solution uses SwiftUI for the user interface and Apple's Keychain Services API to securely save and load a secret string.
The saveSecret function first checks if the secret already exists in the Keychain. If it does, it updates the stored data. If not, it adds a new item. Errors during these operations throw exceptions that are caught and shown as alerts.
The loadSecret function tries to find the secret in the Keychain and returns it as a string. If not found, it returns nil.
The UI has a TextField for input, two buttons to save and load, and a Text view to display the loaded secret. Alerts inform the user if something goes wrong.