我正在構建一個使用按鈕對各種串列進行排序的應用程式。
我正在盡最大努力盡可能 DRYly 遵循 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)
}
}
}
GitHub 上示例專案的鏈接:https : //github.com/sans-connaissance/SOQ-BetterButtons
謝謝參觀!!
uj5u.com熱心網友回復:
我認為您在嘗試學習 MVVM 的代碼中太過分了,并且不必要地使事情復雜化。這段代碼非常脆弱,不容易更改。我確實讓你的代碼作業了,我想稍微通讀一下。
我清理了你的enum,這是在代碼中注釋的。我還將您var bools的型別更改為 [Select:Bool]。這允許您使用列舉案例本身,而不是原始值。然后編譯器可以幫助你,因為它知道什么是有效的Select情況,什么不是。當您使用 時String,它不知道您的意思是“字串,它是Select案例的原始值。大大減少了錯誤。
此外,避免在您的代碼中有太多的空白。嘗試使用用空格分隔的邏輯分組。太多的空白會使你的代碼難以閱讀,因為你最終滾動太多只是為了看到它。
您的代碼實際上失敗的地方在于它SortButton本身。在 中Button,您要執行的操作Button是定義您的func show(button:). 因此,此功能僅在使用任何按鈕時短暫定義,然后就消失了。坦率地說,我無法相信 Xcode 允許這樣做而不會引發一些錯誤。你想在你的外部定義函式var body并從按鈕的動作中呼叫它。
最后,這不是我可以在您的代碼中真正更改的內容,您應該避免使用 a!來強制解包選項。它在這里作業只是因為您有少量固定數量的常量陣列,但隨著事情變得越來越復雜,這將成為一個致命的錯誤。
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,您希望將模型邏輯與視圖分離并將其放置在視圖中,但您應該具有視圖邏輯來顯示視圖模型的資料。盡管視圖模型隱藏了模型邏輯,視圖應該能夠處理不同的視圖模型并一致地顯示資料。這就是可重用性的本質。
此外,您會注意到我洗掉了單獨的SortButton呼叫ContentView并使用了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
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/368975.html
上一篇:計算屬性
