使用来自异步回调的数据推送视图 [英] Push view with data from asynchronous callback
问题描述
我正在尝试找到一种可行的方法来处理从异步回调返回的数据的导航.
I'm trying to find a viable way to handle navigation with data that has been returned from an asynchronous callback.
考虑以下示例.NavigationExampleView
中的按钮在一个单独的对象上触发了一些异步方法,在这种情况下是 NavigationExampleViewModel
.从方法返回的数据,然后应该被推送到导航堆栈在一个UserView
.NavigationLink
似乎是存档的方式,但我找不到获取我需要呈现的数据的非可选值的方法.
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)
}
}
问题:
- 如何获取数据以在视图中显示为非可选值?
- 我应该使用什么作为控制演示的绑定?
我几乎可以存档的一种方法是使用视图修饰符 .sheet(item: Binding
,如下所示:
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?
推荐答案
基于 @pawello2222 回答中的 if let
方法,我创建了这个 ViewModifier
让您将视图推送到导航堆栈.它遵循与 .sheet 相同的模式
修饰符.
Based on the if let
method from @pawello2222's answer, I've created this ViewModifier
that let's you push a View to the Navigation Stack. It follows the same pattern as the .sheet
modifier.
import SwiftUI
private struct PushPresenter<Item: Identifiable, DestinationView: View>: ViewModifier {
let item: Binding<Item?>
let destination: (Item) -> DestinationView
let presentationBinding: Binding<Bool>
init(item: Binding<Item?>, onDismiss: (() -> Void)? = nil, content: @escaping (Item) -> DestinationView) {
self.item = item
self.destination = content
presentationBinding = Binding<Bool> {
item.wrappedValue != nil
} set: { isActive in
if !isActive && item.wrappedValue != nil {
onDismiss?()
item.wrappedValue = nil
}
}
}
func body(content: Content) -> some View {
ZStack {
content
NavigationLink(
destination: item.wrappedValue == nil ? nil : destination(item.wrappedValue!),
isActive: presentationBinding,
label: EmptyView.init
)
}
}
}
extension View {
/// Pushed a View onto the navigation stack using the given item as a data source for the View's content. **Notice**: Make sure to use `.navigationViewStyle(StackNavigationViewStyle())` on the parent `NavigationView` otherwise using this modifier will cause a memory leak.
/// - Parameters:
/// - item: A binding to an optional source of truth for the view's presentation. When `item` is non-nil, the system passes the `item`’s content to the modifier’s closure. This uses a `NavigationLink` under the hood, so make sure to have a `NavigationView` as a parent in the view hierarchy.
/// - onDismiss: A closure to execute when poping the view of the navigation stack.
/// - content: A closure returning the content of the view.
func push<Item: Identifiable, Content: View>(
item: Binding<Item?>,
onDismiss: (() -> Void)? = nil,
content: @escaping (Item) -> Content) -> some View
{
self.modifier(PushPresenter.init(item: item, onDismiss: onDismiss, content: content))
}
}
然后像这样使用它:
struct NavigationExampleView: View {
@ObservedObject var vm: NavigationExampleViewModel
var body: some View {
Button("Get User") {
vm.getUser()
}
.push(item: $vm.pushUser, content: UserView.init)
}
}
请务必将您的实现视图保留在 NavigationView
中,以便 push
生效.
Be sure to keep your implementing view in a NavigationView
for the push
to take effect.
编辑
我发现此解决方案存在问题.如果使用引用类型作为绑定 item
,对象将不会在解除时取消初始化.content
似乎持有对 item
的引用.只有在绑定上设置了新对象(下次推送屏幕时),对象才会被取消初始化.
I found an issue with this solution. If using a reference type as the binding item
, the object will not get deinitialised on dismiss. It seems like the content
is holding a reference to the item
. The Object will only the deinitialised once a new object is set on the binding (next time a screen is pushed).
@pawello2222 的答案也是如此,但在使用 sheet
修饰符时不是问题.欢迎任何有关如何解决此问题的建议.
This is also the case for @pawello2222's answer, but not an issue when using the sheet
modifier. Any suggestions on how to solve this would be much welcomed.
编辑 2
将 .navigationViewStyle(StackNavigationViewStyle())
添加到父 NavigationView
可消除出于某种原因的内存泄漏.有关详细信息,请参阅此处的答案.
Adding .navigationViewStyle(StackNavigationViewStyle())
to the parent NavigationView
removes the memory leak for some reason. See answer here for more details.
使用 if-let
方法导致推送视图转换停止工作.而是在数据未按下时将 nil
传递给 NavigationLink
的 destination
.我已经更新了 ViewModifier
来处理这个问题.
Using the if-let
-method caused the push view transition to stop working. Instead pass nil
to the NavigationLink
s destination
when the data is not pressent. I've updated ViewModifier
to handle this.
这篇关于使用来自异步回调的数据推送视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!