简体   繁体   English

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

[英]How to pass by reference in Swift - number incrementing app using MVVM

I've just started learning swift and was going to build this number-incrementing sample app to understand MVVM.我刚刚开始学习 swift 并打算构建这个数字递增示例应用程序来理解 MVVM。 I don't understand why is my number on the view not updating upon clicking the button.我不明白为什么单击按钮后我在视图上的号码没有更新。

I tried to update the view everytime user clicks the button but the count stays at zero.每次用户单击按钮时,我都尝试更新视图,但计数保持为零。

The View风景

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

The ViewModel视图模型

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

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

The Model Model

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

Following should work:以下应该工作:

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

Please note: ObservableObject and @Published are designed to work together.请注意:ObservableObject 和@Published 旨在协同工作。 Only a value, that is in an observed object gets published and so the view updated.只有一个值,即观察到的 object 中的值被发布,因此视图更新。 A distinction between model and view model is not always necessary and the terms are somewhat misleading. model 和视图 model 之间的区别并不总是必要的,并且这些术语有些误导。 You can just put the count var in the ViewModel.您可以将计数变量放在 ViewModel 中。 Like:喜欢:

 @Published var count = 1

It makes sense to have an own model struct (or class), when fx you fetch a record from a database or via a.network request, than your Model would take the complete record.拥有自己的 model 结构(或类)是有意义的,当您从数据库或通过网络请求获取记录时,您的 Model 将获取完整记录。

Something like:就像是:

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

Please also note the advantages (and disadvantages) of having immutable structs as a model. But this is another topic.另请注意将不可变结构作为 model 的优点(和缺点)。但这是另一个话题。

Hi it's a bad idea to use MVVM in SwiftUI because Swift is designed to take advantage of fast value types for view data like structs whereas MVVM uses slow objects for view data which leads to the kind of consistency bugs that SwiftUI's use of value types is designed to eliminate.嗨,在 SwiftUI 中使用 MVVM 是个坏主意,因为 Swift 旨在利用视图数据(如结构)的快速值类型,而 MVVM 对视图数据使用慢速对象,这会导致 SwiftUI 使用值类型时设计的那种一致性错误消除。 It's a shame so many MVVM UIKit developers (and Harvard lecturers) have tried to push their MVVM garbage onto SwiftUI instead of learning it properly.遗憾的是,许多 MVVM UIKit 开发人员(和哈佛讲师)试图将他们的 MVVM 垃圾推到 SwiftUI 而不是正确学习它。 Fortunately some of them are changing their ways.幸运的是,他们中的一些人正在改变他们的方式。

When learning SwiftUI I believe it's best to learn value semantics first (where any value change to a struct is also a change to the struct itself), then the View struct (ie when body is called), then @Binding , then @State .在学习 SwiftUI 时,我认为最好先学习值语义(其中对结构的任何值更改也是对结构本身的更改),然后是 View 结构(即调用 body 时),然后是@Binding ,然后@State eg have a play around with this:例如玩这个:

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

Then once you are comfortable with view data you can move on to model data which is when ObservableObject and singletons come into play, eg然后,一旦您对视图数据感到满意,就可以继续使用 model 数据,这是ObservableObject和单例发挥作用的时候,例如

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

Note we use singletons because it would be dangerous to use @StateObject for model data because its lifetime is tied to something on screen we could accidentally lose all our model data which should have lifetime tied to the app running.请注意,我们使用单例是因为对 model 数据使用@StateObject是危险的,因为它的生命周期与屏幕上的某些内容相关,我们可能会意外丢失所有 model 数据,这些数据应该与应用程序运行相关。 Best to think of @StateObject when you need a reference type in a @State , ie involving view data.当您需要@State中的引用类型(即涉及视图数据)时,最好考虑@StateObject

When it comes to async.networking use the new .task modifier and you can avoid @StateObject .当谈到 async.networking 时,请使用新的.task修饰符,您可以避免@StateObject

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM