I'm trying to find a viable way to handle navigation with data that has been returned from an asynchronous callback.
Consider the following example. The button in NavigationExampleView
is triggering some async method on a separate object, NavigationExampleViewModel
in this case. The returned data form the method, should then be pushed on the navigation stack in a UserView
. A NavigationLink
seems to be the way to archive this, but I can't find a way to get hold of a non-optional value of the data that I need to present.
struct User: Identifiable {
let id: String
let name: String
}
protocol API {
func getUser() -> AnyPublisher<User, Never>
}
struct NavigationExampleView: View {
@ObservedObject var vm: NavigationExampleViewModel
var body: some View {
HStack {
Button("Get User") {
vm.getUser()
}
NavigationLink.init(
destination: UserView(user: ???),
isActive: ???,
label: EmptyView.init
)
}
}
}
class NavigationExampleViewModel: ObservableObject {
@Published var isLoading = false
@Published var pushUser: User?
var cancellable: AnyCancellable?
let api: API
init(api: API) { self.api = api }
func getUser() {
isLoading = true
cancellable = api.getUser().sink { user in
self.pushUser = user
self.isLoading = false
}
}
}
struct UserView: View, Identifiable {
let id: String
let user: User
var body: some View {
Text(user.name)
}
}
Questions:
- How do I get hold of the data to present as a non-optional value in the view?
- What should I use as a binding to control presentation?
A way I can almost archive this is with the view modifier .sheet(item: Binding<Identifiable?>, content: Identifiable -> View)
, like this:
struct NavigationExampleView: View {
@ObservedObject var vm: NavigationExampleViewModel
var body: some View {
HStack {
Button("Get User") {
vm.getUser()
}
}.sheet(item: $vm.pushUser, content: UserView.init)
}
}
How can archive the same for pushing the view onto the navigation stack, instead of presenting it as a sheet?