简体   繁体   English

NavigationLink 推送两次,然后弹出一次

[英]NavigationLink pushes twice, then pops once

I have a login screen on which I programmatically push to the next screen using a hidden NavigationLink tied to a state variable.我有一个登录屏幕,我使用与 state 变量绑定的隐藏NavigationLink以编程方式推送到下一个屏幕。 The push works, but it seems to push twice and pop once, as you can see on this screen recording:推送有效,但它似乎推送两次并弹出一次,正如您在此屏幕录像中看到的那样:

This is my view hierarchy:这是我的视图层次结构:

App
   NavigationView
      LaunchView
         LoginView
            HomeView

App : App

var body: some Scene {
    WindowGroup {
        NavigationView {
            LaunchView()
        }
        .navigationBarBackButtonHidden(true)
        .navigationBarHidden(true)
        .environmentObject(cache)
    }
}

LaunchView : 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)
    }
}

This is a cleaned version of my LoginView :这是我的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)
    }
}

The LoginView itself is child of a NavigationView . LoginView本身是NavigationView的子级。

The HomeView is really simple: 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)

    }
}

What's going wrong here?这里出了什么问题?


Update:更新:

I've realized that the issue does not occur, when I replace LaunchView() in App with LoginView() directly.当我直接将App中的LaunchView()替换为LoginView()时,我意识到该问题不会发生。 Not sure how this is related...不知道这有什么关系...


Update 2:更新 2:

As Tushar pointed out below, replacing destination: destinationView with destination: LoginView() fixes the problem – but obviously lacks required functionality.正如 Tushar 在下面指出的那样,将destination: destinationView替换为destination: LoginView()可以解决问题——但显然缺少所需的功能。
So I played around with that and now understand what's going on:所以我玩弄了它,现在明白发生了什么:

  1. LaunchView is rendered LaunchView被渲染
  2. LaunchView finds there's no user data yet, so pushes to LoginView LaunchView发现还没有用户数据,所以推送到LoginView
  3. upon user interaction, LoginView pushes to HomeView在用户交互时, LoginView推送到HomeView
  4. at this point, the NavigationLink inside LaunchView is called again (idk why but a breakpoint showed this), and since there is user data now, it renders the HomeView instead of the LoginView .此时, LaunchView内的NavigationLink再次被调用(idk 为什么但断点显示了这一点),并且由于现在用户数据,它呈现HomeView而不是LoginView

That's why we see only one push animation, and the LoginView becoming the HomeView w/o any push animation, b/c it's replaced , essentially.这就是为什么我们只看到一次推送 animation,而LoginView成为HomeView没有任何推送 animation,b/c 它基本上被替换了。

So now the objective is preventing LaunchView 's NavigationLink to re-render its destination view.所以现在的目标是阻止LaunchViewNavigationLink重新渲染其目标视图。

I was finally able to resolve the issue thanks to Tushar 's help in the comments .多亏了Tushar 在评论中的帮助,我终于能够解决这个问题。

Problem问题

The main problem lies in the fact I didn't understand how the environment object triggers re-renders.主要问题在于我不明白 object 环境如何触发重新渲染。 Here's what was going on:这是发生了什么:

  1. LaunchView has the environment object cache , which is changed in LoginView , when we set cache.user = user . LaunchView有环境 object cache ,当我们设置cache.user = user时,它在LoginView中发生了变化。
  2. That triggers the LaunchView to re-render its body.这会触发LaunchView重新渲染其主体。
  3. since the access token is not nil after login, on each re-render, the user would be fetched from the API via getUser() .由于登录后访问令牌nil ,因此在每次重新渲染时,都会通过getUser()从 API 获取用户。
  4. disregarding the fact whether that api call yields a valid user, shouldPush is set to true不管 api 调用是否产生有效用户这一事实, shouldPush设置为true
  5. LaunchView 's body is rendered again and the destinationView is computed again LaunchView的 body 再次渲染, destinationView再次计算
  6. since now the user does have data, the computed view becomes HomeView因为现在用户确实有数据,所以计算出来的视图变成了HomeView
    This is why we see the LoginView becoming the HomeView w/o any push – it's being replaced.这就是为什么我们看到LoginView在没有任何推送的情况下变成HomeView - 它正在被替换。
  7. at the same time, the LoginView pushes to HomeView , but since that view is already presented, it pops back to its first instance同时, LoginView推送到HomeView ,但由于该视图已经呈现,它会弹回它的第一个实例

Solution解决方案

To fix this, we need to make the property not computed, so that it only changes when we want it to.为了解决这个问题,我们需要使属性不被计算,以便它只在我们想要的时候改变。 To do so, we can make it a state-managed variable and set it manually in the response of the getUser api call:为此,我们可以将其设为状态管理变量并在getUser api 调用的响应中手动设置它:

Excerpt from LaunchView :摘自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.

相关问题 NavigationLink.navigationDestination 多次调用并推送到新视图两次 - NavigationLink .navigationDestination called multiple times and pushes to new View twice SwiftUI NavigationLink 导致推送发生,然后在完成后弹出,然后再次推送 - SwiftUI NavigationLink causes a push to happen, then it pops after completion, then pushes again for good 使用 isActive 的 List 中的 NavigationLink 推送错误的行 - NavigationLink in List using isActive pushes the wrong row SwiftUI:如果在 ForEach 中使用,NavigationLink 会立即弹出 - SwiftUI: NavigationLink pops immediately if used within ForEach SwiftUI 4 NavigationLink 正在调用链接两次 - SwiftUI 4 NavigationLink is invoking the link twice SwiftUI NavigationLink push in onAppear 使用@ObservableObject 时立即弹出视图 - SwiftUI NavigationLink push in onAppear immediately pops the view when using @ObservableObject macOS 上的 NavigationLink 导致源视图出现两次 - NavigationLink on macOS causing source view to appear twice 仅当列表未滚动到顶部时,导航链接才会在列表更新时弹出 - NavigationLink pops out upon List update ONLY when List is not scrolled to the top animateWithDuration动画一次但不两次 - animateWithDuration Animates Once But Not Twice SwiftUI 键盘弹出一次输入并不断出现和消失 - SwiftUI Keyboard pops up once for Input and keeps appearing and disappearing
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM