ios 如何在Swift中通过引用传递-使用MVVM的数字递增应用程序

csbfibhn  于 2023-02-01  发布在  iOS
关注(0)|答案(2)|浏览(125)

我刚刚开始学习swift,打算构建这个数字递增的示例应用程序来理解MVVM,我不明白为什么我在视图上的数字在点击按钮时不更新。
我尝试在用户每次单击按钮时更新视图,但计数保持为零。
观点

import SwiftUI

struct ContentView: View {
    @ObservedObject var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            Text("\(viewModel.model.count)")
            Button(action: {
                self.viewModel.increment()
            }) {
                Text("Increment")
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

视图模型

import SwiftUI
class CounterViewModel: ObservableObject {
    @ObservedObject var model = Model()

    func increment() {
        self.model.count += 1
    }
}

模特儿

import Foundation
class Model : ObservableObject{
    @Published var count = 0
}
6pp0gazn

6pp0gazn1#

以下应起作用:

import SwiftUI

struct Model {
    var count = 0
}

class CounterViewModel: ObservableObject {
   @Published var model = Model()

    func increment() {
        self.model.count += 1
    }
}

struct ContentView: View {
    @ObservedObject var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            Text("\(viewModel.model.count)")
            Button(action: {
                self.viewModel.increment()
            }) {
                Text("Increment")
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

请注意:ObservableObject和@Published是设计来一起工作的。只有一个值,也就是在一个被观察的对象中的值被发布,因此视图被更新。模型和视图模型之间的区别并不总是必要的,术语有点误导。你可以只把计数变量放在视图模型中。比如:

@Published var count = 1

有一个自己的模型结构体(或类)是有意义的,当你从数据库或通过网络请求获取一条记录时,你的模型会获取完整的记录。
比如:

struct Adress {
   let name: String
   let street: String
   let place: String
   let email: String
}

还请注意使用不可变结构体作为模型的优点(和缺点),但这是另一个主题。

iqih9akk

iqih9akk2#

大家好,在SwiftUI中使用MVVM是一个坏主意,因为SwiftUI旨在利用快速值类型作为视图数据(如结构体),而MVVM使用慢速对象作为视图数据,这导致了SwiftUI使用值类型旨在消除的一致性错误。(以及哈佛的讲师)试图将他们的MVVM垃圾推到SwiftUI上,而不是正确地学习它。幸运的是,some of them正在改变他们的方式。
在学习SwiftUI时,我认为最好先学习值语义(对结构体的任何值更改也是对结构体本身的更改),然后是View结构体(即调用body时),然后是@Binding,然后是@State。例如,尝试以下操作:

// use a config struct like this for view data to group related vars
struct ContentViewConfig {
   var count = 0 {
       didSet {
           // could do validation here, e.g. isValid = count < 10
       }
   }
   // include other vars that are all related, e.g. you could have searchText and searchResults.

   // use mutating func for logic that affects multiple vars
    mutating func increment() {
        count += 1
        //othervar += 1
    }
}

struct ContentView: View {
    @State var config = ContentViewConfig() // normally structs are immutable, but @State makes it mutable like magic, so its like have a view model object right here, but better.

    var body: some View {
        VStack {
            ContentView2(count: config.count)
            ContentView3(config: $config)
        }
    }
}

// when designing a View first ask yourself what data does this need to do its job?
struct ContentView2: View {
    let count: Int

    // body is only called if count is different from the last time this was init.
    var body: some View {
        Text(count, format: .number)
    }
}

struct ContentView3: View {
    @Binding var config: ContentViewConfig

    var body: some View {
        Button(action: {
            config.increment()
            }) {
                Text("Increment")
            }
        }
    }
}

然后,一旦您熟悉了视图数据,您就可以转到模型数据,这是ObservableObject和singletons开始发挥作用的时候,例如。

struct Item: Identifiable {
    let id = UUID()
    var text = ""
}

class MyStore: ObservableObject {
    @Published var items: [Item] = []

    static var shared = MyStore()
    static var preview = MyStore(preview: true)

    init(preview: Bool = false) {
        if preview {
            items = [Item(text: "Test Item")]
        }
    }
}

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(MyStore.shared)
        }
    }
}

struct ContentView: View {
    @EnvironmentObject store: MyStore

    var body: some View {
        List($store.items) { $item in
            TextField("Item", $item.text)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(MyStore.preview)
    }
}

注意,我们使用单例是因为使用@StateObject作为模型数据是危险的,因为它的生存期与屏幕上的某些东西相关联,我们可能会意外地丢失所有的模型数据,这些数据的生存期应该与应用程序的运行相关联。当您需要@State中的引用类型时,最好考虑@StateObject,即涉及视图数据。
当涉及到异步网络时,使用新的.task修饰符可以避免@StateObject

相关问题