SwiftUI NavigationLink自动弹出

26

我有一个简单的用例,使用NavigationLink将一个屏幕推入另一个屏幕。在iOS 14.5 beta(1,2,3)中出现了奇怪的行为,即推送的屏幕被推出后立即弹出。

我成功创建了一个示例应用程序,在其中复制了此问题。我认为原因是存在@ Environment(\.presentationMode),它似乎重新创建视图,并导致推送的视图被弹出。

在Xcode 12 / iOS 14.4中,完全相同的代码可以正常工作。

输入图像描述

这是一个示例代码。

import SwiftUI

public struct FirstScreen: View {
    public init() {}
    public var body: some View {
        NavigationView {
            List {
                row
                row
                row
            }
        }
    }
    private var row: some View {
        NavigationLink(destination: SecondScreen()) {
            Text("Row")
        }
    }
}

struct SecondScreen: View {

    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    public var body: some View {
        VStack(spacing: 10) {
            NavigationLink(destination: thirdScreenA) {
                Text("Link to Third Screen A")
            }

            NavigationLink(destination: thirdScreenB) {
                Text("Link to Third Screen B")
            }

            Button("Go back", action: { presentationMode.wrappedValue.dismiss() })
        }

    }

    var thirdScreenA: some View {
        Text("thirdScreenA")
    }

    var thirdScreenB: some View {
        Text("thirdScreenB")
    }
}


struct FirstScreen_Previews: PreviewProvider {
    static var previews: some View {
        FirstScreen()
    }
}

在iOS15及以上版本(包括iOS15)中,这不应该成为一个问题。 - undefined
6个回答

37

我刚添加了.navigationViewStyle(StackNavigationViewStyle()),然后问题就解决了。

例子:--


 NavigationView {
         content
}
.navigationViewStyle(StackNavigationViewStyle())

5
我刚刚花了4个小时来解决这个bug。这是唯一有效的解决方法... - Quentin Hayot
我也遇到了一个问题,就是当一个行被按下时,应用程序应该转到另一个视图/屏幕并关闭该表格,但实际上视图/屏幕在被推出后立即弹出。我已经将此属性添加到NV中,但在iOS 15上无法正常工作。 - Jose Ricardo Citerio Alcala
2
这是截至2021年12月1日唯一可行的解决方案。 - Naveen Kumawat
1
对我有效。在午夜过后花了5个小时找到它。 - Ben Lu
1
对我来说可以用。花了4个小时找到了。这也适用于最新的iOS 15.4。非常感谢! - Qazi Ammar
显示剩余5条评论

20

4
@coopersita,我看到了你在苹果开发者论坛上的评论。看起来你正在面临不同的问题。如果你的DetailView更新的数据被用于MasterView中的ForEach列表,并且这导致Master重新渲染,则可能会导致子视图层次结构弹出。请提交另一个带有更多详细信息的问题。 - Paul Budzinsky
@PaulBudzinsky 我觉得这可能是我的情况。你能帮我一下吗? - Mohamed Elsayed
20
在iOS 15上似乎不再起作用。 - LiangWang
3
可以确认最新的iOS 15版本(2021年11月9日)存在这种不当行为。 - Entalpi
1
这不起作用!!! - Naveen Kumawat
显示剩余7条评论

9
应用.isDetailLink(false)到您的NavigationLink可能会有所作用。默认情况下为true。来自文档:
该方法设置导航链接在多列导航视图中使用时的行为,例如DoubleColumnNavigationViewStyle。 如果isDetailLink为true,则在主列中执行链接将使次要(详细信息)列的内容设置为链接的目标视图。如果isDetailLink为false,则链接在主列内导航到目标视图。

这个运行得非常好。这应该是正确的答案。 - Charlie Pico
它对我不起作用。整个行为非常奇怪。 - who9vy
这对我来说正如预期的那样有效,必须是被选中的答案。 - undefined

5

在我的情况下,我有:

(1) 一个带有根视图的选项卡视图,该视图具有2个导航起点

(2) 第一个和第二个导航具有相当多的嵌套视图

(3) 添加.navigationViewStyle(StackNavigationViewStyle())仅对只有一个导航起点的根视图解决了问题

(4) 只要我从同一视图中开始另一个导航,问题就会重新出现(仅适用于iOS 14.5至14.8)

(5) 仅添加

NavigationLink(destination: EmptyView()) {
    EmptyView()
}

对我没有用

(6) 在我的项目中,我有一个协调员负责创建视图模型(这些是发布属性),我有一个容器视图来处理所有的导航。导航链接是根据视图模型的存在与否创建的,因此如果视图模型存在,则会呈现该视图,并在视图被解除时将视图模型设置为nil。(我将在最后添加执行此操作的代码)

(7) 由于某种奇怪的原因,向负责导航的视图添加第三个导航会导致我的容器视图无法重新渲染,视图也停止了弹回。

简单地将NavigationLink添加到容器中并不起作用,但使用我正在使用的修饰符并将目标设置为Empty NavigationLink则可以。

这是用于导航的代码:

    func navigation<Item, Destination: View>(
        item: Binding<Item?>,
        @ViewBuilder destination: (Item) -> Destination
    ) -> some View {
        let isActive = Binding(
            get: { item.wrappedValue != nil },
            set: { value in
                if !value {
                    item.wrappedValue = nil
                }
            }
        )
        return navigation(isActive: isActive) {
            item.wrappedValue.map(destination)
        }
    }

    func navigation<Destination: View>(
        isActive: Binding<Bool>,
        @ViewBuilder destination: () -> Destination
    ) -> some View {
        overlay(
            NavigationLink(
                destination: isActive.wrappedValue ? destination() : nil,
                isActive: isActive,
                label: { EmptyView() }
            )
        )
    }

这是我的容器视图的工作方式。我的协调器具有可选的视图模型属性,并且该视图模型的存在与否会触发导航链接上的isActive值。添加最后一个 .navigation 就可以了。我的空容器只有一个空的 navigationLink。

My navigation container


1
尝试移除协调器和视图模型,使用它们与SwiftUI真的很奇怪。 - malhal
2
@malhal,你为什么认为这很奇怪?在SwiftUI中,你会如何将导航与视图解耦?我不确定你是否有一个非常深嵌套的导航需要在SwiftUI中处理,但我的项目非常大,在视图内使用导航链接并根据操作弹出到不同的视图是相当困难的。 - Marcela Ceneviva Auslenter
将相关属性组合到它们自己的结构体中,并使用 mutating func 进行操作。通过 @Binding 将其传递下去。请参阅 WWDC 2020 SwiftUI 中的数据基础知识,从第 4 分钟开始。 - malhal
谢谢!我正在使用相同的导航架构,你刚刚救了我的一天! - Niccolò Fontana

2

我在SwiftUI 1.0中遇到了很多这种行为。虽然不想要的popbacks令人发疯,但有一些逻辑上的,但未记录的可以消除导致弹出的情况,而无需首先假设(如我从其他关于后来iOS版本的答案中所做的那样)这是Swift bug。我想在这里分享它们,让其他开发者不必浪费时间在这个iOS13痛点上。

  1. 链接本身在您单击后被销毁。 显而易见:如果您正在使用依赖inActive决定是否进行推送的NavigationLinks,请确保它们依赖的布尔值不会在子视图中的某个位置发生更改。不太明显:您点击的链接是否已停止存在?如果是,那么您将返回!

例如:

// This variable will pop you back if false. 
// What if it's changed in @Binding somewhere down the hierarchy?
// What if it were an Environment Object that was being changed?

@State var modeForShowingButtons: Bool = true    

if $modeForShowingButtons == true {     
   NavigationLink(myDestination())
}
  • 竞争性导航链接。 在创建多个导航链接时要小心,例如在列表中,这些链接非常相似,共享变量或者竞争一个单一的isActive绑定变量。例如,一个包含5个项目的列表,每个项目都有自己的NavigationLink,而所有这些项目都依赖于同一个变量$shouldPush,可能会导致奇怪的弹回行为。
  • 我有一个带有弹回问题的列表。我尝试了Stack Overflow上的所有修复方法,但都没有成功,直到我简化了NavigationLink层次结构。我创建了一个单独的push @State在父视图中,而不是让每一行创建自己的多个导航链接,这些链接可能会相互冲突或矛盾。弹回问题消失了。

    希望这能帮助到某些人。这些可能看起来非常明显。但如果你像我一样习惯于Storyboard世界,在那里你可以推动一个segue然后忘记它,这些可能没有在你的脑海中出现。


    谢谢 #1,我有一个 if loading { <some view> } else { NavigationLink } 类型的设置。我在包含的始终可见的视图中添加了 .background { NavigationLink }。这样就解决了问题。 - undefined

    0
    NavigationView { content } 更改为 NavigationStack { content }

    1
    请添加一些解释,如果可能的话,附上一些来源链接,这有助于未来的读者理解,尤其是那些访问这个网站来学习的初学者。 - undefined
    老实说,我不知道为什么这个方法有效,而其他答案对我都不起作用。我只知道在另一个答案中提到的.navigationViewStyle(StackNavigationViewStyle())已经被苹果废弃了,所以我尝试了一个变体,结果奏效了。 - undefined
    这实际上很有帮助,请将其作为解释添加,并且如果可能的话,请提供一个确认其废弃的来源链接。 - undefined

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接