Contents

edonv/user-default-entries

Usage

.package(url: "https://github.com/edonv/user-default-entries.git", from: "0.0.0")
.product(name: "UserDefault", package: "user-default-entries")

Macro vs Manual

To use the @UserDefault property wrapper, you need to have added a get/set property to UserDefaults via an extension. These can be defined manually or with the packaged @DefaultEntry macro.

Macro:

import DefaultEntry

extension UserDefaults {
    @DefaultEntry("defaultValue")
    var exampleWithDefault: String

    @DefaultEntry<String>
    var exampleWithoutDefault: String?

    @DefaultEntry(123)
    var exampleIntWithDefault: Int

    @DefaultEntry<Int>(prefixedWith: "customPrefix_")
    var exampleIntWithoutDefault: Int?
}

or, manually:

extension UserDefaults {
    var exampleWithDefault: String {
        get { String(withKey: "key_exampleWithDefault", in: self) ?? "defaultValue" }
        set { newValue.store(in: self, withKey: "key_exampleWithDefault") }
    }
    
    var exampleWithoutDefault: String? {
        get { String(withKey: "key_exampleWithoutDefault", in: self) }
        set { newValue.store(in: self, withKey: "key_exampleWithoutDefault") }
    }
    
    var exampleIntWithDefault: Int {
        get { Int(withKey: "key_exampleIntWithDefault", in: self) ?? "defaultValue" }
        set { newValue.store(in: self, withKey: "key_exampleValue") }
    }
    
    var exampleIntWithoutDefault: Int? {
        get { Int(withKey: "customPrefix_exampleIntWithoutDefault", in: self) }
        set { newValue.store(in: self, withKey: "customPrefix_exampleIntWithoutDefault") }
    }
}

Then, in a SwiftUI view, you can do the following:

import SwiftUI
import UserDefault

struct ExampleView: View {
    @UserDefault(\.exampleWithDefault)
    private var exampleWithDefault // implied to be a String
    
    @UserDefault(\.exampleWithoutDefault)
    private var exampleWithoutDefault // implied to be an optional String

    var body: some View {
        VStack {
            TextField("Example Field", text: $exampleWithDefault)

            Text(exampleWithoutDefault ?? "Value is empty")
        }        
    }
}

UserDefaultable

The @UserDefault property wrapper relies on both the @DefaultEntry macro (or manual entry) and the UserDefaultable protocol. Any type used must conform to UserDefaultable. If it's a custom type, it must explicitly conform to UserDefaultable and one of RawRepresentable or Codable. See the following section for more details on those.

RawRepresentable/Codable

UserDefaultable supports types that conform to RawRepresentable (whose RawValue types conform to UserDefaultable) and Codable, but due to Swift language restrictions, you still have to explicitly add conformance of UserDefaultable to your own type. The protocol requirements will be automatically synthesized for you.

Notes

NSNumber

Out of the box, NSNumber is supported by UserDefaults, but due to Swift language restrictions when it comes to adding initializers for a protocol to an existing non-final class, it's not possible to add UserDefaultable conformance to NSNumber. I'd recommend using Int, Float, or Double instead.

To-Do's

  • [x] Write a macro equivalent to @Entry

- `@UserDefaultsEntry(_ key: String, in userDefaults: UserDefaults? = nil)

  • [ ] Add conformance of UserDefaultable to BinaryInteger and BinaryFloatingPoint types.
  • [ ] Add DocC content from README.

Package Metadata

Repository: edonv/user-default-entries

Default branch: main

README: README.md