A SwiftUI property wrapper that keeps a Codable value in a local JSON file. It works like @State, but the value is saved to disk when it changes.
- iOS 14+ / macOS 11+
- Swift 5.5+
Add the package with Swift Package Manager:
dependencies: [
.package(url: "https://github.com/drewster99/NCCCodableStorage.git", from: "1.1.2")
]Then add "NCCCodableStorage" to your target's dependencies.
Declare the property inside a SwiftUI View. The storage behind it is a @StateObject, so it is created once per view identity and only works correctly when SwiftUI installs it in a view. Outside a View, each access creates a new storage object and changes are not kept in memory.
import SwiftUI
import NCCCodableStorage
struct Profile: Codable {
var name: String = "Mister Default"
var age: Double = 29
}
struct ProfileView: View {
// Loads profile.json from the Documents directory if it exists and decodes;
// otherwise starts with Profile(). Changes are written 2 seconds after the last edit.
@NCCCodableStorage(defaultValue: Profile(), filename: "profile.json") var profile: Profile
var body: some View {
TextField("Name", text: $profile.name)
}
}defaultValue:reads the existing file when the storage is created. If the file is missing or can't be decoded,defaultValueis used.initialValue:or a value after the declaration (var x: T = value) does not read the file. The property starts with the given value and the next automatic write overwrites any existing file.
// Loads the saved array, or starts empty.
@NCCCodableStorage(defaultValue: [], filename: "items.json") var items: [Profile]
// Starts with 8.675309 without reading someURL. Equivalent to:
// @NCCCodableStorage(url: someURL) var jenny: Double = 8.675309
@NCCCodableStorage(initialValue: 8.675309, url: someURL) var jenny: DoubleUse filename: with an optional directoryType: (default .documentDirectory, user domain), or pass a full file url:. The parent directory of a custom URL must already exist.
@NCCCodableStorage(defaultValue: [], filename: "cache.json", directoryType: .cachesDirectory)
var cachedItems: [String]| Mode | When changes are written |
|---|---|
.afterIdle(seconds:) (default: .afterIdle(seconds: 2.0)) |
Once the value has not changed for the given time. Pending changes are also written when the app moves to the background or terminates, and when the storage is released. |
.immediate |
Synchronously, on every change. |
.manual |
Only when you call save(). |
@NCCCodableStorage(defaultValue: [], filename: "items.json", updateMode: .afterIdle(seconds: 5.0))
var items: [Profile]
@NCCCodableStorage(filename: "draft.json", updateMode: .manual) var draft: [Profile] = []
// Later, through the wrapper:
try _draft.save()
try _draft.load()save() writes synchronously and throws on failure. load() replaces the value with the file's contents and throws if the file can't be read or decoded.
A failed automatic write (.immediate, .afterIdle, or the flush when the app moves to the background, terminates, or the storage is released) logs a fault with os.Logger (subsystem com.nuclearcyborg.NCCCodableStorage) and then terminates the app with fatalError. To handle write errors yourself, use .manual and call save(), which throws.
Files are written atomically, so a failed or interrupted write leaves the previous file intact.
MIT. See LICENSE.