SwiftUI 和 MVVM:使用按钮对列表进行排序
SwiftUI and MVVM: Using Buttons to Sort Lists
我正在构建一个使用按钮对各种列表进行排序的应用程序。
我正在尽最大努力尽可能地遵循 MVVM 设计模式。有几个显示列表的视图,所以我希望能够在多个视图中重用按钮结构,并将它们连接到多个视图模型
我目前使用此设置代码生成的方式,但按下新按钮时列表不会更改。有什么想法吗?
可以从 GitHub 此处下载有问题的示例项目:https://github.com/sans-connaissance/SOQ-BetterButtons
这是视图的代码:
import SwiftUI
struct ContentView: View {
@StateObject private var vm = ContentViewModel()
var body: some View {
VStack {
HStack {
SortButton(name: .arrayOne, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
SortButton(name: .arrayTwo, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
SortButton(name: .arrayThree, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
}
List {
ForEach(vm.contentArray, id: \.self) { content in
Text(content.self)
}
}
}
.onAppear {vm.setButtons()}
.onAppear {vm.getArray()}
}
}
这是按钮的代码
import SwiftUI
struct SortButton: View {
var name: Select
@Binding var bools: [String : Bool]
var body: some View {
Button {
func show(button: Select) {
Select.allCases.forEach { button in
bools[button.rawValue] = false
}
bools[button.rawValue] = true
}
} label: {
Text(name.rawValue)
}
}
}
enum Select: String, CaseIterable {
case arrayOne = "arrayOne"
case arrayTwo = "arrayTwo"
case arrayThree = "arrayThree"
}
最后是本示例的 ViewModel。
import Foundation
class ContentViewModel: ObservableObject {
@Published var contentArray = [String]()
@Published var bools = [String : Bool]()
private let arrayOne = ["One", "Two", "Three"]
private let arrayTwo = ["Four", "Five", "Six"]
private let arrayThree = ["Seven", "Eight", "Nine"]
func setButtons() {
Select.allCases.forEach { button in
bools[button.rawValue] = false
}
bools["arrayOne"] = true
}
func getArray() {
if bools["arrayOne"]! {
contentArray.removeAll()
contentArray.append(contentsOf: arrayOne)
}
if bools["arrayTwo"]! {
contentArray.removeAll()
contentArray.append(contentsOf: arrayTwo)
}
if bools["arrayThree"]! {
contentArray.removeAll()
contentArray.append(contentsOf: arrayThree)
}
}
}
Link 到 GitHub 上的示例项目:
https://github.com/sans-connaissance/SOQ-BetterButtons
感谢观看!!
我认为您在尝试学习 MVVM 时在代码中走得太远,并且不必要地使事情复杂化。这段代码非常脆弱,不容易更改。我确实让你的代码工作了,我想看一下它。
我清理了你的 enum
并在代码中进行了注释。我还将您的 var bools
更改为 [Select:Bool] 类型。这允许您使用枚举案例本身,而不是原始值。然后编译器可以帮助您,因为它知道什么是有效的 Select
情况,什么不是。当您使用 String
时,它不知道您指的是一个“字符串,它是 Select
案例的原始值。这大大减少了错误。
此外,请避免在您的代码中有太多空白 space。尝试使用 space 分隔逻辑分组。太多的白色 space 会使您的代码难以阅读,因为您最终滚动太多只是为了看到它。
您的代码实际失败的地方是 SortButton
本身。在 Button
中,您告诉 Button
执行的操作是定义您的 func show(button:)
。因此,这个函数只在使用任何按钮时被简单地定义,然后就消失了。坦率地说,我不敢相信 Xcode 允许这样做而不引发一些错误。您想要在 var body
之外定义函数并从按钮的操作中调用它。
最后,这不是我可以在你的代码中真正改变的东西,你应该避免使用 !
来强制解包选项。它在这里工作只是因为你有少量固定数量的常量数组,但随着事情变得越来越复杂,这会变成一个致命的错误。
struct ContentView: View {
@StateObject private var vm = ContentViewModel()
var body: some View {
VStack {
HStack {
SortButton(name: .arrayOne, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
SortButton(name: .arrayTwo, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
SortButton(name: .arrayThree, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
}
List {
ForEach(vm.contentArray, id: \.self) { content in
Text(content.self)
}
}
}
.onAppear {
vm.setButtons()
vm.getArray()
}
}
}
struct SortButton: View {
var name: Select
@Binding var bools: [Select : Bool]
var body: some View {
Button {
show(button: name)
} label: {
Text(name.rawValue)
}
}
//This is defined outside of the body, and called from the button.
func show(button: Select) {
Select.allCases.forEach { button in
bools[button] = false
}
bools[button] = true
}
}
enum Select: String, CaseIterable {
// when you declare an enum to be of type String, you get the string version of the name for free
// you don't need case arrayOne = "arrayOne". Also, once you remove the = ""
// you can use one case statement to define them all. The only time you need the = "" is when
// you want to change the default rawValue such as case arrayOne = "Array One"
case arrayOne, arrayTwo, arrayThree
}
class ContentViewModel: ObservableObject {
@Published var contentArray = [String]()
@Published var bools = [Select : Bool]()
private let arrayOne = ["One", "Two", "Three"]
private let arrayTwo = ["Four", "Five", "Six"]
private let arrayThree = ["Seven", "Eight", "Nine"]
func setButtons() {
Select.allCases.forEach { button in
bools[button] = false
}
bools[.arrayOne] = true
}
func getArray() {
// if you just set contentArray equal to one of the other arrays, you
// get the same result as the .removeAll and the .append(contentsOf:)
if bools[.arrayOne]! { contentArray = arrayOne }
if bools[.arrayTwo]! { contentArray = arrayTwo }
if bools[.arrayThree]! { contentArray = arrayThree }
}
}
我还快速 运行 了解了如何进一步压缩和简化您的代码。还有更多工作要做,但这是一种人为练习。使用 MVVM,您希望将模型逻辑与视图分开并将其放置在视图中,但您应该有视图逻辑来显示视图模型的数据。尽管视图模型隐藏了模型逻辑,视图应该能够处理不同的视图模型并一致地显示数据。这就是可重用性的本质。
此外,您会注意到我删除了 ContentView
中单独的 SortButton
调用并使用了 ForEach
。这是 DRY 实际应用的一个很好的例子,它可以在您添加 Select
个案例时轻松扩展。
此代码可以改进的一个方面是 SortButton 中的一个更好的机制来获取 'ContentViewModel' 更新。我刚刚传入 ContentViewModel
,但这可以进一步简化。
struct ContentView: View {
@StateObject private var vm = ContentViewModel()
var body: some View {
VStack {
HStack {
ForEach(Select.allCases, id: \.self) { name in
SortButton(name: name, vm: vm)
}
}
List {
ForEach(vm.contentArray, id: \.self) { content in
Text(content.self)
}
}
}
}
}
struct SortButton: View {
var name: Select
let vm: ContentViewModel
var body: some View {
Button {
vm.updateContentArray(select: name)
} label: {
Text(name.rawValue)
}
}
}
enum Select: String, CaseIterable {
case arrayOne, arrayTwo, arrayThree
}
class ContentViewModel: ObservableObject {
@Published var contentArray: [String]
private let arrayOne = ["One", "Two", "Three"]
private let arrayTwo = ["Four", "Five", "Six"]
private let arrayThree = ["Seven", "Eight", "Nine"]
init() {
contentArray = arrayOne
}
func updateContentArray(select: Select) {
switch select {
case .arrayOne:
contentArray = arrayOne
case .arrayTwo:
contentArray = arrayTwo
case .arrayThree:
contentArray = arrayThree
}
}
}
我正在构建一个使用按钮对各种列表进行排序的应用程序。
我正在尽最大努力尽可能地遵循 MVVM 设计模式。有几个显示列表的视图,所以我希望能够在多个视图中重用按钮结构,并将它们连接到多个视图模型
我目前使用此设置代码生成的方式,但按下新按钮时列表不会更改。有什么想法吗?
可以从 GitHub 此处下载有问题的示例项目:https://github.com/sans-connaissance/SOQ-BetterButtons
这是视图的代码:
import SwiftUI
struct ContentView: View {
@StateObject private var vm = ContentViewModel()
var body: some View {
VStack {
HStack {
SortButton(name: .arrayOne, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
SortButton(name: .arrayTwo, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
SortButton(name: .arrayThree, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
}
List {
ForEach(vm.contentArray, id: \.self) { content in
Text(content.self)
}
}
}
.onAppear {vm.setButtons()}
.onAppear {vm.getArray()}
}
}
这是按钮的代码
import SwiftUI
struct SortButton: View {
var name: Select
@Binding var bools: [String : Bool]
var body: some View {
Button {
func show(button: Select) {
Select.allCases.forEach { button in
bools[button.rawValue] = false
}
bools[button.rawValue] = true
}
} label: {
Text(name.rawValue)
}
}
}
enum Select: String, CaseIterable {
case arrayOne = "arrayOne"
case arrayTwo = "arrayTwo"
case arrayThree = "arrayThree"
}
最后是本示例的 ViewModel。
import Foundation
class ContentViewModel: ObservableObject {
@Published var contentArray = [String]()
@Published var bools = [String : Bool]()
private let arrayOne = ["One", "Two", "Three"]
private let arrayTwo = ["Four", "Five", "Six"]
private let arrayThree = ["Seven", "Eight", "Nine"]
func setButtons() {
Select.allCases.forEach { button in
bools[button.rawValue] = false
}
bools["arrayOne"] = true
}
func getArray() {
if bools["arrayOne"]! {
contentArray.removeAll()
contentArray.append(contentsOf: arrayOne)
}
if bools["arrayTwo"]! {
contentArray.removeAll()
contentArray.append(contentsOf: arrayTwo)
}
if bools["arrayThree"]! {
contentArray.removeAll()
contentArray.append(contentsOf: arrayThree)
}
}
}
Link 到 GitHub 上的示例项目: https://github.com/sans-connaissance/SOQ-BetterButtons
感谢观看!!
我认为您在尝试学习 MVVM 时在代码中走得太远,并且不必要地使事情复杂化。这段代码非常脆弱,不容易更改。我确实让你的代码工作了,我想看一下它。
我清理了你的 enum
并在代码中进行了注释。我还将您的 var bools
更改为 [Select:Bool] 类型。这允许您使用枚举案例本身,而不是原始值。然后编译器可以帮助您,因为它知道什么是有效的 Select
情况,什么不是。当您使用 String
时,它不知道您指的是一个“字符串,它是 Select
案例的原始值。这大大减少了错误。
此外,请避免在您的代码中有太多空白 space。尝试使用 space 分隔逻辑分组。太多的白色 space 会使您的代码难以阅读,因为您最终滚动太多只是为了看到它。
您的代码实际失败的地方是 SortButton
本身。在 Button
中,您告诉 Button
执行的操作是定义您的 func show(button:)
。因此,这个函数只在使用任何按钮时被简单地定义,然后就消失了。坦率地说,我不敢相信 Xcode 允许这样做而不引发一些错误。您想要在 var body
之外定义函数并从按钮的操作中调用它。
最后,这不是我可以在你的代码中真正改变的东西,你应该避免使用 !
来强制解包选项。它在这里工作只是因为你有少量固定数量的常量数组,但随着事情变得越来越复杂,这会变成一个致命的错误。
struct ContentView: View {
@StateObject private var vm = ContentViewModel()
var body: some View {
VStack {
HStack {
SortButton(name: .arrayOne, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
SortButton(name: .arrayTwo, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
SortButton(name: .arrayThree, bools: $vm.bools)
.onChange(of: vm.bools){ _ in vm.getArray()}
}
List {
ForEach(vm.contentArray, id: \.self) { content in
Text(content.self)
}
}
}
.onAppear {
vm.setButtons()
vm.getArray()
}
}
}
struct SortButton: View {
var name: Select
@Binding var bools: [Select : Bool]
var body: some View {
Button {
show(button: name)
} label: {
Text(name.rawValue)
}
}
//This is defined outside of the body, and called from the button.
func show(button: Select) {
Select.allCases.forEach { button in
bools[button] = false
}
bools[button] = true
}
}
enum Select: String, CaseIterable {
// when you declare an enum to be of type String, you get the string version of the name for free
// you don't need case arrayOne = "arrayOne". Also, once you remove the = ""
// you can use one case statement to define them all. The only time you need the = "" is when
// you want to change the default rawValue such as case arrayOne = "Array One"
case arrayOne, arrayTwo, arrayThree
}
class ContentViewModel: ObservableObject {
@Published var contentArray = [String]()
@Published var bools = [Select : Bool]()
private let arrayOne = ["One", "Two", "Three"]
private let arrayTwo = ["Four", "Five", "Six"]
private let arrayThree = ["Seven", "Eight", "Nine"]
func setButtons() {
Select.allCases.forEach { button in
bools[button] = false
}
bools[.arrayOne] = true
}
func getArray() {
// if you just set contentArray equal to one of the other arrays, you
// get the same result as the .removeAll and the .append(contentsOf:)
if bools[.arrayOne]! { contentArray = arrayOne }
if bools[.arrayTwo]! { contentArray = arrayTwo }
if bools[.arrayThree]! { contentArray = arrayThree }
}
}
我还快速 运行 了解了如何进一步压缩和简化您的代码。还有更多工作要做,但这是一种人为练习。使用 MVVM,您希望将模型逻辑与视图分开并将其放置在视图中,但您应该有视图逻辑来显示视图模型的数据。尽管视图模型隐藏了模型逻辑,视图应该能够处理不同的视图模型并一致地显示数据。这就是可重用性的本质。
此外,您会注意到我删除了 ContentView
中单独的 SortButton
调用并使用了 ForEach
。这是 DRY 实际应用的一个很好的例子,它可以在您添加 Select
个案例时轻松扩展。
此代码可以改进的一个方面是 SortButton 中的一个更好的机制来获取 'ContentViewModel' 更新。我刚刚传入 ContentViewModel
,但这可以进一步简化。
struct ContentView: View {
@StateObject private var vm = ContentViewModel()
var body: some View {
VStack {
HStack {
ForEach(Select.allCases, id: \.self) { name in
SortButton(name: name, vm: vm)
}
}
List {
ForEach(vm.contentArray, id: \.self) { content in
Text(content.self)
}
}
}
}
}
struct SortButton: View {
var name: Select
let vm: ContentViewModel
var body: some View {
Button {
vm.updateContentArray(select: name)
} label: {
Text(name.rawValue)
}
}
}
enum Select: String, CaseIterable {
case arrayOne, arrayTwo, arrayThree
}
class ContentViewModel: ObservableObject {
@Published var contentArray: [String]
private let arrayOne = ["One", "Two", "Three"]
private let arrayTwo = ["Four", "Five", "Six"]
private let arrayThree = ["Seven", "Eight", "Nine"]
init() {
contentArray = arrayOne
}
func updateContentArray(select: Select) {
switch select {
case .arrayOne:
contentArray = arrayOne
case .arrayTwo:
contentArray = arrayTwo
case .arrayThree:
contentArray = arrayThree
}
}
}