在SwiftUI中使用Store对象建模应用程序状态

这周,我想谈谈在SwiftUI中为数据层建模的过程。 我已经完成了仅使用SwiftUI创建的第一个应用程序的工作。 现在,我可以共享使用开发NapBot应用程序时使用的Store对象创建模型层的方法。

储存物件


存储对象负责维护状态并提供更改状态的操作。 您可以根据需要拥有任意数量的Store对象,希望它们简单并负责应用程序状态的一小部分。 例如,您可能具有SettingsStore来保存用户设置的状态,而TodoStore来保存自定义任务。

若要创建一个Store对象,您必须创建一个符合ObservableObject协议的类。 ObservableObject协议允许SwiftUI观察并响应数据更改。 要了解有关ObservableObject的更多信息,请参阅文章“ 在SwiftUI中管理数据流 ”。 让我们来看一个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) } } 

在上面的代码示例中,我们具有SettingsStore类,该类提供对用户设置的访问。 每当用户更改默认设置时,我们还使用didChangeNotification通知SwiftUI。

扩展使用


让我们通过创建一个简单的Todo应用程序来看看store对象的另一种用法。 我们需要创建一个存储对象,该对象存储任务列表并提供更改任务的操作,例如,删除和过滤任务。

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

有一个符合ObservableObject协议的TodosStore类。 TodosStore提供了几种更改其状态的操作,我们可以从视图中使用这些方法。 默认情况下,每次@Published字段更改时, SwiftUI都会更新视图。 这就是为什么Todo元素数组指定为@Published的原因 。 一旦我们从此数组中添加或删除元素,SwiftUI将更新订阅TodosStore的视图。

现在,您可以创建一个视图,该视图显示任务列表以及诸如将任务标记为已完成,删除和更改任务显示顺序的操作。 让我们首先创建一个显示任务标题的视图和一个将任务标记为已完成的开关。

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

在上面的示例中, 绑定用于提供引用,例如对值类型的访问。 换句话说,授予对todo元素的写访问权限。 TodoItemView不拥有Todo结构的实例,但是通过Binding确实具有对TodoStore的写权限。

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

现在我们有了TodosView ,它是一个使用List组件显示任务的元素。 列表组件还提供重新排序和删除。 另一个有趣的事情是indexed()函数。 此函数返回带有索引的元素的集合。 我们使用它通过Binding访问商店中的商品。 这是此扩展程序的完整源代码。

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

环境是存储商店对象的理想选择。 环境可以通过init方法将它们拆分为多个视图,而无需显式实现。 要了解有关SwiftUI中环境的好处的更多信息,请查看文章“ SwiftUI中的环境功能 ”。

待办事项屏幕截图

结论


本文讨论了一种使用多个存储对象对应用程序状态进行建模的方法。 我非常喜欢这种方法的简单性,以及通过添加更多存储对象来扩展应用程序的简便性。 希望您喜欢这篇文章。

Source: https://habr.com/ru/post/zh-CN467675/


All Articles