繁体   English   中英

为什么我的 SwiftUI 应用程序在“NavigationView”中的“navigationBarItems”中放置“NavigationLink”后向后导航时崩溃?

[英]Why does my SwiftUI app crash when navigating backwards after placing a `NavigationLink` inside of a `navigationBarItems` in a `NavigationView`?

编辑:这已在 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修饰符中,该修饰符嵌套在 SwiftUI 视图中,该视图的根视图是NavigationView 崩溃报告表明我试图弹出一个视图 controller ,当我向前导航到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 ,以防苹果公司的任何人关心:)。

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

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

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

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

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

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

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

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

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

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()
            } )
        }
    }
}

这是一个主要错误,我看不到解决它的正确方法。 在 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 应用程序(包括我的)。

作为一种解决方法,根据上面 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”注释中,这样当 Apple 修复它时,您可以替换它。

根据你们提供的信息,特别是@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。

Xcode 11.2.1 Swift 5

知道了。 我花了几天时间才弄明白这个...

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

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

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

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

并且不要忘记根据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()
    }
}

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

手指交叉。

它在 iOS 13.3 中得到解决。 只需更新您的操作系统和 xCode。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM