簡體   English   中英

NavigationLink 推送兩次,然后彈出一次

[英]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()時,我意識到該問題不會發生。 不知道這有什么關系...


更新 2:

正如 Tushar 在下面指出的那樣,將destination: destinationView替換為destination: LoginView()可以解決問題——但顯然缺少所需的功能。
所以我玩弄了它,現在明白發生了什么:

  1. LaunchView被渲染
  2. LaunchView發現還沒有用戶數據,所以推送到LoginView
  3. 在用戶交互時, LoginView推送到HomeView
  4. 此時, LaunchView內的NavigationLink再次被調用(idk 為什么但斷點顯示了這一點),並且由於現在用戶數據,它呈現HomeView而不是LoginView

這就是為什么我們只看到一次推送 animation,而LoginView成為HomeView沒有任何推送 animation,b/c 它基本上被替換了。

所以現在的目標是阻止LaunchViewNavigationLink重新渲染其目標視圖。

多虧了Tushar 在評論中的幫助,我終於能夠解決這個問題。

問題

主要問題在於我不明白 object 環境如何觸發重新渲染。 這是發生了什么:

  1. LaunchView有環境 object cache ,當我們設置cache.user = user時,它在LoginView中發生了變化。
  2. 這會觸發LaunchView重新渲染其主體。
  3. 由於登錄后訪問令牌nil ,因此在每次重新渲染時,都會通過getUser()從 API 獲取用戶。
  4. 不管 api 調用是否產生有效用戶這一事實, shouldPush設置為true
  5. LaunchView的 body 再次渲染, destinationView再次計算
  6. 因為現在用戶確實有數據,所以計算出來的視圖變成了HomeView
    這就是為什么我們看到LoginView在沒有任何推送的情況下變成HomeView - 它正在被替換。
  7. 同時, 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM