Modélisation de l'état de l'application à l'aide d'objets Store dans SwiftUI

Cette semaine, je veux parler de la modélisation d'une couche de données dans SwiftUI. J'ai déjà fini de travailler sur ma toute première application, que je crée en utilisant uniquement SwiftUI. Maintenant, je peux partager la façon de créer une couche de modèle à l'aide des objets Store que j'ai utilisés lors du développement de l'application NapBot.

Objet de magasin


Les objets de magasin sont chargés de maintenir l'état et de fournir l'action pour le changer. Vous pouvez avoir autant d'objets Store que nécessaire, de préférence ils sont simples et responsables d'une petite partie de l'état de votre application. Par exemple, vous pouvez avoir SettingsStore pour enregistrer l'état des paramètres utilisateur et TodoStore pour enregistrer des tâches personnalisées.

Pour créer un objet Store, vous devez créer une classe conforme au protocole ObservableObject . Le protocole ObservableObject permet à SwiftUI d'observer et de répondre aux changements de données. Pour en savoir plus sur ObservableObject, consultez l'article " Gestion du flux de données dans SwiftUI ". Regardons un exemple simple d'un objet SettingsStore .

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) } } 

Dans l'exemple de code ci-dessus, nous avons la classe SettingsStore , qui donne accès aux paramètres utilisateur. Nous utilisons également didChangeNotification pour informer SwiftUI chaque fois que l'utilisateur modifie les paramètres par défaut.

Utilisation étendue


Voyons une autre utilisation de l'objet de magasin en créant une application Todo simple. Nous devons créer un objet de stockage qui stocke une liste de tâches et fournit des actions pour les modifier, par exemple, les supprimer et les filtrer.

 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 } } } 

Il existe une classe TodosStore conforme au protocole ObservableObject. TodosStore propose plusieurs actions pour changer son état, nous pouvons utiliser ces méthodes à partir de nos vues. Par défaut, SwiftUI met à jour la vue chaque fois que le champ @Published change . C'est pourquoi le tableau des éléments Todo est désigné comme @Published . Dès que nous ajoutons ou supprimons des éléments de ce tableau, SwiftUI mettra à jour la vue abonnée à TodosStore .

Vous pouvez maintenant créer une vue qui affiche une liste de tâches et des actions telles que marquer une tâche comme terminée, supprimer et changer l'ordre dans lequel les tâches sont affichées. Commençons par créer une vue qui affiche le titre de la tâche et un commutateur pour marquer la tâche comme terminée.

 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) } } } } 

Dans l'exemple ci-dessus, la liaison a été utilisée pour fournir une référence, par exemple l'accès à un type de valeur. En d'autres termes, accordez un accès en écriture à l'élément todo. TodoItemView ne possède pas d'instance de la structure Todo, mais il a un accès en écriture à TodoStore via Binding .

 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 = "" } } 

Nous avons maintenant TodosView , un élément qui utilise le composant List pour afficher les tâches. Le composant List fournit également la réorganisation et la suppression. Une autre chose intéressante est la fonction indexée () . Cette fonction renvoie une collection d'éléments avec ses indices. Nous l'utilisons pour accéder aux articles de la boutique via Binding. Voici la source complète de cette extension.

 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) } } 

L'environnement est un candidat idéal pour stocker des objets de magasin. L'environnement peut les séparer entre plusieurs vues sans implémentation explicite via la méthode init . Pour en savoir plus sur les avantages de l'environnement dans SwiftUI, consultez l'article « Fonctions d'environnement dans SwiftUI ».

todos-captures d'écran

Conclusion


Cet article explique comment modéliser l'état d'une application à l'aide de plusieurs objets de magasin . J'aime vraiment la simplicité de cette approche et la facilité avec laquelle il est possible de faire évoluer votre application en ajoutant plus d'objets de magasin. J'espère que cet article vous a plu.

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


All Articles