2

My app shows a list of IDs. When you tap on the ID, a details view is opened, and the ID is used to retrieve details from the network. I'm currently doing this with an onAppear modifier, but I'm not happy with that. It's not really clean, and causes all sorts of other issues. I'd like to specifically trigger a function in the viewModel when the user navigates.

The following code can be pasted into a new SwiftUI project:

import SwiftUI let fleet = Fleet(registries: ["NCC-1031"]) struct Fleet { let registries: [String] } struct Starship { let registry: String let name: String } class StarshipViewModel: ObservableObject { enum Mode { case idle case loading case success(Starship) } @Published var mode: Mode = .idle let registry: String private var timer: Timer? func fetchFromNetwork() { self.mode = .loading self.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { _ in let starship = Starship( registry: "NCC-1031", name: "Discovery" ) self.mode = .success(starship) }) } init(registry: String) { self.registry = registry } } struct StarshipDetails: View { @ObservedObject var viewModel: StarshipViewModel var body: some View { VStack { switch self.viewModel.mode { case .idle: Text("Idle") case .loading: Text("Loading") case .success(let starship): Text("Name: \(starship.name)") } } .onAppear(perform: { viewModel.fetchFromNetwork() }) } } struct ContentView: View { var body: some View { NavigationView { List { ForEach(fleet.registries, id: \.self) { registry in NavigationLink(destination: self.makeDestination(from: registry)) { Text(registry) } } } } } private func makeDestination(from registry: String) -> StarshipDetails { let viewModel = StarshipViewModel(registry: registry) // Don't do it here, because a network request will be done for the whole list // viewModel.fetchFromNetwork() let view = StarshipDetails(viewModel: viewModel) return view } } 

How should I run the fetchFromNetwork() call when navigating, but without using onAppear?

Note: I can't just use a button with an action, because there's no way to get a reference to the viewModel.

0

1 Answer 1

1

Actually, we can. It not needed any additional reference to viewModel.

Here is a demo of solution, similar to what was referenced before. Tested with Xcode 12.1 / iOS 14.1

demo

struct TestActionBeforeLink: View { @State private var navigate = false @State private var selectedRegistry: String = "" var body: some View { NavigationView { List { ForEach(fleet.registries, id: \.self) { registry in Button(action: { self.selectedRegistry = registry self.navigate = true }) { HStack { Text(registry) Spacer() Image(systemName: "chevron.right") // if one needed } } } } .background( NavigationLink(destination: self.makeDestination(from: selectedRegistry), isActive: $navigate) { EmptyView() } ) } } // no changes below, just removed comment private func makeDestination(from registry: String) -> StarshipDetails { let viewModel = StarshipViewModel(registry: registry) let view = StarshipDetails(viewModel: viewModel) return view } } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.