I have a SwiftUI project using MVVM structure but I have lots of variables in View now, is that right?How can I improve this?

Viewed 72

I have a SwiftUI project using MVVM structure but I have lots of variables in View now, is that right?How can I improve this?

For example, this is my add record view

    @State private var money: String = ""
    @State private var desc: String = ""
    @FocusState var isDescFieldFocused: Bool
    @State private var showDatePicker: Bool = false
    @State private var showingAlert: Bool = false
    @State private var showConstantlyAlert: Bool = false
    @State private var showKeyPad: Bool = false
    @State private var showCatesPanel: Bool = false
    @State private var selectedCate: String = ""

Should I put all these in View Models?

2 Answers

Apple addresses this in Data Essentials in SwiftUI WWDC 2020 at 4:18

EditorConfig can maintain invariants on its properties and be tested independently. And because EditorConfig is a value type, any change to a property of EditorConfig, like its progress, is visible as a change to EditorConfig itself. 4:18

So in your case it would look like:

struct MyConfig {
    var money: String = ""
    var desc: String = ""
    var showDatePicker: Bool = false
    var showingAlert: Bool = false
    var showConstantlyAlert: Bool = false
    var showKeyPad: Bool = false
    var showCatesPanel: Bool = false
    var selectedCate: String = ""

    mutating func otherLogic() {}
}

struct MyView {

    @State var config = MyConfig()
    @FocusState var isDescFieldFocused: Bool

Just to let you know, MVVM (i.e. using objects for view data) is not suitable for SwiftUI because the View struct and property wrappers is equivalent to a view model object but faster and less error-prone. If you use actual objects instead of learning SwiftUI's features you'll have problems.

The time we use an ObservableObject in SwiftUI is for model data, i.e. the environment object/singleton that stores the arrays of model structs in @Published properties and is responsible for loading/saving/syncing etc. the model data etc. That is covered in the same video later on, from about 9mins 30secs.

For write-access to model data we use @Binding.

We used to also use @StateObject for handling networking or delegates but that is pretty much redundant now we have the task modifier which does the same thing but better. Because we can simply set the result (or thrown error) of await to an @State and the task will be automatically cancelled when the view disappears (and also cancelled and restarted if an id passed to task(id:) changes).

Like you said, you could create a view model for that view, which would either be a @StateObject or @ObservedObject (depends on whether you want to create the view model within the view or inject it from outside).

I tend to create view models for my views if they require any logic and not only display information which is passed down from parent views.

Thus, you could do this:

final class YourNameViewModel: ObservableObject {

    @Published var money = ""
    // other properties

    init() {
        ...
    }

}

and use it within your view like so:

struct YourNameView: View {

     @StateObject var vm = YourNameViewModel()
     // or @ObservedObject var vm: YourNameViewModel (if you want to pass it down from a parent view)    

}
Related