为啥我的 SwiftUI 应用程序在 `NavigationView` 中的 `navigationBarItems` 内放置 `NavigationLink` 后向后导航时崩溃?

Posted

技术标签:

【中文标题】为啥我的 SwiftUI 应用程序在 `NavigationView` 中的 `navigationBarItems` 内放置 `NavigationLink` 后向后导航时崩溃?【英文标题】:Why does my SwiftUI app crash when navigating backwards after placing a `NavigationLink` inside of a `navigationBarItems` in a `NavigationView`?为什么我的 SwiftUI 应用程序在 `NavigationView` 中的 `navigationBarItems` 内放置 `NavigationLink` 后向后导航时崩溃? 【发布时间】:2020-02-12 17:28:25 【问题描述】:

编辑:这已在 iOS 13.3 中修复!

最小的可重现示例(Xcode 11.2 beta,在 Xcode 11.1 中有效):

struct Parent: View 
    var body: some View 
        NavigationView 
            Text("Hello World")
                .navigationBarItems(
                    trailing: NavigationLink(destination: Child(), label:  Text("Next") )
                )
        
    


struct Child: View 
    @Environment(\.presentationMode) var presentation
    var body: some View 
        Text("Hello, World!")
            .navigationBarItems(
                leading: Button(
                    action: 
                        self.presentation.wrappedValue.dismiss()
                    ,
                    label:  Text("Back") 
                )
            )
    


struct ContentView: View 
    var body: some View 
        Parent()
    

问题似乎在于将我的 NavigationLink 放在 navigationBarItems 修饰符内,该修饰符嵌套在根视图为 NavigationView 的 SwiftUI 视图内。崩溃报告表明,当我向前导航到 Child 然后返回到 Parent 时,我正试图弹出一个不存在的视图控制器。

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Tried to pop to a view controller that doesn't exist.'
*** First throw call stack:

如果我改为将 NavigationLink 放在视图主体中,如下所示,它工作得很好。

struct Parent: View 
    var body: some View 
        NavigationView 
            NavigationLink(destination: Child(), label:  Text("Next") )
        
    

这是 SwiftUI 错误还是预期行为?

编辑:我已经在他们的反馈助理中向 Apple 提出了一个问题,ID 为 FB7423964,以防任何来自 Apple 的人关心:)。

编辑:我在反馈助手中打开的工单表明有 10 多个类似的报告问题。他们已经用Resolution: Potential fix identified - For a future OS update 更新了分辨率。手指交叉,修复很快就会着陆。

【问题讨论】:

您在上面提供的示例适用于 Xcode 11.2 beta。我们在这里遗漏了什么吗? @SubramanianMariappan 它在 11.2 beta 上对我来说也可以正常工作。 有趣的是,它每次都为我崩溃。我什至尝试创建一个新项目并复制该确切代码来代替ContentView.swift。我将对帖子进行编辑,但仅当您向前导航然后返回时才会发生崩溃。 好问题!您在这里的示例也每次都为我崩溃。我刚刚发布了一个对我非常有效的新答案。让我知道它是否也适合您。谢谢。 感谢有关苹果门票的更新! 【参考方案1】:

这对我来说是个痛点!我离开了它,直到我的大部分应用程序完成并且我有足够的空间来处理崩溃。

我认为我们都同意 SwifUI 有一些非常棒的东西,但调试可能很困难。

在我看来,我会说这是一个BUG。这是我的理由:

如果你在大约半秒的异步延迟中包装presentationModedismiss调用,你应该会发现程序将不再崩溃。

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) 
    self.presentationMode.wrappedValue.dismiss()
 

这向我表明,该错误是 SwiftUI 如何与所有其他 UIKit 代码交互以管理各种视图的一种意外行为。根据您的实际代码,您可能会发现如果视图中存在一些较小的复杂性,则实际上不会发生崩溃。例如,如果您从一个视图中解散到一个具有列表的视图,并且该列表为空,您将在没有异步延迟的情况下发生崩溃。另一方面,如果您在该列表视图中甚至只有一个条目,强制循环迭代以生成父视图,您将看到不会发生崩溃。

我不太确定我在延迟中包装解雇呼叫的解决方案有多强大。我必须对其进行更多测试。如果您对此有任何想法,请告诉我!很高兴向您学习!

【讨论】:

非常聪明!我没有想到这一点。希望尽快修复! @Robert 解决了您的问题吗?这是一个棘手的问题,因为我发现一个不相关的问题是在子导航视图中使用 Picker。虽然分段选取器样式有效,但单击后退按钮时,默认设置似乎会在同一点导致崩溃。如果它仍然让您感到悲伤,我们可以进一步讨论。 PS。我讨厌我的解决方案。这是一个 hack,但如果 Apple 修复了时间问题,它不应该需要更新代码。 我同意时间方面,以及它在 11.1 中运行良好并且在 .navigationBarItems() 之外运行的事实表明这是一个错误。 是的,我认为这是一个错误,这是我目前获得赏金的主要候选人。由于截至撰写本文时我的赏金还剩 4 天,所以我只是坚持以防万一有人提供新信息:)。 这是一个非常有趣的提示,谢谢!不幸的是,我仍然在 100% 的时间内可靠地使模拟器中的应用程序崩溃:/ 它在设备上运行得更好,但并非完全没有崩溃。但这也是毫不拖延的情况。【参考方案2】:

这也让我沮丧了一段时间。在过去的几个月里,根据 Xcode 版本、模拟器版本和真实设备类型和/或版本,它从工作到失败再到再次工作,似乎是随机的。但是,最近它对我来说一直失败,所以昨天我深入研究了它。我目前正在使用 Xcode 版本 11.2.1 (11B500)。

看起来问题围绕着导航栏和按钮添加到它的方式。因此,我没有对按钮本身使用 NavigationLink(),而是尝试使用标准 Button() 和一个操作,该操作设置一个激活隐藏 NavigationLink 的@State var。这是 Robert 的 Parent View 的替代品:

struct Parent: View 
    @State private var showingChildView = false
    var body: some View 
        NavigationView 
            VStack 
                Text("Hello World")
                NavigationLink(destination: Child(),
                               isActive: self.$showingChildView)
                 EmptyView() 
                    .frame(width: 0, height: 0)
                    .disabled(true)
                    .hidden()            
             
             .navigationBarItems(
                 trailing: Button(action: self.showingChildView = true )  Text("Next") 
             )
        
    

对我来说,这在所有模拟器和所有真实设备上都非常一致。

这是我的辅助视图:

struct HiddenNavigationLink<Destination : View>: View 

    public var destination:  Destination
    public var isActive: Binding<Bool>

    var body: some View 

        NavigationLink(destination: self.destination, isActive: self.isActive)
         EmptyView() 
            .frame(width: 0, height: 0)
            .disabled(true)
            .hidden()
    


struct ActivateButton<Label> : View where Label : View 

    public var activates: Binding<Bool>
    public var label: Label

    public init(activates: Binding<Bool>, @ViewBuilder label: () -> Label) 
        self.activates = activates
        self.label = label()
    

    var body: some View 
        Button(action:  self.activates.wrappedValue = true , label:  self.label  )
    

这里是一个用法示例:

struct ContentView: View 
    @State private var showingAddView: Bool = false
    var body: some View 
        NavigationView 
            VStack 
                Text("Hello, World!")
                HiddenNavigationLink(destination: AddView(), isActive: self.$showingAddView)
            
            .navigationBarItems(trailing:
                HStack 
                    ActivateButton(activates: self.$showingAddView)  Image(uiImage: UIImage(systemName: "plus")!) 
                    EditButton()
             )
        
    

【讨论】:

我可以确认这是可行的(对于黑客来说真的很好;-))!苹果需要尽快解决这个问题。 Xcode 11.2.1、Catalina 10.15.2(测试版)、ios 13.2.2 我同意 100%。一般来说,关于 SwiftUI 中的导航,有很多东西要么损坏,要么完全丢失。这当然会导致我们遇到真正的问题。 Apple 没有“事实来源”(即文档和示例),只有像我们这样的黑客。顺便说一句,我经常使用上述技术,我创建了两个实用程序视图,它们对可读性有很大帮助。如果有人感兴趣,我会将它们添加到我的答案中。 这对我来说不适用于多个导航。弹回上一个屏幕后,不可见的链接将不再起作用。 看起来这仅在 iOS 13.3 上被破坏。解决方法适用于 13.2。有什么新想法吗? 我在 13.3 (build 17C54) 有几个真实设备,它们都可以按需要工作。由于我几乎所有的测试都是在真实设备上进行的,所以我不经常使用模拟器。但我只是在 13.3 模拟器上尝试了我的测试用例,但测试确实失败了。我确实注意到 Xcode 模拟器上的 iOS 13.3 是比公共更新更早的版本 (17C45)。我很想知道是否有人在真实设备上观察到失败行为。【参考方案3】:

这是一个重大错误,我看不到解决它的正确方法。在 iOS 13/13.1 中运行良好,但在 13.2 中崩溃。

您实际上可以以更简单的方式复制它(这段代码实际上就是您所需要的)。

struct ContentView: View 
    var body: some View 
        NavigationView 
            Text("Hello, World!").navigationBarTitle("To Do App")
                .navigationBarItems(leading: NavigationLink(destination: Text("Hi")) 
                    Text("Nav")
                    
            )
        
    

希望 Apple 解决它,因为它肯定会破坏大量 SwiftUI 应用程序(包括我的)。

【讨论】:

哈哈...这太棒了。您已经导航到 SwiftUI 中的 Text 视图,它是一个视图!是的,那应该导航回它的父母,不是吗?然而,事实并非如此。有趣的是,您的示例中的行为破坏了 UI,但实际上并没有导致致命的崩溃。 是的,SwiftUI(以及 React Native/Flutter 等)的可组合性令人难以置信。为您提供如此多的控制/灵活性(至少当它起作用时)。 确认这会在 Catalina (10.15.1)、Xcode (11.2.1)、iOS (13.2.2) 上崩溃 它在 13.3 中不再崩溃,但是导航似乎只在您第一次触发时才起作用?‍♂️【参考方案4】:

作为一种解决方法,根据上面 Chuck H 的回答,我将 NavigationLink 封装为隐藏元素:

struct HiddenNavigationLink<Content: View>: View 
var destination: Content
@Binding var activateLink: Bool

var body: some View 
    NavigationLink(destination: destination, isActive: self.$activateLink) 
        EmptyView()
    
    .frame(width: 0, height: 0)
    .disabled(true)
    .hidden()


然后您可以在 NavigationView 中使用它(这很重要)并从导航栏中的 Button 触发它:

VStack 
    HiddenNavigationList(destination: SearchView(), activateLink: self.$searchActivated)
    ...

.navigationBarItems(trailing: 
    Button("Search")  self.searchActivated = true 
)

将其包装在“//HACK” cmets 中,以便 Apple 修复此问题时您可以替换它。

【讨论】:

这似乎只适用于 iOS 13.3 的第一次使用。【参考方案5】:

根据你们提供的信息,特别是@Robert 对 NavigationView 放置位置的评论,我找到了一种方法来解决这个问题,至少在我的具体情况下。

在我的例子中,我有一个 TabView,它包含在这样的 NavigationView 中:

struct ContentViewThatCrashes: View 
@State private var selection = 0

var body: some View 
    NavigationView
        TabView(selection: $selection)
            NavigationLink(destination: NewView())
                Text("First View")
                    .font(.title)
            
            .tabItem 
                VStack 
                    Image("first")
                    Text("First")
                
            
            .tag(0)
            NavigationLink(destination: NewView())
                Text("Second View")
                    .font(.title)
            
            .tabItem 
                VStack 
                    Image("second")
                    Text("Second")
                
            
            .tag(1)
        
    
  

此代码崩溃,因为每个人都在 iOS 13.2 中报告并在 iOS 13.1 中工作。经过一番研究,我找到了解决这种情况的方法。

基本上,我在每个选项卡上分别将 NavigationView 移动到每个屏幕,如下所示:

struct ContentViewThatWorks: View 
@State private var selection = 0

var body: some View 
    TabView(selection: $selection)
        NavigationView
            NavigationLink(destination: NewView())
                Text("First View")
                    .font(.title)
            
        
        .tabItem 
            VStack 
                Image("first")
                Text("First")
            
        
        .tag(0)
        NavigationView
            NavigationLink(destination: NewView())
                Text("Second View")
                    .font(.title)
            
        
        .tabItem 
            VStack 
                Image("second")
                Text("Second")
            
        
        .tag(1)
    
  

在某种程度上违背了 SwiftUI 简单的前提,但它适用于 iOS 13.2。

【讨论】:

这可行,但问题是删除 NewView 上的 tabViews。 @FRIDDAY 此示例在 13.1 中有效,但在 13.2 中崩溃。这是一个已知的错误,我的目的是尝试通过解决方法帮助处于相同情况的某人 我认为您的问题可能是 Swift(不是 SwiftUI)通常将 NavigationControllers 嵌入 TabViewControllers 中。我希望 SwiftUI 需要一种类似的方法来同时使用这两者。 看看github上的bottombar包。您可以查看源代码,它只有 4 个文件。这是一个很好的例子,说明如何使用隐藏在子视图上的标签栏实现理想的导航层次结构。我已经验证了在 iOS 15.2 上实现这个 repo 中的模式对我有用,没有崩溃。 github.com/smartvipere75/bottombar-swiftui【参考方案6】:

Xcode 11.2.1 Swift 5

知道了!我花了几天时间才弄明白这个……

在我使用 SwiftUI 的情况下,只有当我的列表底部超出屏幕然后我尝试“移动”任何列表项时,我才会崩溃。我最终发现的是,如果我在 List() 下面有太多“东西”,那么它会在移动中崩溃。例如,在我的 List() 下面有一个 Text()、Spacer()、Button()、Spacer() Button()。如果我注释掉其中任何一个对象,那么我突然无法重新创建崩溃。我不确定限制是什么,但如果您遇到此崩溃,请尝试删除列表下方的对象以查看是否有帮助。

【讨论】:

【参考方案7】:

虽然我看不到任何崩溃,但您的代码存在一些问题:

通过设置前导项,您实际上取消了导航转换的默认行为。 (尝试从前端滑动,看看是否有效)。

所以不需要在那里有一个按钮。保持原样,您就有一个免费的后退按钮。

并且不要忘记根据HIG,返回按钮标题应该显示它的去向,不是它是什么!因此,请尝试为第一页设置一个标题,以便在弹出的任何后退按钮中显示它。

struct Parent: View 
    var body: some View 
        NavigationView 
            Text("Hello World")
                .navigationBarItems(
                    trailing: NavigationLink(destination: Child(), label:  Text("Next") )
                )
                .navigationBarTitle("First Page",displayMode: .inline)
        
    


struct Child: View 
    @Environment(\.presentationMode) var presentation
    var body: some View 
        Text("Hello, World!")
    


struct ContentView: View 
    var body: some View 
        Parent()
    

【讨论】:

您好,谢谢您的回答。虽然我同意保留默认的后退按钮行为是可取的,但它仍然会导致崩溃。 你用的是什么版本?发送前我已经测试过了。也许你还有另一个问题。可以提供一个示例项目吗? Xcode 11.2 beta 就像问题说的那样。我在问题中提供的示例就是重现崩溃所需的全部内容。 我使用相同的版本和相同的代码,但没有崩溃? 确认这会在 Catalina (10.15.1)、Xcode (11.2.1)、iOS (13.2.2) 上崩溃【参考方案8】:

FWIW - 以上建议隐藏 NavigationLink Hack 的解决方案仍然是 iOS 13.3b3 中的最佳解决方法。为了子孙后代,我还提交了 FB7386339,并且与上述其他 FB 类似地关闭:“确定的潜在修复 - 用于未来的操作系统更新”。

手指交叉。

【讨论】:

请避免添加 cmets 作为答案。【参考方案9】:

iOS 13.3 已解决。只需更新您的操作系统和 xCode。

【讨论】:

Xcode 11.3 (11C29) on 10.15.2 对我来说导致了不同的行为:向后导航正在工作,但之后 NavigationLink 不再起作用。点击它什么都不做。 @malte 最好为此打开一个新问题。在我检查你的代码之前,给你的 NavigationLink .buttonStyle(PlainButtonStyle()) 修饰符,然后再试一次。如果您提出问题,请告诉我。 你是对的。原来已经有一个新问题:***.com/questions/59279176/…

以上是关于为啥我的 SwiftUI 应用程序在 `NavigationView` 中的 `navigationBarItems` 内放置 `NavigationLink` 后向后导航时崩溃?的主要内容,如果未能解决你的问题,请参考以下文章

为啥将 ViewModel 添加到我的 SwiftUI 应用程序后我的 UI 没有更新?

SwiftUI 导航链接

为啥我的视图在 swiftUI 中首次出现在屏幕上时没有转换

为啥我的 SwiftUI JSONDecoder 会出现致命错误?

为啥我的 SwiftUI 动画在旋转时恢复到前一帧?

为啥即使发布者发出一个值,我的视图也没有在 SwiftUI 中呈现