![](/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.