UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: MVVM Comprehension Help

Forums > Swift

I've just begun building what will be a pretty extensive application and want to use MVVM to organize things as it scales up. I'm having a lot of trouble understanding how to correctly bridge between a Model and a ViewModel.

I have the main "user account" Model like this:

struct UserSLP: Codable, Hashable {
    let email : String
    let password : String
    let firstName : String
    let lastName : String
    let state : String
    let zipCode : String
    let company : String
    let licenseNumber : String
    let licenseState : String
    let phoneNumber : String
    let timezone : String
    let language : String
    let token: String
}

In my ViewModel, I have the same variables created which my View then pushes form data into:

class slpCreateAccount: ObservableObject {

    var slpEmail: String = ""
    var slpPassword: String = ""
    var slpFirstName: String = ""
    var slpLastName: String = ""
    var slpZipCode: String = ""
    @Published var slpStateSelection: String = "State"
    var slpCompany: String = ""
    var slpLicenseNumber: String = ""
    @Published var slpLicenseState: String = "License State"
    var slpPhoneNumber: String = ""
    @Published var slpTimezone: String = "Timezone"
    @Published var slpLanguage: String = "Language"
    }

In my ViewModel, I also have a network call to the server and database to create this user's account. PS - I publish those 4 variables above because they're Pickers() in the View's Form and need the default string values(state, license state, etc.) displayed there before users pick something. Maybe that's the wrong way? Anyways, here's an example of one of the form field pickers in the View.

HStack {
                    Image(systemName: "location")
                        .font(.system(size: 37, weight: .ultraLight))

                    Picker(selection: $slpCreateAccountVM.slpStateSelection,
                           label: Text(slpCreateAccountVM.slpStateSelection),
                           content: {
                            ForEach(StatesList().states.sorted{ $0 > $1 }, id: \.self) { states in
                                Text(states)
                            }
                           })
                        .pickerStyle(MenuPickerStyle())
                        .frame(width: 300, alignment: .leading)
                        .padding()
                        .background(Color(#colorLiteral(red: 0.9545406699, green: 0.9489219785, blue: 0.9588606954, alpha: 1)))
                        .cornerRadius(10)
                        .foregroundColor(.black)
                }
                .padding(.bottom, 10)

So I push my View form data into the ViewModel variables, but I'm also supposed to use the Model Struct UserSLP as some sort of blueprint for handling that data as well?

I just want to follow the correct approach here, get the user's inputs on their form into the ViewModel and into my network call. The network call piece is working great with hard-coded parameters. Just need to figure out how exactly a ViewModel and Model work together here.

Thanks in advance and sorry for the long post.

3      

Okay. Will try and break it down (ps shorten number fields)

Model (which you have)

struct UserSLP: Codable {
    let email : String
    let password : String
    let firstName : String
}

ViewModel (This is where the logic for the view is eg network call etc)

class SLPCreateAccount: ObservableObject {
    @Published var slpEmail: String = ""
    @Published var slpPassword: String = ""
    @Published var slpFirstName: String = ""

    func sendToNetWork() {
        let newUser = UserSLP(email: slpEmail, password: slpPassword, firstName: slpFirstName)

        print("Sending to Network……\(newUser)")
    }
}

Then View

struct ContentView: View {
    @StateObject var viewModel = SLPCreateAccount()

    var body: some View {
        VStack {
            Form {
                TextField("Email", text: $viewModel.slpEmail)
                SecureField("Password", text: $viewModel.slpPassword)
                TextField("First Name", text: $viewModel.slpFirstName)
            }
            Button("Submit") {
                viewModel.sendToNetWork()
            }
        }
    }
}

3      

Hey, you pretty much correctly understand the division between layers, except that you don't seem to use your Model at all. The solution is to, instead of copying each field/property into the ViewModel, just have your Model there. Just use @Published var user: UserSLP. This will greatly reduce clutter and code duplication. As for your picker connection to model - that's exactly why views don't use models directly. You can have separate properties in your ViewModel that will handle those cases. For example your language property:

struct User {
    var language: String?
}

class ViewModel: ObservableObject {
    @Published var user = User()
    @Published var languageString = "Language"

    private var bag = Set<AnyCancellable>()

    init() {
        $languageString
            .filter { $0 != "Language" }
            .sink { [weak self] in self?.user.language = $0 }
            .store(in: &bag)
    }
}

On UI you change viewModel.languageString, and then your ViewModel does the work to make sure Model has correct data. The filter, of course, can be more complex if you need.

Another thing is, that languageString can be of enum type, which will increase clarity even more. I guess you need the language property in your model be of type String, if so the view model can handle this as well.

3      

Thank you both, very helpful.

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.