Modellieren des Anwendungsstatus mithilfe von Store-Objekten in SwiftUI

Diese Woche möchte ich über das Modellieren einer Datenschicht in SwiftUI sprechen. Ich habe bereits die Arbeit an meiner allerersten Anwendung beendet, die ich nur mit SwiftUI erstelle. Jetzt kann ich die Methode zum Erstellen einer Modellebene mithilfe der Store-Objekte freigeben, die ich bei der Entwicklung der NapBot-Anwendung verwendet habe.

Objekt speichern


Speicherobjekte sind dafür verantwortlich, den Status beizubehalten und die Aktion zum Ändern des Status bereitzustellen. Sie können so viele Store-Objekte haben, wie Sie benötigen. Diese sind vorzugsweise einfach und für einen kleinen Teil des Status Ihrer Anwendung verantwortlich. Beispielsweise haben Sie möglicherweise SettingsStore , um den Status der Benutzereinstellungen zu speichern, und TodoStore , um benutzerdefinierte Aufgaben zu speichern.

Um ein Store-Objekt zu erstellen, müssen Sie eine Klasse erstellen, die dem ObservableObject- Protokoll entspricht. Mit dem ObservableObject-Protokoll kann SwiftUI Datenänderungen beobachten und darauf reagieren. Weitere Informationen zu ObservableObject finden Sie im Artikel " Verwalten des Datenflusses in SwiftUI ". Schauen wir uns ein einfaches Beispiel für ein SettingsStore- Objekt an.

import Foundation import Combine final class SettingsStore: ObservableObject { let objectWillChange = PassthroughSubject<Void, Never>() @UserDefault(Constants.UserDefaults.sleepGoal, defaultValue: 8.0) var sleepGoal: Double @UserDefault(Constants.UserDefaults.notifications, defaultValue: true) var isNotificationsEnabled: Bool private var didChangeCancellable: AnyCancellable? override init() { super.init() didChangeCancellable = NotificationCenter.default .publisher(for: UserDefaults.didChangeNotification) .map { _ in () } .receive(on: DispatchQueue.main) .subscribe(objectWillChange) } } 

Im obigen Codebeispiel haben wir die SettingsStore- Klasse, die den Zugriff auf Benutzereinstellungen ermöglicht. Wir verwenden didChangeNotification auch, um SwiftUI zu benachrichtigen, wenn der Benutzer die Standardeinstellungen ändert.

Erweiterte Nutzung


Schauen wir uns eine andere Verwendung des Store-Objekts an, indem wir eine einfache Todo- App erstellen. Wir müssen ein Speicherobjekt erstellen, das eine Liste von Aufgaben speichert und Aktionen zum Ändern dieser Aufgaben bereitstellt, z. B. Löschen und Filtern.

 import Foundation import Combine struct Todo: Identifiable, Hashable { let id = UUID() var title: String var date: Date var isDone: Bool var priority: Int } final class TodosStore: ObservableObject { @Published var todos: [Todo] = [] func orderByDate() { todos.sort { $0.date < $1.date } } func orderByPriority() { todos.sort { $0.priority > $1.priority } } func removeCompleted() { todos.removeAll { $0.isDone } } } 

Es gibt eine TodosStore- Klasse, die dem ObservableObject-Protokoll entspricht. TodosStore bietet verschiedene Aktionen zum Ändern seines Status. Wir können diese Methoden aus unserer Sicht verwenden. Standardmäßig aktualisiert SwiftUI die Ansicht jedes Mal, wenn sich das Feld @Published ändert . Aus diesem Grund wird das Array der Todo- Elemente als @Published bezeichnet . Sobald wir Elemente zu diesem Array hinzufügen oder daraus entfernen, aktualisiert SwiftUI die für TodosStore abonnierte Ansicht .

Jetzt können Sie eine Ansicht erstellen, in der eine Liste von Aufgaben und Aktionen wie das Markieren einer Aufgabe als erledigt, das Löschen und Ändern der Reihenfolge, in der Aufgaben angezeigt werden, angezeigt werden. Beginnen wir mit der Erstellung einer Ansicht, in der der Titel der Aufgabe angezeigt wird, und einem Schalter, um die Aufgabe als erledigt zu markieren.

 import SwiftUI struct TodoItemView: View { let todo: Binding<Todo> var body: some View { HStack { Toggle(isOn: todo.isDone) { Text(todo.title.wrappedValue) .strikethrough(todo.isDone.wrappedValue) } } } } 

Im obigen Beispiel wurde die Bindung verwendet, um eine Referenz bereitzustellen, beispielsweise den Zugriff auf einen Werttyp. Mit anderen Worten, gewähren Sie Schreibzugriff auf das todo-Element. TodoItemView besitzt keine Instanz der Todo-Struktur, hat jedoch Schreibzugriff auf TodoStore durch Bindung .

 import SwiftUI struct TodosView: View { @EnvironmentObject var store: TodosStore @State private var draft: String = "" var body: some View { NavigationView { List { TextField("Type something...", text: $draft, onCommit: addTodo) ForEach(store.todos.indexed(), id: \.1.id) { index, _ in TodoItemView(todo: self.$store.todos[index]) } .onDelete(perform: delete) .onMove(perform: move) } .navigationBarItems(trailing: EditButton()) .navigationBarTitle("Todos") } } private func delete(_ indexes: IndexSet) { store.todos.remove(atOffsets: indexes) } private func move(_ indexes: IndexSet, to offset: Int) { store.todos.move(fromOffsets: indexes, toOffset: offset) } private func addTodo() { let newTodo = Todo(title: draft, date: Date(), isDone: false, priority: 0) store.todos.insert(newTodo, at: 0) draft = "" } } 

Jetzt haben wir TodosView , ein Element, das die List- Komponente zum Anzeigen von Aufgaben verwendet. Die List- Komponente ermöglicht auch das Neuordnen und Löschen. Eine weitere interessante Sache ist die indizierte () Funktion. Diese Funktion gibt eine Sammlung von Elementen mit ihren Indizes zurück. Wir verwenden es, um über die Bindung auf Artikel im Geschäft zuzugreifen. Hier ist die vollständige Quelle dieser Erweiterung.

 import Foundation struct IndexedCollection<Base: RandomAccessCollection>: RandomAccessCollection { typealias Index = Base.Index typealias Element = (index: Index, element: Base.Element) let base: Base var startIndex: Index { base.startIndex } var endIndex: Index { base.endIndex } func index(after i: Index) -> Index { base.index(after: i) } func index(before i: Index) -> Index { base.index(before: i) } func index(_ i: Index, offsetBy distance: Int) -> Index { base.index(i, offsetBy: distance) } subscript(position: Index) -> Element { (index: position, element: base[position]) } } extension RandomAccessCollection { func indexed() -> IndexedCollection<Self> { IndexedCollection(base: self) } } 

Die Umgebung ist ein idealer Kandidat zum Speichern von Geschäftsobjekten. Die Umgebung kann sie ohne explizite Implementierung über die init- Methode auf mehrere Ansichten aufteilen. Weitere Informationen zu den Vorteilen der Umgebung in SwiftUI finden Sie im Artikel „ Umgebungsfunktionen in SwiftUI “.

todos-Screenshots

Fazit


In diesem Artikel wurde eine Möglichkeit zum Modellieren des Status einer Anwendung mithilfe mehrerer Speicherobjekte erläutert. Ich mag die Einfachheit dieses Ansatzes und wie einfach es ist, Ihre Anwendung durch Hinzufügen weiterer Speicherobjekte zu skalieren. Ich hoffe, Ihnen hat dieser Artikel gefallen.

Source: https://habr.com/ru/post/de467675/


All Articles