@AppStorage
1. Giới thiệu
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “3. Với RawRepresentable”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
Phần tiêu đề “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
Phần tiêu đề “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 = 05. Settings Screen
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “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
Phần tiêu đề “8. So sánh Storage Options”| Option | Persist | Size | Use Case |
|---|---|---|---|
@State |
❌ | Any | UI state |
@AppStorage |
✅ | Small | Settings, preferences |
| SwiftData | ✅ | Large | Complex data |
| Keychain | ✅ | Small | Sensitive data |
📝 Tóm tắt
Phần tiêu đề “📝 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)