SwiftUI: calling a method from nested view

Viewed 706

I am pretty nw to SwiftUI, and to make my code more readable, I usually break my views into smaller views.

Let's say I have a viewModel, what is the best way to call a method of my viewModel from a nested view? Right now I am passing around my ViewModel as a parameter of every nested view, but I don't find it very optimal and clean...

Is there a way to notify MyMainView that the button of HeaderSubview was tapped ? Can I use Combine for example?

ViewModel

class MyViewModel {
    func fetchSomeData() {
        print("Fetching Some Data")
    }
}

MainView

struct MyMainView: View {

    var myViewModel = MyViewModel()
    var body: some View {
        HeaderView(viewModel: myViewModel)
    }
}

struct HeaderView: View {
    var viewModel: MyViewModel

    var body: some View {
        HeaderSubview(viewModel: viewModel)
    }
}

struct HeaderSubview: View {
    var viewModel: MyViewModel
    var body: some View {
        Button("Search") {
            // I want to call my View Model method here
            viewModel.fetchSomeData()
        }
    }
}
1 Answers

You can make your class ObservableObject and inject it as environment object, so any subview can use it and main view would be able to observe it.

Something like

class MyViewModel: ObservableObject {
    @Published var loading = false
    func fetchSomeData() {
        loading = true
        
        DispatchQueue.global(qos: .background).async {
            print("Fetching Some Data")
            // ... long activity here
            DispatchQueue.main.async { [weak self] in
                self?.loading = false
            }
        }
        
    }
}

struct MyMainView: View {
    
    @StateObject var myViewModel = MyViewModel()
    var body: some View {
        HeaderView().environmentObject(myViewModel)
        if myViewModel.loading {
            Text("Loading...")
        }
    }
}

struct HeaderView: View {
    
    var body: some View {
        HeaderSubview()
    }
}

struct HeaderSubview: View {
    @EnvironmentObject var viewModel: MyViewModel
    var body: some View {
        Button("Search") {
            // I want to call my View Model method here
            viewModel.fetchSomeData()
        }
    }
}
Related