@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 аутоматизује цео циклус: декларација у једној линији замењује 15–20 линија boilerplate кода. Штавише, @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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође