SwiftData : Standardinstanzen beim App-Start erzeugen


Die Programmierung mit SwiftData ist so einfach, dass es sich geradezu anbietet, sämtliche Daten einer Anwendung damit zu speichern – vielleicht sogar einschließlich der Programmeinstellungen.

Bei der Entwicklung gibt es jedoch zwei Herausforderungen zu meistern: Erstens sollten die Einstellungen in der Anwendung einzigartig sein. Im Gegensatz zu vielen anderen Anwendungsfällen wird das Framework hier nicht verwendet, um eine Vielzahl von Objekten zu speichern, sondern lediglich eine einzige Instanz. Diese zu verwalten ist an sich unkompliziert. Eine größere Herausforderung stellt der erste Start der Anwendung dar. Wenn im ModelContainer noch kein Einstellungen-Objekt existiert, führt der Zugriff darauf unweigerlich zu einem Fehler – ein Szenario, das unbedingt vermieden werden muss. Stattdessen sollte die Anwendung mit Standardwerten für die Einstellungen starten. Werden diese Standardwerte automatisch dem ModelContainer hinzugefügt, können die Einstellungen im gesamten Programm problemlos genutzt werden. Änderungen werden anschließend automatisch vom SwiftData-Framework gespeichert.

Zunächst wird eine Klasse benötigt, die als Datenmodell dient. Der folgende Code zeigt die Klasse AppConfig aus einem meiner Projekte. Über die Eigenschaft isShowingInspector wird festgehalten, ob ein Inspektor-Panel in der grafischen Oberfläche geöffnet oder geschlossen war. Wird die Anwendung mit einem geöffneten Panel beendet, soll dieses beim Neustart des Programms ebenfalls wieder sichtbar sein.

Ob SwiftUI eine solche Funktionalität automatisch unterstützt, kann ich nicht sicher sagen – in meinem Fall hat es nicht funktioniert.

import Foundation
import SwiftData

@Model
public class AppConfig {
    
    @Attribute(.unique) public var Id = UUID()
    
    public var isShowingInspector : Bool = false
    
    init() {
    }
}

Im nächsten Schritt muss der ModelContainer für die Anwendung erstellt werden. Dies gestaltet sich etwas komplexer als in vielen anderen Anwendungen, da bei jedem Start überprüft werden soll, ob bereits eine Instanz der Einstellungen existiert. Falls nicht, muss diese Instanz erzeugt werden.

Der folgende Codeblock zeigt die create-Methode der Klasse ApplicationModelContainer. Hier sind insbesondere drei Schritte von Bedeutung: Zunächst wird ein Schema erstellt, das aus den Klassen besteht, die von SwiftData verwaltet werden sollen. Anschließend wird eine ModelConfiguration erzeugt. Danach folgt die Erstellung des ModelContainers, der aus dem Schema und der ModelConfiguration generiert wird.

import Foundation
import SwiftData

actor ApplicationModelContainer {
    
    @MainActor
    static func create() -> ModelContainer {
        
        let schema = Schema([
            AppConfig.self
        ])
        
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            let container = try ModelContainer(for: schema, configurations: [modelConfiguration])
            checkForDefaults(container: container)
            return container
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }
}

Bevor der ModelContainer von der Klasse zurückgegeben wird, wird die Methode checkForDefaults aufgerufen. Der Inhalt dieser Methode ist überraschend einfach: Mit einer Abfrage (fetchCount) auf den ModelContext wird die Anzahl der Objekte vom Typ AppConfig ermittelt. Ist die Anzahl gleich null, wird ein neues Objekt der Klasse AppConfig erstellt und dem Kontext hinzugefügt.

Optional könnte man an dieser Stelle auch prüfen, ob sich möglicherweise mehrere Instanzen der Einstellungen im Kontext befinden, um überflüssige Einträge zu entfernen. Auf diese Prüfung wurde jedoch verzichtet, da eine solche Situation gar nicht erst auftreten sollte.

private static func checkForDefaults(container : ModelContainer) {
       
    let settingsCount = (try? container.mainContext.fetchCount(FetchDescriptor<Settings>())) ?? 0
    if  settingsCount == 0 {
        print("No Settings found. Creating default")
        container.mainContext.insert( Settings())
    }
               
    let configCount = (try? container.mainContext.fetchCount(FetchDescriptor<AppConfig>())) ?? 0
    if  configCount == 0 {
        print("No AppConfig found. Creating default")
        container.mainContext.insert( AppConfig())
    }
}

Der ModelContext wird in der App-Klasse des Projekts der Scene hinzugefügt. Da die App-Klasse den Startpunkt des Programms darstellt, steht der ModelContext – einschließlich der Einstellungen-Instanz – der gesamten Anwendung zur Verfügung.

import SwiftUI
import SwiftData

@main
struct FileNameFixerApp: App {
    
    var contentViewStore = ContentViewStore()
    
    var body: some Scene {
        WindowGroup {
            NavigationManagerView()
                .background(VisualEffectView())
        }
        .modelContainer(ApplicationModelContainer.create())
        .environment(contentViewStore)
    }
}

Die Einstellungen, wie beispielsweise die spezifische Eigenschaft, können anschließend problemlos in jeder SwiftUI-Klasse verwendet werden. Im folgenden Codebeispiel gibt es einen kleinen Wrapper, der den Umgang mit dieser Eigenschaft weiter vereinfacht.

Ein wichtiger Punkt ist, dass eine Abfrage (Query) immer eine Liste von Ergebnissen zurückliefert. In diesem speziellen Fall enthält diese Liste jedoch stets genau einen Eintrag. Deshalb kann hier ohne Weiteres die first-Eigenschaft genutzt werden, um direkt auf das gewünschte Objekt zuzugreifen.

struct ContentView: View, FileInfoViewActionDelegateProtocol {
    
    // SwiftData
    @Environment(\.modelContext) private var modelContext
    @Query private var appconfig : [AppConfig]
    
    var body: some View {
        
        var isShowingInspector :Bool {
            get { return self.appconfig.first!.isShowingInspector }
            set {  self.appconfig.first!.isShowingInspector = newValue  }
        }

…

Resümee
In diesem Tutorial wurde gezeigt, wie man mit SwiftData beim Programmstart automatisch eine Default-Instanz für App-Einstellungen erstellt. Durch die Überprüfung des ModelContexts und das Hinzufügen von Standardwerten wird sichergestellt, dass die Anwendung immer mit einer gültigen Konfiguration startet. Dieses Vorgehen bietet eine einfache und zuverlässige Lösung für die Verwaltung von Standardeinstellungen.

Der in diesem Beispiel gezeigte Code ist Teil des Projektes FileNameFixer. Es steht auf GitHub kostenlos zur Verfügung.

Veröffentlicht am: 01.01.2025


© 2025 Holger Hinzberg Contact