Modelando o estado do aplicativo usando objetos Store no SwiftUI

Nesta semana, quero falar sobre a modelagem de uma camada de dados no SwiftUI. Já terminei o trabalho no meu primeiro aplicativo, criado usando apenas o SwiftUI. Agora posso compartilhar a maneira de criar uma camada de modelo usando os objetos Store que usei ao desenvolver o aplicativo NapBot.

Armazenar Objeto


Os objetos de armazenamento são responsáveis ​​por manter o estado e fornecer a ação para alterá-lo. Você pode ter quantos objetos da loja forem necessários, de preferência eles são simples e responsáveis ​​por uma pequena parte do estado do seu aplicativo. Por exemplo, você pode ter o SettingsStore para salvar o estado das configurações do usuário e TodoStore para salvar tarefas personalizadas.

Para criar um objeto Store, você deve criar uma classe que esteja em conformidade com o protocolo ObservableObject . O protocolo ObservableObject permite que o SwiftUI observe e responda às alterações de dados. Para saber mais sobre o ObservableObject, consulte o artigo " Gerenciando o fluxo de dados no SwiftUI ". Vejamos um exemplo simples de um 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) } } 

No exemplo de código acima, temos a classe SettingsStore , que fornece acesso às configurações do usuário. Também usamos didChangeNotification para notificar o SwiftUI sempre que o usuário altera as configurações padrão.

Uso prolongado


Vejamos outro uso do objeto store criando um aplicativo Todo simples. Precisamos criar um objeto de armazenamento que armazene uma lista de tarefas e forneça ações para alterá-las, por exemplo, excluindo-as e filtrando-as.

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

Há uma classe TodosStore em conformidade com o protocolo ObservableObject. TodosStore fornece várias ações para mudar seu estado, podemos usar esses métodos a partir de nossas visualizações. Por padrão, o SwiftUI atualiza a exibição sempre que o campo @Published é alterado . É por isso que a matriz de elementos Todo é designada como @Published . Assim que adicionarmos ou removermos elementos dessa matriz, o SwiftUI atualizará a visualização inscrita no TodosStore .

Agora você pode criar uma exibição que exibe uma lista de tarefas e ações como marcar uma tarefa como concluída, excluir e alterar a ordem em que as tarefas são exibidas. Vamos começar criando uma exibição que exibe o título da tarefa e uma opção para marcar a tarefa como concluída.

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

No exemplo acima, Binding foi usado para fornecer uma referência, por exemplo, acesso a um tipo de valor. Em outras palavras, conceda acesso de gravação ao elemento todo. TodoItemView não possui uma instância da estrutura Todo, mas possui acesso de gravação ao TodoStore por meio de Ligação .

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

Agora temos o TodosView , um elemento que usa o componente List para exibir tarefas. O componente Lista também fornece reordenação e exclusão. Outra coisa interessante é a função indexed () . Esta função retorna uma coleção de elementos com seus índices. Nós o usamos para acessar itens da loja através do Binding. Aqui está a fonte completa desta extensão.

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

O ambiente é um candidato ideal para armazenar objetos de loja. O ambiente pode dividi-los entre várias visualizações sem implementação explícita através do método init . Para saber mais sobre os benefícios do ambiente no SwiftUI, consulte o artigo " Recursos do ambiente no SwiftUI ".

todos-screenshots

Conclusão


Este artigo discutiu uma maneira de modelar o estado de um aplicativo usando vários objetos de armazenamento . Eu realmente gosto da simplicidade dessa abordagem e de como é fácil dimensionar seu aplicativo adicionando mais objetos de armazenamento. Espero que você tenha gostado deste artigo.

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


All Articles