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

SwiftUI and MVVM basics

Forums > Swift

I am learning SwiftUI and the basic MVVM pattern. For learning I came up with this code

import Foundation
import SwiftUI

class DataModel: ObservableObject {
    @Published var data: [String] = ["One", "Two", "Three"]
}

class ContentViewModel: ObservableObject {
    @Published private var dataModel = DataModel()

    var data: [String] {
        dataModel.data
    }

    func add() {
        dataModel.data.append(Date().description)
    }
}

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

    var body: some View {
        List {
            Button("Add") {
                viewModel.add()
            }
            ForEach(viewModel.data, id: \.self) {
                Text($0)
            }
        }
    }
}

I don't understand why my view is not updated. Should not eavery change of DataModel.data be published and the view redrawen. Please help me Thank you

2      

You have two classes where you do not need this and the whole point of MVVM is to put anything that not a view in a separate file so you have a file called ContentViewModel

class ContentViewModel: ObservableObject {
    @Published var data = ["One", "Two", "Three"]

    func add() {
        data.append(Date().description)
    }
}

and in the View

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

    var body: some View {
        List {
            Button("Add") {
                viewModel.add()
            }
            ForEach(viewModel.data, id: \.self) {
                Text($0)
            }
        }
    }
}

A little tip if you put the VM in a extension ContentView you can reuse ViewModel class eg

extension ContentView {
    class ViewModel: ObservableObject {
        @Published var data = ["One", "Two", "Three"]

        func add() {
            data.append(Date().description)
        }
    }
}

and then you can do this

struct ContentView: View {
    @StateObject private var vm = ViewModel()

    var body: some View {
        List {
            Button("Add") {
                vm.add()
            }
            ForEach(vm.data, id: \.self) {
                Text($0)
            }
        }
    }
}

3      

I think a more fundamental problem is that an @Published var must be a value type, not a reference type. SwiftUI updates the view when an @Published var changes. However, when the @Published var is a reference type object, i.e., an instance of a class such as dataModel = DataModel(), the var dataModel does not change even when a property of the object dataModel changes. That is because a var that holds a reference type object actually stores a pointer to that object's location in memory, not the value of the object. When you change the value of a property of a reference-type object such as a class instance, the object's location in memory does not change, hence the pointer does not change, hence the var does not change, hence SwiftUI fails to detect any change in the @Published var.

2      

Regarding MVVM, my impression is that there is no need to consider adhering to MVVM design to be virtuous goal. Neither Apple nor Paul Hudson ever refer to the concepts of MVVM or View Model. MVVM is a design approach that inspired SwiftUI, but SwiftUI employs its own, somewhat different approach.

I think it's simpler to just think of ObservableObjects as what the name implies, objects whose values are observed (and modified) by views, without worrying about whether they adhere to some theoretical definition of a view model.

Also, it seems to me that the structs that are typically considered "data models" in SwiftUI are not really data models because they don’t store data. They are data types used to organize the data that is stored in the ObservableObjects.

2      

I agree with @bobstern statement

Regarding MVVM, my impression is that there is no need to consider adhering to MVVM design to be virtuous goal.

I tend start with M - Model and then V - View. When there are starting to be alot of logic in the View then look at making a VM - ViewModel. I think that @maikischa was just trying to learn as in the example I would not employ a VM for one property and one method.

Model file

import Foundation

struct Model: Identifiable {
    var id = UUID()
    var date: String
}

View file

import SwiftUI

struct ContentView: View {
    @StateObject private var vm = ViewModel()

    var body: some View {
        List {
            Button("Add") {
                vm.add()
            }
            ForEach(vm.data) {
                Text($0.date)
            }
        }
    }
}

View Model

import Foundation

extension ContentView {
    class ViewModel: ObservableObject {
        @Published var data = [Model]()

        func add() {
            let newData = Model(date: Date().description)
            data.append(newData)
        }
    }
}

As I said I would not use the View Model for this but if I start adding more logic in the View then would look at this. Paul (I think) does not do this in videos and live steams as it would make them longer but when you listen to he does say that he would do thing slightly different.

They are number of different architecture and you just use the one that you are conferable with.

2      

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.