@AppStorage ใน SwiftUI เป็น property wrapper สำหรับทำงานกับ UserDefaults ที่ซิงค์ค่ากับ UI โดยอัตโนมัติ เมื่อคุณสมบัติที่ประกาศด้วย @AppStorage เปลี่ยนแปลง ค่าใหม่จะถูกบันทึกไปยัง UserDefaults ทันที และเมื่อ UserDefaults เปลี่ยนแปลงจากภายนอก — โดยวิดเจ็ตหรือส่วนขยาย — View จะถูกวาดใหม่โดยอัตโนมัติ ตาม Apple Developer Documentation (2025), @AppStorage รองรับ String, Int, Double, Bool, Data, URL และเวอร์ชันที่เป็นทางเลือก ให้การจัดเก็บการตั้งค่าผู้ใช้แบบปฏิกิริยาโดยไม่ต้องมีโค้ดสังเกตการณ์ด้วยตนเอง
ประเด็นสำคัญ
@AppStorage คือ property wrapper ที่ Apple แนะนำใน iOS 14 ซึ่งเชื่อมโยงคุณสมบัติของ View กับคีย์ใน UserDefaults เมื่ออ่านคุณสมบัติ SwiftUI จะโหลดค่าจาก UserDefaults ผ่านคีย์ที่ระบุ เมื่อเขียน จะบันทึกค่าใหม่และแจ้งให้ View ทราบว่าต้องวาดใหม่
ก่อน @AppStorage นักพัฒนาต้องอ่าน UserDefaults ด้วยตนเองใน onAppear สมัครรับ UserDefaults.didChangeNotification และอัปเดต @State เมื่อมีการเปลี่ยนแปลง @AppStorage จะทำให้วงจรทั้งหมดเป็นอัตโนมัติ: การประกาศหนึ่งบรรทัดแทนที่โค้ด boilerplate 15–20 บรรทัด นอกจากนี้ @AppStorage ยังให้การซิงค์สองทิศทาง — หากค่า UserDefaults เปลี่ยนแปลงจากกระบวนการอื่น (เช่น App Extension หรือ Widget) View ก็ยังคงได้รับการอัปเดต
ในทางสถาปัตยกรรม @AppStorage ถูกนำไปใช้เป็น DynamicProperty ซึ่งช่วยให้ SwiftUI ติดตามการพึ่งพาและวาด View ใหม่เมื่อค่าที่สังเกตเปลี่ยนแปลง ทำให้เหมาะสำหรับการจัดเก็บการตั้งค่าผู้ใช้: ภาษาอินเทอร์เฟซ การเปิด/ปิดฟีเจอร์ แท็บที่เลือกล่าสุด ชื่อผู้ใช้
แม้ว่า @AppStorage จะใช้ UserDefaults ภายใน แต่แนวทางการทำงานกับพื้นที่จัดเก็บนั้นแตกต่างกันโดยพื้นฐาน UserDefaults เป็น API ระดับต่ำที่ต้องการการจัดการด้วยตนเองในการอ่าน การเขียน และการแจ้งเตือนการเปลี่ยนแปลง @AppStorage เป็นนามธรรมของ SwiftUI ที่ให้พฤติกรรมแบบปฏิกิริยาทันที
UserDefaults เหมาะสำหรับการดำเนินการครั้งเดียว: โหลดการตั้งค่าเมื่อเริ่มต้นแอป เขียนการวิเคราะห์ แคชโทเค็น @AppStorage สำหรับการตั้งค่าที่ควร อัปเดต UI แบบปฏิกิริยา: สวิตช์ธีม การเลือกภาษา การบันทึกสถานะอินเทอร์เฟซ การใช้ UserDefaults โดยตรงภายใน View เป็นปฏิปักษ์รูปแบบ เนื่องจาก View ไม่ทราบเกี่ยวกับการเปลี่ยนแปลงหากไม่สมัครรับเพิ่มเติม
| พารามิเตอร์ | @AppStorage | UserDefaults |
|---|---|---|
| ปฏิกิริยา | อัตโนมัติ | ต้องการสมัครรับการแจ้งเตือน |
| Boilerplate | 1 บรรทัดต่อคุณสมบัติ | 15–20 บรรทัดต่อคุณสมบัติ |
| ประเภท | String, Int, Double, Bool, Data, URL | ทุกประเภท + วัตถุที่เก็บถาวร |
| ประเภทกำหนดเอง | ผ่าน RawRepresentable | ผ่าน NSKeyedArchiver |
| App Extension | ซิงค์อัตโนมัติ | สมัครรับด้วยตนเอง |
สำหรับการตั้งค่าอย่างง่ายด้วย UI แบบปฏิกิริยา @AppStorage เป็นตัวเลือกที่ต้องการ สำหรับข้อมูลที่ซับซ้อน (อาร์เรย์ พจนานุกรม วัตถุที่กำหนดเอง) ให้ใช้ UserDefaults ร่วมกับ @State และการสมัครรับการเปลี่ยนแปลงด้วยตนเอง หรือเปลี่ยนไปใช้ SwiftData / Core Data สำหรับการจัดเก็บที่มีโครงสร้าง
@AppStorage รองรับประเภทมาตรฐานที่ UserDefaults สามารถซีเรียลไลซ์ได้โดยตรง: String, Int, Double, Bool, Data, URL สำหรับแต่ละประเภทจะมีเวอร์ชันทางเลือก (String?, Int?, Double?, Bool?, Data?, URL?) ซึ่งช่วยให้แยกแยะระหว่าง "ไม่ได้ตั้งค่า" และ "ค่าว่าง"
สำหรับการจัดเก็บประเภทกำหนดเองที่สอดคล้องกับโปรโตคอล RawRepresentable @AppStorage ก็ทำงานโดยอัตโนมัติเช่นกัน หาก enum มี rawValue ประเภท String หรือ Int ก็สามารถใช้ได้โดยตรง: @AppStorage("theme") var theme: AppTheme = .system SwiftUI จะซีเรียลไลซ์/ดีซีเรียลไลซ์ค่าโดยอัตโนมัติผ่าน rawValue
enum AppTheme: String {
case system, light, dark
}
struct SettingsView: View {
@AppStorage("username") var username: String = "Guest"
@AppStorage("launchCount") var launchCount: Int = 0
@AppStorage("isDarkMode") var isDarkMode: Bool = false
@AppStorage("appTheme") var theme: AppTheme = .system
@AppStorage("lastOpened") var lastOpened: Date? = nil
var body: some View {
Form {
TextField("Username", text: $username)
Toggle("Dark mode", isOn: $isDarkMode)
Text("เปิดใช้งาน \(launchCount) ครั้ง")
}
}
}
ตัวอย่างใช้ @AppStorage ประเภทต่างๆ: String ที่มีค่าเริ่มต้น "Guest", Int สำหรับตัวนับการเปิด, Bool สำหรับธีมมืด, enum AppTheme ที่มี rawValue ประเภท String และ Date? ที่เป็นทางเลือกสำหรับเวลาที่เปิดล่าสุด แต่ละคุณสมบัติเชื่อมโยงกับ คีย์ UserDefaults ที่ระบุเป็นอาร์กิวเมนต์แรก ค่าเริ่มต้นจะถูกใช้หากไม่มีคีย์ในพื้นที่จัดเก็บในการเปิดครั้งแรก
ข้อดีหลักประการหนึ่งของ @AppStorage คือการสังเกตการเปลี่ยนแปลงของ UserDefaults โดยอัตโนมัติจาก แหล่งใดก็ได้ หาก App Extension หรือ Widget เปลี่ยนแปลงค่า @AppStorage ในแอปหลักจะได้รับการแจ้งเตือนและวาด View ใหม่ ซึ่งทำได้ผ่านกลไก KVO (Key-Value Observing) ที่ @AppStorage ตั้งค่าโดยอัตโนมัติบน UserDefaults.didChangeNotification
ในทางปฏิบัติหมายความว่าหากผู้ใช้เปลี่ยนการตั้งค่าใน Widget (เช่น เปิดใช้งานธีมมืด) แอปจะรับการเปลี่ยนแปลงทันที การซิงค์เดียวกันทำงานระหว่างแอปหลักและ Share Extension, Watch App หรือ Today Widget นักพัฒนาไม่จำเป็นต้องเขียนโค้ดสำหรับ การแลกเปลี่ยนข้อมูลระหว่างกระบวนการ — @AppStorage ทำโดยอัตโนมัติ
struct ThemeSettingView: View {
@AppStorage("isDarkMode") var isDarkMode: Bool = false
var body: some View {
VStack {
Toggle("Dark Mode", isOn: $isDarkMode)
.onChange(of: isDarkMode) { oldValue, newValue in
print("โหมดมืดเปลี่ยนเป็น \(newValue)")
}
}
}
}
Toggle เชื่อมโยงกับ $isDarkMode ผ่าน @AppStorage เมื่อสลับ ค่าจะถูกบันทึกไปยัง UserDefaults โดยอัตโนมัติภายใต้คีย์ "isDarkMode" ตัวปรับแต่ง .onChange อนุญาตให้ดำเนินการผลข้างเคียงเมื่อมีการเปลี่ยนแปลง — เช่น ส่งการวิเคราะห์หรืออัปเดต UI ของหน้าจออื่น หาก Widget เปลี่ยนคีย์เดียวกัน @AppStorage จะทริกเกอร์ onChange เช่นกัน เพื่อให้มั่นใจในความสอดคล้องของสถานะ
มาดูหน้าจอการตั้งค่าแอปแบบเต็มรูปแบบที่ใช้ @AppStorage เพื่อจัดเก็บการกำหนดค่าทั้งหมด ฟอร์มประกอบด้วยส่วนต่าง ๆ ที่มีการตั้งค่าหลายประเภท: ฟิลด์ข้อความ สวิตช์ ตัวนับ — ค่าทั้งหมดจะถูกบันทึกไปยัง UserDefaults โดยอัตโนมัติ
struct AppSettingsView: View {
@AppStorage("displayName") var displayName = ""
@AppStorage("notificationsEnabled") var notificationsEnabled = true
@AppStorage("maxResults") var maxResults = 25
@AppStorage("selectedTab") var selectedTab = "home"
var body: some View {
NavigationStack {
Form {
Section(header: Text("โปรไฟล์")) {
TextField("Display name", text: $displayName)
}
Section(header: Text("การตั้งค่า")) {
Toggle("Enable notifications",
isOn: $notificationsEnabled)
Stepper("Max results: \(maxResults)",
value: $maxResults,
in: 10...100,
step: 5)
}
Section {
Button("รีเซ็ตการตั้งค่า") {
UserDefaults.standard.removePersistentDomain(
forName: Bundle.main.bundleIdentifier!)
}
.tint(.red)
}
}
.navigationTitle("Settings")
}
}
}
ฟอร์มประกอบด้วยคุณสมบัติ @AppStorage สี่ประเภท: String สำหรับชื่อ, Bool สำหรับการแจ้งเตือน, Int สำหรับจำนวนผลลัพธ์ และ String สำหรับแท็บที่เลือก ตัวควบคุมทั้งหมดเชื่อมโยงกับคุณสมบัติผ่าน Binding ($displayName, $notificationsEnabled ฯลฯ) ปุ่ม "Reset settings" จะล้าง UserDefaults ทั้งหมดโดยการลบโดเมนแอป — หลังจากนั้น @AppStorage จะกลับไปใช้ค่าเริ่มต้นโดยอัตโนมัติ
struct SharedSettingsView: View {
let sharedDefaults = UserDefaults(suiteName: "group.com.example.app")
@AppStorage("widgetTheme", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetTheme: String = "ระบบ"
@AppStorage("widgetColor", store: UserDefaults(suiteName: "group.com.example.app")!)
var widgetColor: String = "น้ำเงิน"
var body: some View {
Form {
Picker("Widget theme", selection: $widgetTheme) {
Text("ระบบ").tag("system")
Text("สว่าง").tag("สว่าง")
Text("มืด").tag("มืด")
}
Picker("Accent color", selection: $widgetColor) {
Text("น้ำเงิน").tag("blue")
Text("เขียว").tag("เขียว")
Text("แดง").tag("แดง")
}
}
}
}
สำหรับ App Group (พื้นที่จัดเก็บที่ใช้ร่วมกันระหว่างแอปและส่วนขยาย) @AppStorage ยอมรับพารามิเตอร์ store: UserDefaults(suiteName:) ค่าจะถูกบันทึกในคอนเทนเนอร์ที่ใช้ร่วมกันซึ่งมีให้สำหรับแอปหลัก, Widget, Watch App และส่วนขยายอื่น ๆ ในกลุ่มเดียวกัน Widget สามารถอ่านการตั้งค่าเหล่านี้ และเมื่อเปลี่ยนแปลงในแอป Widget จะอัปเดตโดยอัตโนมัติผ่านกลไกการสังเกต UserDefaults
คำถามที่พบบ่อย
@State เก็บค่าไว้ในหน่วยความจำเท่านั้นและถูกรีเซ็ตเมื่อรีสตาร์ทแอป @AppStorage บันทึกค่าใน UserDefaults และกู้คืนเมื่อเปิดครั้งต่อไป ใช้ @State สำหรับข้อมูลหน้าจอชั่วคราว @AppStorage สำหรับการตั้งค่าที่ควรคงอยู่หลังการรีสตาร์ท
ได้ หาก Enum ใช้โปรโตคอล RawRepresentable ที่มี rawValue ประเภท String หรือ Int ตัวอย่าง: @AppStorage("theme") var theme: AppTheme = .system SwiftUI จะซีเรียลไลซ์ enum โดยอัตโนมัติผ่าน rawValue และกู้คืนเมื่อโหลด
เรียก UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!) สำหรับพื้นที่จัดเก็บมาตรฐานหรือ removeObject(forKey:) สำหรับคีย์เฉพาะ หลังจากล้าง คุณสมบัติ @AppStorage ทั้งหมดจะกลับไปใช้ค่าเริ่มต้นที่ระบุในการประกาศ
ได้ สำหรับการซิงค์ระหว่างแอปและส่วนขยาย ให้ใช้ App Group: @AppStorage("key", store: UserDefaults(suiteName: "group.com.example.app")!) Widget, Share Extension และ Watch App สามารถอ่านและเขียนใน UserDefaults เดียวกัน และการเปลี่ยนแปลงจะถูกติดตามโดยอัตโนมัติ
@AppStorage ใช้ UserDefaults ซึ่งออกแบบมาสำหรับ ข้อมูลปริมาณน้อย: การตั้งค่า โทเค็น ตัวนับ ขีดจำกัดที่แนะนำคือสูงสุด 100 KB ต่อแอป สำหรับข้อมูลที่มีโครงสร้างหรือขนาดใหญ่ (อาร์เรย์วัตถุ ไฟล์สื่อ) ให้ใช้ SwiftData, Core Data หรือระบบไฟล์
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม