Persisting User Settings with SwiftData

I was wondering what the recommended way is to persist user settings with SwiftData?

It seems the SwiftData API is focused around querying for multiple objects, but what if you just want one UserSettings object that is persisted across devices say for example to store the user's age or sorting preferences.

Do we just create one object and then query for it or is there a better way of doing this?

Right now I am just creating:

import SwiftData

@Model
final class UserSettings {
   var age: Int = 0
   var sortAtoZ: Bool = true

   init(age: Int = 0, sortAtoZ: Bool = true) {
        self.age = age
        self.sortAtoZ = sortAtoZ
    }
}

In my view I am doing as follows:

import SwiftUI
import SwiftData

struct SettingsView: View {
   @Environment(\.modelContext) var context
   @Query var settings: [UserSettings]

   var body: some View {
      ForEach(settings) { setting in 
          let bSetting = Bindable(setting)
          Toggle("Sort A-Z", isOn: bSetting.sortAtoZ)
          TextField("Age", value: bSetting.age, format: .number)
      }
      .onAppear {
         if settings.isEmpty {
             context.insert(UserSettings(age: 0, sortAtoZ: true))
          }
      }
   }
}

Unfortunately, there are two issues with this approach:

  1. I am having to fetch multiple items when I only ever want one.
  2. Sometimes when running on a new device it will create a second UserSettings while it is waiting for the original one to sync from CloudKit.

AppStorage is not an option here as I am looking to persist for the user across devices and use CloudKit syncing.

Answered by DTS Engineer in 871937022

I'd like to comment the second question a bit, as it hasn't been addressed yet:

Sometimes when running on a new device it will create a second UserSettings while it is waiting for the original one to sync from CloudKit.

When you use SwiftData + CloudKit, under the hood, the framework uses NSPersistentCloudKitContainer to synchronize data. In that environment, duplicate data is sometimes inevitable, which is discussed in great details in the following section:

The solution is to de-duplicate the data. How to do so is discussed and demonstrated in the sample code.

Back to what to what you intent to achieve, my suggestion will be:

  1. If you plan to use SwiftData + CloudKit extensively in your app, you might need to handle duplicate data anyway. In this case, persisting user settings to your SwiftData store (and Synchronizing them via NSPersistentCloudKitContainer) makes a lot of sense.

  2. If the target is as simple as saving some Cloud-based user settings, I'd probably consider NSUbiquitousKeyValueStore instead, because, depending your concrete data structure, de-duplicating data in a Cloud environment can be a bit involved, and can unnecessarily complicate your app.

In the first case, your question 1 probably doesn't matter any more, because UserSettings can indeed have multiple items, which you need to de-duplicate.

In the second case, the following sample demonstrates how to use NSUbiquitousKeyValueStore. It isn't quite up to date, but the main idea is there:

With NSUbiquitousKeyValueStore, you might still need a way to update your SwiftUI views when the user settings change. You can do so by wrapping the user settings with an observable class + SwiftUI environment, as @joadan mentioned.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thank you for your post. After reviewing it, I have a suggestion for using UserDefaults instead, in my modest opinion and without knowing all your requirements.

If you intend to utilize SwiftData for this purpose, I recommend querying for the single instance of the model and binding it to your view. To achieve this, query for a single instance where a unique attribute, such as a stored value, matches a specific value. Retrieve the stored ID and use it in your filter to ensure that only one instance of the model is fetched and used within the view.

To facilitate future debugging, you may also consider deleting the existing model from CloudKit to eliminate any potential duplicates in your container. This can be done from the CloudKit Dashboard.

While UserDefaults may not be the most efficient solution for your specific requirements, I would be interested in understanding the reasons behind your decision to use SwiftData. Additionally, I would appreciate any recommendations from other developers regarding this matter.

Albert Pascual
  Worldwide Developer Relations.

I would wrap the UserSettings type inside an observable class and then use that class as an environment value

@Environment(SettingsManager.self) var settingsManager

Then this manager object could be made to handle all loading, updating and saving of the UserSettings object internally to have only one single source of truth.

I'd like to comment the second question a bit, as it hasn't been addressed yet:

Sometimes when running on a new device it will create a second UserSettings while it is waiting for the original one to sync from CloudKit.

When you use SwiftData + CloudKit, under the hood, the framework uses NSPersistentCloudKitContainer to synchronize data. In that environment, duplicate data is sometimes inevitable, which is discussed in great details in the following section:

The solution is to de-duplicate the data. How to do so is discussed and demonstrated in the sample code.

Back to what to what you intent to achieve, my suggestion will be:

  1. If you plan to use SwiftData + CloudKit extensively in your app, you might need to handle duplicate data anyway. In this case, persisting user settings to your SwiftData store (and Synchronizing them via NSPersistentCloudKitContainer) makes a lot of sense.

  2. If the target is as simple as saving some Cloud-based user settings, I'd probably consider NSUbiquitousKeyValueStore instead, because, depending your concrete data structure, de-duplicating data in a Cloud environment can be a bit involved, and can unnecessarily complicate your app.

In the first case, your question 1 probably doesn't matter any more, because UserSettings can indeed have multiple items, which you need to de-duplicate.

In the second case, the following sample demonstrates how to use NSUbiquitousKeyValueStore. It isn't quite up to date, but the main idea is there:

With NSUbiquitousKeyValueStore, you might still need a way to update your SwiftUI views when the user settings change. You can do so by wrapping the user settings with an observable class + SwiftUI environment, as @joadan mentioned.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Persisting User Settings with SwiftData
 
 
Q