ios – Finest strategy to retailer app settings in SwiftData

0
2
ios – Finest strategy to retailer app settings in SwiftData


I’ve an app that at the moment makes use of @AppStorage to retailer consumer settings. Quite a lot of settings ought to sync between gadgets so I added a generic mannequin to retailer consumer settings in SwiftData like so:

@Mannequin
class CloudSetting {
    var key: SettingItem
    var date: Date?
    var worth: Knowledge
    
    init(key: SettingItem, date: Date?, worth: T) {
        self.key = key
        self.date = date
        self.worth = attempt! JSONEncoder().encode(worth)
    }
    
    func getValue(as sort: T.Sort) -> T? {
        attempt? JSONDecoder().decode(sort, from: worth)
    }
    
    func setValue(_ newValue: T) {
        self.worth = attempt! JSONEncoder().encode(newValue)
    }
}

I already had an enum (SettingItem) that comprises all related settings that’s then used to arrange the defaults within the newly @Observable class “SettingsManager” that handles the creation and adjustments to the mannequin information. For this I created an exemplary property “useLiveActivity” to make accessing the setting within the view simpler:

@Observable
class SettingsManager {
    personal var modelContext: ModelContext
    personal var settings = [SettingItem: [CloudSetting]]()
    
    // Computed variables for simpler entry
    var useLiveActivity: Bool {
        get { getSetting(for: .useliveActivity)}
        set { setSetting(for: .useliveActivity, to: newValue)}
    }
    
    init(modelContext: ModelContext) {
        self.modelContext = modelContext
        fetchAndCreateSettings()
    }
    
    func getSetting(for key: SettingItem) -> T {
        (settings[key]?.first?.getValue(as: T.self))!
    }
    
    func setSetting(for key: SettingItem, to worth: Codable) {
        settings[key]?.first?.setValue(worth)
    }
    
    func fetchAndCreateSettings() {
        // Fetch present settings or if empty, create a brand new array occasion
        var existingSettings = (attempt? modelContext.fetch(FetchDescriptor())) ?? [CloudSetting]()
        for setting in SettingItem.allCases {
            // Test that setting will not be but saved in container, in any other case proceed with subsequent iteration
            guard (existingSettings.firstIndex(the place: {$0.key == setting}) == nil) else {
                proceed
            }
            
            // Add new setting to array and to container
            let setting = CloudSetting(key: setting, date: setting.defaultDate, worth: setting.defaultValue)
            modelContext.insert(setting)
            existingSettings.append(setting)
        }
        // Save after inserting obligatory parts
        attempt? modelContext.save()
        
        // Replace variable
        settings = Dictionary(grouping: existingSettings, by: { $0.key })
    }

The SettingsManager is then instantiated within the App Struct like so:

struct ArbeitszeitApp: App {
    /// Container holds the persistent modelContainer for all SwiftData
    @State personal var container: ModelContainer
    /// SettingsManager is a retailer for all settings that must be synced through CloudKit (SwiftData)
    @State personal var settingsManager: SettingsManager
    
    init() {
        let container = ArbeitszeitApp.createContainer()
        _container = State(wrappedValue: container)
        
        let settingsManager = SettingsManager(modelContext: container.mainContext)
        _settingsManager = State(wrappedValue: settingsManager)
    }
    
    var physique: some Scene {
        WindowGroup {
            ContentView()
                .setting(settingsManager)
        }
        .modelContainer(container)
    }
}

Then I can entry the information within the view like so and whereas this typically works as anticipated, I used to be questioning what could be one of the best strategy:

struct ManageLiveActivitiesView: View {
    @Surroundings(SettingsManager.self) personal var settingsManager
    @Bindable var settings: SettingsManager
    
    var physique: some View {
        Checklist {
                Toggle("", isOn: Binding(
                    get: {settingsManager.stay},
                    set: {settingsManager.stay = $0})
                Toggle("", isOn: $settingsManager.stay) 
            }
        }
    }
}

So the principle query(s) could be:

  • Is it extra widespread/higher to
    • use the SettingsManager occasion from the @Surroundings within the view on to entry and alter the setting however with the necessity to create an specific binding
    • or learn the SettingsManager occasion within the root view of the settings after which move it all the way down to the views to make dealing with the binding simpler?
  • Does it make sense to create helper variables for all settings within the SettingsManager for simpler entry within the view?
  • Or is there a greater strategy on the whole?

LEAVE A REPLY

Please enter your comment!
Please enter your name here