Modelado del estado de la aplicación utilizando objetos Store en SwiftUI

Esta semana quiero hablar sobre modelar una capa de datos en SwiftUI. Ya he terminado de trabajar en mi primera aplicación, que creo usando solo SwiftUI. Ahora puedo compartir la forma de crear una capa modelo utilizando los objetos de la Tienda que utilicé al desarrollar la aplicación NapBot.

Almacenar objeto


Los objetos de la tienda son responsables de mantener el estado y proporcionar la acción para cambiarlo. Puede tener tantos objetos Store como necesite, preferiblemente son simples y responsables de una pequeña parte del estado de su aplicación. Por ejemplo, puede tener SettingsStore para guardar el estado de la configuración del usuario y TodoStore para guardar tareas personalizadas.

Para crear un objeto Store, debe crear una clase que se ajuste al protocolo ObservableObject . El protocolo ObservableObject permite a SwiftUI observar y responder a los cambios de datos. Para obtener más información sobre ObservableObject, consulte el artículo " Gestión del flujo de datos en SwiftUI ". Veamos un ejemplo simple de un objeto 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) } } 

En el ejemplo de código anterior, tenemos la clase SettingsStore , que proporciona acceso a la configuración del usuario. También utilizamos didChangeNotification para notificar a SwiftUI cada vez que el usuario cambia la configuración predeterminada.

Uso extendido


Veamos otro uso del objeto de la tienda creando una aplicación simple de Todo . Necesitamos crear un objeto de tienda que almacene una lista de tareas y proporcione acciones para cambiarlas, por ejemplo, eliminarlas y filtrarlas.

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

Hay una clase TodosStore que se ajusta al protocolo ObservableObject. TodosStore proporciona varias acciones para cambiar su estado, podemos usar estos métodos desde nuestras vistas. De manera predeterminada, SwiftUI actualiza la vista cada vez que cambia el campo @Published . Es por eso que la matriz de elementos de Todo se designa como @Published . Tan pronto como agreguemos o eliminemos elementos de esta matriz, SwiftUI actualizará la vista suscrita a TodosStore .

Ahora puede crear una vista que muestre una lista de tareas y acciones como marcar una tarea como completada, eliminar y cambiar el orden en que se muestran las tareas. Comencemos creando una vista que muestre el título de la tarea y un interruptor para marcar la tarea como completada.

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

En el ejemplo anterior, el enlace se utilizó para proporcionar una referencia, por ejemplo, el acceso a un tipo de valor. En otras palabras, otorgue acceso de escritura al elemento todo. TodoItemView no posee una instancia de la estructura Todo, pero tiene acceso de escritura a TodoStore a través de 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 = "" } } 

Ahora tenemos TodosView , un elemento que usa el componente Lista para mostrar tareas. El componente Lista también proporciona reordenamiento y eliminación. Otra cosa interesante es la función indexed () . Esta función devuelve una colección de elementos con sus índices. Lo usamos para acceder a artículos en la tienda a través de Binding. Aquí está la fuente completa de esta extensión.

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

El entorno es un candidato ideal para almacenar objetos de la tienda. El entorno puede dividirlos entre múltiples vistas sin una implementación explícita a través del método init . Para obtener más información sobre los beneficios del medio ambiente en SwiftUI, consulte el artículo " Características del medio ambiente en SwiftUI ".

todos-screenshots

Conclusión


Este artículo discutió una forma de modelar el estado de una aplicación utilizando múltiples objetos de la tienda . Realmente me gusta la simplicidad de este enfoque y lo fácil que es escalar su aplicación agregando más objetos de la tienda. Espero que hayas disfrutado este artículo.

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


All Articles