Skip to Content

@AppStorage trong SwiftUI

1. Giới thiệu

@AppStorage lưu trữ data vào UserDefaults, persist qua app restarts.

struct ContentView: View { @AppStorage("username") var username = "" @AppStorage("isDarkMode") var isDarkMode = false var body: some View { VStack { TextField("Username", text: $username) Toggle("Dark Mode", isOn: $isDarkMode) } } }

2. Supported Types

// String @AppStorage("name") var name = "" // Int @AppStorage("count") var count = 0 // Double @AppStorage("score") var score = 0.0 // Bool @AppStorage("isEnabled") var isEnabled = false // URL @AppStorage("lastURL") var lastURL: URL? // Data @AppStorage("data") var data: Data?

3. Với RawRepresentable

Enum

enum Theme: String { case light, dark, system } struct SettingsView: View { @AppStorage("theme") var theme: Theme = .system var body: some View { Picker("Theme", selection: $theme) { Text("Light").tag(Theme.light) Text("Dark").tag(Theme.dark) Text("System").tag(Theme.system) } } }

Custom Types

struct UserPreferences: Codable, RawRepresentable { var fontSize: Int = 16 var showNotifications: Bool = true var rawValue: String { guard let data = try? JSONEncoder().encode(self), let string = String(data: data, encoding: .utf8) else { return "{}" } return string } init?(rawValue: String) { guard let data = rawValue.data(using: .utf8), let decoded = try? JSONDecoder().decode(UserPreferences.self, from: data) else { return nil } self = decoded } init() { } } struct SettingsView: View { @AppStorage("preferences") var preferences = UserPreferences() var body: some View { Form { Stepper("Font Size: \(preferences.fontSize)", value: $preferences.fontSize, in: 12...24) Toggle("Show Notifications", isOn: $preferences.showNotifications) } } }

4. Custom UserDefaults Store

// Dùng App Group cho sharing data giữa app và widget @AppStorage("score", store: UserDefaults(suiteName: "group.com.example.app")) var score = 0

5. Settings Screen

struct SettingsView: View { @AppStorage("username") var username = "" @AppStorage("email") var email = "" @AppStorage("notifications") var notifications = true @AppStorage("theme") var theme = "system" @AppStorage("language") var language = "en" var body: some View { NavigationStack { Form { Section("Profile") { TextField("Username", text: $username) TextField("Email", text: $email) } Section("Preferences") { Toggle("Notifications", isOn: $notifications) Picker("Theme", selection: $theme) { Text("Light").tag("light") Text("Dark").tag("dark") Text("System").tag("system") } Picker("Language", selection: $language) { Text("English").tag("en") Text("Vietnamese").tag("vi") } } Section { Button("Reset Settings", role: .destructive) { resetSettings() } } } .navigationTitle("Settings") } } func resetSettings() { username = "" email = "" notifications = true theme = "system" language = "en" } }

6. Onboarding Flow

struct ContentView: View { @AppStorage("hasCompletedOnboarding") var hasCompletedOnboarding = false var body: some View { if hasCompletedOnboarding { MainView() } else { OnboardingView(isComplete: $hasCompletedOnboarding) } } } struct OnboardingView: View { @Binding var isComplete: Bool @State private var currentPage = 0 var body: some View { VStack { TabView(selection: $currentPage) { OnboardingPage(title: "Welcome", description: "...") .tag(0) OnboardingPage(title: "Features", description: "...") .tag(1) OnboardingPage(title: "Get Started", description: "...") .tag(2) } .tabViewStyle(.page) if currentPage == 2 { Button("Continue") { isComplete = true } .buttonStyle(.borderedProminent) } } } }

7. Recent Items

struct RecentItemsView: View { @AppStorage("recentSearches") var recentSearchesData: Data = Data() var recentSearches: [String] { get { (try? JSONDecoder().decode([String].self, from: recentSearchesData)) ?? [] } set { recentSearchesData = (try? JSONEncoder().encode(newValue)) ?? Data() } } func addSearch(_ query: String) { var searches = recentSearches searches.removeAll { $0 == query } searches.insert(query, at: 0) searches = Array(searches.prefix(10)) // Keep last 10 recentSearchesData = (try? JSONEncoder().encode(searches)) ?? Data() } var body: some View { List { ForEach(recentSearches, id: \.self) { search in Text(search) } } } }

8. So sánh Storage Options

OptionPersistSizeUse Case
@StateAnyUI state
@AppStorageSmallSettings, preferences
SwiftDataLargeComplex data
KeychainSmallSensitive data

📝 Tóm tắt

  • @AppStorage("key") lưu vào UserDefaults
  • Persist qua app restarts
  • Dùng cho settings, preferences, simple data
  • Custom types cần conform RawRepresentable
  • Không dùng cho sensitive data (passwords)
Last updated on