![](/img/trans.png)
[英]NavigationLink .navigationDestination called multiple times and pushes to new View twice
[英]NavigationLink pushes twice, then pops once
我有一个登录屏幕,我使用与 state 变量绑定的隐藏NavigationLink
以编程方式推送到下一个屏幕。 推送有效,但它似乎推送两次并弹出一次,正如您在此屏幕录像中看到的那样:
这是我的视图层次结构:
App
NavigationView
LaunchView
LoginView
HomeView
App
:
var body: some Scene {
WindowGroup {
NavigationView {
LaunchView()
}
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
.environmentObject(cache)
}
}
LaunchView
:
struct LaunchView: View {
@EnvironmentObject var cache: API.Cache
@State private var shouldPush = API.shared.accessToken == nil
func getUser() {
[API call to get user, if already logged in](completion: { user in
if let user = user {
// in our example, this is NOT called
// thus `cache.user.hasData` remains `false`
cache.user = user
}
shouldPush = true
}
}
private var destinationView: AnyView {
cache.user.hasData
? AnyView(HomeView())
: AnyView(LoginView())
}
var body: some View {
if API.shared.accessToken != nil {
getUser()
}
return VStack {
ActivityIndicator(style: .medium)
NavigationLink(destination: destinationView, isActive: self.$shouldPush) {
EmptyView()
}.hidden()
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
这是我的LoginView
的清理版本:
struct LoginView: View {
@EnvironmentObject var cache: API.Cache
@State private var shouldPushToHome = false
func login() {
[API call to get user](completion: { user in
self.cache.user = user
self.shouldPushToHome = true
})
}
var body: some View {
VStack {
ScrollView(showsIndicators: false) {
// labels
// textfields
// ...
PrimaryButton(title: "Anmelden", action: login)
NavigationLink(destination: HomeView(), isActive: self.$shouldPushToHome) {
EmptyView()
}.hidden()
}
// label
// signup button
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
LoginView
本身是NavigationView
的子级。
HomeView
非常简单:
struct HomeView: View {
@EnvironmentObject var cache: API.Cache
var body: some View {
let user = cache.user
return Text("Hello, \(user.contactFirstname ?? "") \(user.contactLastname ?? "")!")
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
这里出了什么问题?
当我直接将App
中的LaunchView()
替换为LoginView()
时,我意识到该问题不会发生。 不知道这有什么关系...
正如 Tushar 在下面指出的那样,将destination: destinationView
替换为destination: LoginView()
可以解决问题——但显然缺少所需的功能。
所以我玩弄了它,现在明白发生了什么:
LaunchView
被渲染LaunchView
发现还没有用户数据,所以推送到LoginView
LoginView
推送到HomeView
LaunchView
内的NavigationLink
再次被调用(idk 为什么但断点显示了这一点),并且由于现在有用户数据,它呈现HomeView
而不是LoginView
。 这就是为什么我们只看到一次推送 animation,而LoginView
成为HomeView
没有任何推送 animation,b/c 它基本上被替换了。
所以现在的目标是阻止LaunchView
的NavigationLink
重新渲染其目标视图。
多亏了Tushar 在评论中的帮助,我终于能够解决这个问题。
主要问题在于我不明白 object 环境如何触发重新渲染。 这是发生了什么:
LaunchView
有环境 object cache
,当我们设置cache.user = user
时,它在LoginView
中发生了变化。LaunchView
重新渲染其主体。nil
,因此在每次重新渲染时,都会通过getUser()
从 API 获取用户。shouldPush
设置为true
LaunchView
的 body 再次渲染, destinationView
再次计算HomeView
LoginView
在没有任何推送的情况下变成HomeView
- 它正在被替换。LoginView
推送到HomeView
,但由于该视图已经呈现,它会弹回它的第一个实例为了解决这个问题,我们需要使属性不被计算,以便它只在我们想要的时候改变。 为此,我们可以将其设为状态管理变量并在getUser
api 调用的响应中手动设置它:
摘自LaunchView
:
// default value is `LoginView`, we could also
// set that in the guard statement in `getUser`
@State private var destinationView = AnyView(LoginView())
func getUser() {
// only fetch if we have an access token
guard API.shared.accessToken != nil else {
return
}
API.shared.request(User.self, for: .user(action: .get)) { user, _, _ in
cache.user = user ?? cache.user
shouldPush = true
// manually assign the destination view based on the api response
destinationView = cache.user.hasData
? AnyView(HomeView())
: AnyView(LoginView())
}
}
var body: some View {
// only fetch if user hasn't been fetched
if cache.user.hasData.not {
getUser()
}
return [all the views]
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.