@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 = 05. 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
| 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
@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