How can I run a func that lives in a seperate View in SwiftUI?

128 Views Asked by At

I have an app wherein once navigated to a second page and shown some results, the user has the option of running the same function but with some different parameters.

I am stuck on figuring out how to run the func that is inside my initial screen from within my second...

I have tried several methods but seem to get stuck. I have tried moving the func to my @main struct as well and having it be global. seems there are some linking issues as I'm doing it wrong. I have placed ContentView.doFunction() as an example of where I need to run the func from, this obviously doesn't work and is a bit more pythonic..

I will provide a very basic reproduction of the code.

struct ContentView: View {
    
    @State private var readyToNavigate: Bool = false
    
    var body: some View {
        NavigationStack {
            VStack {
                Button(action: doFunction, label: {
                    Text("Button")
                })
                .navigationDestination(isPresented: $readyToNavigate) {
                    secondPage()}
                .navigationTitle("Test")
            }
        }
    }
    func doFunction() {
        Task {
            let example = await "This is running an async operation"
            print(example)
        }
        readyToNavigate = true
    }
}

#Preview {
    ContentView()
}

and from the other view which is in a seperate swift file...

struct secondPage: View {
    
    var body: some View {
        Button("run func from first page", action: {
//            ContentView.doFunction()
            print("running async func from ContentView")
        })
    }
}
#Preview {
    secondPage()
}
2

There are 2 best solutions below

3
malhal On BEST ANSWER

In SwiftUI use async/await with .task not Task {}, e.g.

struct ContentView: View {
    
    @State private var readyToNavigate: Bool = false
    @State private var isRunning = false

    var body: some View {
        NavigationStack {
            VStack {
                Button(isRunning ? "Stop" : "Start") {
                    isRunning.toggle()
                }
                .navigationDestination(isPresented: $readyToNavigate) {
                    SecondPage()
                }
                .navigationTitle("Test")
            }
        }
        .task(id: isRunning) { // runs on appear and cancelled and restarted when id changes, cancelled on dissapear
             if isRunning == false { return }
             await doFunction()
             isRunning = false
        }
    }

    func doFunction() async {
        let example = await "This is running an async operation"
        print(example)
        readyToNavigate = true
    }
}

If you want to restart the function from a child View then you can pass a Binding in to allow write access to the same State that is used as the id in .task.

6
son On

ContentView.doFunction is calling a static function in ContentView, not a single instance itself. And of course, it will not work.

If you want a couple views to share the same functionality, you might think about a kind of ViewModel, passing around.

class ViewModel: ObservableObject {
    @Published var readyToNavigate = false

    func doFunction() {
        //I don't know why you put await without any async task here. Just remove it.
        let example = "This is running an async operation"
        print(example)
        readyToNavigate = true
    }
}

Then in your views:

struct ContentView: View {
    @StateObject private var viewModel = ViewModel()
    
    var body: some View {
        NavigationStack {
            VStack {
                Button {
                    viewModel.doFunction()
                } label: {
                    Text("Button")
                }
                .navigationDestination(isPresented: $viewModel.readyToNavigate) {
                    SecondPage(viewModel: viewModel)}
                .navigationTitle("Test")
            }
        }
    }
}

struct SecondPage: View {
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        Button("run func from first page", action: {
            viewModel.doFunction()
            print("running async func from ContentView")
        })
    }
}