iosswiftswiftuiuserdefaults

SwiftUI: What is @AppStorage property wrapper


I used to save important App data like login credentials into UserDefaults using the following statement:

UserDefaults.standard.set("sample@email.com", forKey: "emailAddress")

Now, I have come to know SwiftUI has introduced new property wrapper called:

@AppStorage

Could anyone please explain how the new feature works?


Solution

  • AppStorage

    @AppStorage is a convenient way to save and read variables from UserDefaults and use them in the same way as @State properties. It can be seen as a @State property which is automatically saved to (and read from) UserDefaults.

    You can think of the following:

    @AppStorage("emailAddress") var emailAddress: String = "sample@email.com"
    

    as an equivalent of this (which is not allowed in SwiftUI and will not compile):

    @State var emailAddress: String = "sample@email.com" {
        get {
            UserDefaults.standard.string(forKey: "emailAddress")
        }
        set {
            UserDefaults.standard.set(newValue, forKey: "emailAddress")
        }
    }
    

    Note that @AppStorage behaves like a @State: a change to its value will invalidate and redraw a View.

    By default @AppStorage will use UserDefaults.standard. However, you can specify your own UserDefaults store:

    @AppStorage("emailAddress", store: UserDefaults(...)) ...
    

    Unsupported types (e.g., Array):

    As mentioned in iOSDevil's answer, AppStorage is currently of limited use:

    types you can use in @AppStorage are (currently) limited to: Bool, Int, Double, String, URL, Data

    If you want to use any other type (like Array), you can add conformance to RawRepresentable:

    extension Array: RawRepresentable where Element: Codable {
        public init?(rawValue: String) {
            guard let data = rawValue.data(using: .utf8),
                  let result = try? JSONDecoder().decode([Element].self, from: data)
            else {
                return nil
            }
            self = result
        }
    
        public var rawValue: String {
            guard let data = try? JSONEncoder().encode(self),
                  let result = String(data: data, encoding: .utf8)
            else {
                return "[]"
            }
            return result
        }
    }
    

    Demo:

    struct ContentView: View {
        @AppStorage("itemsInt") var itemsInt = [1, 2, 3]
        @AppStorage("itemsBool") var itemsBool = [true, false, true]
    
        var body: some View {
            VStack {
                Text("itemsInt: \(String(describing: itemsInt))")
                Text("itemsBool: \(String(describing: itemsBool))")
                Button("Add item") {
                    itemsInt.append(Int.random(in: 1...10))
                    itemsBool.append(Int.random(in: 1...10).isMultiple(of: 2))
                }
            }
        }
    }
    

    Useful links: