在SwiftUI中导航前的触发功能

在SwiftUI中导航前的触发功能,swiftui,Swiftui,我的应用程序显示ID列表。当您点击ID时,将打开详细信息视图,该ID用于从网络检索详细信息。我现在用一个onAppear修饰符来做这件事,但我对此不满意。它不是很干净,会引起其他各种问题。我特别希望在用户导航时触发viewModel中的一个函数 可以将以下代码粘贴到新的SwiftUI项目中: import SwiftUI let fleet = Fleet(registries: ["NCC-1031"]) struct Fleet { let registrie

我的应用程序显示ID列表。当您点击ID时,将打开详细信息视图,该ID用于从网络检索详细信息。我现在用一个
onAppear
修饰符来做这件事,但我对此不满意。它不是很干净,会引起其他各种问题。我特别希望在用户导航时触发viewModel中的一个函数

可以将以下代码粘贴到新的SwiftUI项目中:

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
    }
}
导航时如何运行
fetchFromNetwork()
调用,但不使用appear

注意:我不能只使用带有动作的按钮,因为无法获取对viewModel的引用

事实上,我们可以。它不需要对viewModel进行任何附加引用

这是一个解决方案的演示,类似于之前引用的内容。使用Xcode 12.1/iOS 14.1进行测试

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
    }
}