Home > Software design >  How to create Viewmodel for subview and update values on change in Swiftui?
How to create Viewmodel for subview and update values on change in Swiftui?

Time:03-07

Currently i am using subview without using viewmodel and which is woking fine..UI is updating on value change. (find code below) but i want to create viewmodel for subview and update UI on value change..

Normal code without viewmodel

struct MainView: View {

    @State private var selectedTag: String?

    var body: some View {
        VStack {
            ForEach(products, id: \.description) { item in
                SubView(productTag: item.productId, selectedTag: self.$selectedTag)
            }
        }
    }
}


struct SubView: View {

    var productTag: String?
    @Binding var selectedTag: String?

    var body: some View {

        Button(action: {
            self.selectedTag = self.productTag
        })
    }
}

with viewmodel (but not working for me - UI is not updating)

struct MainView: View {

    @State private var selectedTag: String?

    var body: some View {
        VStack {
            ForEach(products, id: \.description) { item in
                SubView(viewModel: SubViewViewModel(productTag: item.productId ?? "", selectedTag: self.selectedTag ?? ""))
            }
        }
    }
}

struct SubView: View {

    private var viewModel: SubViewViewModel

    var body: some View {

        Button(action: {
            viewModel.selectedTag = viewModel.productTag
        })
    }
}

class SubViewViewModel: ObservableObject {

    var productTag: String
    @Published var selectedTag: String?

    init(productTag: String, selectedTag: String) {
        self.productTag = productTag
        self.selectedTag = selectedTag
    }
}

I might missing some concept, kindly suggest the solution for same.

CodePudding user response:

If you want SubView to alter MainView via a shared model you will first need to initialize it as a @StateObject on the MainView, because MainView is the owner.

Then you pass that same viewModel to SubView as an @ObservedObject since SubView is only borrowing it.

struct MainView: View {
    @StateObject private var viewModel = ViewModel()

    var body: some View {
        VStack {
            ForEach(products, id: \.description) { item in
                SafHelpCard(viewModel: viewModel, productTag: item.productId)
            }
        }
        .onAppear {
            viewModel.onAppear(/* whatever setup is needed */)
        }
    }
}

struct SubView: View {
    @ObservedObject var viewModel: ViewModel
    let productTag: String?

    var body: some View {
        Button(action: {
            viewModel.selectedTag = viewModel.productTag
        })
    }
}

class ViewModel: ObservableObject {
    @Published var selectedTag: String?

    func onAppear(args:...) {
        // do required setup in here
    }
}

CodePudding user response:

Make your view model a singleton.

struct MainView: View {
@StateObject private var viewModel = ViewModel.shared

var body: some View {
    VStack {
        ForEach(products, id: \.description) { item in
            SafHelpCard(viewModel: viewModel, productTag: item.productId)
        }
    }
    .onAppear {
        viewModel.onAppear(/* whatever setup is needed */)
    }
}
}

struct SubView: View {
@ObservedObject var viewModel: ViewModel.shared
let productTag: String?

var body: some View {
    Button(action: {
        viewModel.selectedTag = viewModel.productTag
    })
}
}

class ViewModel: ObservableObject {
@Published var selectedTag: String?
static var shared = ViewModel()

func onAppear(args:...) {
    // do required setup in here
}
}

CodePudding user response:

You can pass the view model as an environment object to your subview like this or use a Binding variable as well.

Since you are using @State, which only works with simple data types. For the view to be updated as per your view model, you need to use @StateObject as viewmodel is a complex data type.

Please check this link: https://levelup.gitconnected.com/state-vs-stateobject-vs-observedobject-vs-environmentobject-in-swiftui-81e2913d63f9

 class ViewModel: ObservableObject {
     @Published var name: String = "Test"
 }

 struct MainView: View {

     @StateObject var viewModel = ViewModel()

     var body: some View {
         ChildView()
             .environmentObject(viewModel)
     }
 }


 struct ChildView: View {

     @EnvironmentObject var model: ViewModel

     var body: some View {
         Text(model.name)
     }
 }

CodePudding user response:

you need to make your viewModel @StateObject to affect your view

struct SubView: View {
    @StateObject private var viewModel: SubViewViewModel
    
    init(productTag: String, selectedTag: String?) {
        _viewModel = StateObject(
            wrappedValue: SubViewViewModel(
                productTag: productTag,
                selectedTag: selectedTag
            )
        )
    }

    var body: some View {
        Button(action: {
            viewModel.selectedTag = viewModel.productTag
        }) {
            Text("\(viewModel.productTag) - \(viewModel.selectedTag ?? "nil")")
        }
    }
}

in your comment you said you don't want MainView's value be updated, so it doesn't need to be @State as well (so other won't misunderstand your intent), just private var selectedTag: String? is enough

  • Related