【问题标题】:SwiftUI and MVVM: Using Buttons to Sort ListsSwiftUI 和 MVVM:使用按钮对列表进行排序
【发布时间】:2022-01-05 19:18:54
【问题描述】:

我正在构建一个使用按钮对各种列表进行排序的应用。

我正在尽最大努力尽可能 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

感谢观看!!

【问题讨论】:

    标签: ios swift button mvvm swiftui


    【解决方案1】:

    我认为您在尝试学习 MVVM 的代码中太过分了,并且不必要地使事情复杂化。这段代码非常脆弱,不容易更改。我确实让你的代码工作了,我想看看它。

    我清理了您的 enum 并在代码中进行了注释。我还将您的 var bools 更改为 [Select:Bool] 类型。这允许您使用枚举案例本身,而不是原始值。然后编译器可以帮助您,因为它知道什么是有效的Select 案例,什么不是。当您使用String 时,它不知道您的意思是“字符串,它是Select 案例的原始值。这大大减少了错误。

    另外,请避免在代码中包含太多空白。尝试使用空格分隔的逻辑分组。太多的空白使您的代码难以阅读,因为您最终滚动了很多时间才能看到所有内容。

    您的代码实际上失败的地方是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
            }
        }
    }
    

    【讨论】:

    • 非常感谢您的宝贵时间和回复,感谢您提供的所有反馈!
    猜你喜欢
    • 2013-04-27
    • 2014-12-18
    • 1970-01-01
    • 1970-01-01
    • 2016-10-18
    • 2021-01-25
    • 1970-01-01
    • 2021-02-07
    相关资源
    最近更新 更多