在将“ NavigationLink”放入“ NavigationView”中的“ navigationBarItems”内部后,为什么向后导航时我的SwiftUI应用会崩溃?


47

最小的可重现示例(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()
    }
}

问题似乎在于将我放置NavigationLinknavigationBarItems嵌套于SwiftUI视图(其根视图为)的修改器内部NavigationView。崩溃报告表明,当我向前导航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。手指交叉,修复很快就到了。

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


您上面提供的示例在Xcode 11.2 beta中可以正常工作。我们在这里缺少什么吗?
Subramanian Mariappan

@SubramanianMariappan对我来说,在11.2 beta上也很好用。
法罕·阿姆贾德

1
有趣的是,它每次都崩溃了。我什至尝试创建一个新项目并复制该确切代码代替ContentView.swift。我将对帖子进行编辑,但是崩溃只会在您前后导航时发生。
罗伯特

好问题!您的例子也每次都崩溃。我刚刚发布了一个新答案,对我来说效果很好。让我知道它是否也适用于您。谢谢。
Chuck H

1
感谢您关于苹果门票的更新!
马尔特,

Answers:


20

这对我来说是很痛苦的一点!我把它留了下来,直到我的大多数应用程序完成为止,并且我有足够的空间来处理崩溃问题。

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

我认为这是一个错误。这是我的基本原理:

  • 如果以大约半秒的异步延迟包装presentationMode dismiss调用,则应该发现程序不再崩溃。

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

我不太确定我将延迟中的dismiss调用包装起来的解决方案有多么健壮。我必须测试更多。如果您对此有任何想法,请告诉我!我很高兴向您学习!


1
非常聪明!我没想到。希望它能尽快解决!
罗伯特

1
@Robert它解决了您的问题吗?这是一个艰难的过程,因为我发现一个不相关的问题是在子导航视图中使用了Picker。尽管分段选择器样式有效,但单击后退按钮时,默认值似乎会导致崩溃。如果它仍然给您带来悲伤,我们可以进一步讨论。PS。我讨厌我的解决方案。这是一种黑客行为,但是如果苹果公司解决了计时问题,就不需要更新代码了。
贾斯汀·颜

2
我同意计时方面的问题,以及它在11.1中可以正常工作,并且超出了.navigationBarItems()要点,这是一个错误。
John M.

3
是的,我相信这是一个错误,这是我目前的赏金奖主要候选人。由于在撰写本文时,我还有4天的悬赏期,所以我只是坚持以防万一有人提出新信息:)。
罗伯特

1
这是一个非常有趣的提示,谢谢!不幸的是,我仍然确实有100%的时间在模拟器中崩溃了该应用程序:/它在设备上工作得更好,但并非没有崩溃。但这也是刻不容缓的情况。
吉利安

15

这也让我很沮丧。在过去的几个月中,根据Xcode版本,模拟器版本以及实际设备类型和(或)版本,它似乎从工作状态变为无法正常工作。但是,最近它对我来说一直是失败的,所以昨天我深入研究了它。我当前正在使用Xcode版本11.2.1(11B500)。

看来问题出在导航栏和按钮添加方式上。因此,我没有使用按钮本身的NavigationLink(),而是尝试使用具有设置@State var的动作的标准Button()来激活隐藏的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()
            } )
        }
    }
}

我可以确认这项工作(对于黑客来说真的很好;-))!苹果需要尽快修复此问题。xcode的11.2.1,卡塔利娜10.15.2(测试版),iOS的13.2.2
P.耳鼻喉科

1
我同意100%。通常,就SwiftUI中的导航而言,有很多是损坏的,或者是完全缺少的。当然哪个会导致我们遇到真正的问题。苹果没有“真相”(即文档和示例),只有像我们这样的黑客。顺便说一句,我非常使用上述技术,我创建了两个实用程序视图,这些视图有助于提高可读性。如果有人感兴趣,我会将它们添加到我的答案中。
Chuck H

感谢您的解决方法,它可以正常工作!
斯坦尼斯拉夫·波斯拉夫斯基(Stanislav Poslavsky)

1
这对我来说不止一个导航。跳回到上一个屏幕后,看不见的链接将不再起作用。
乔恩·谢尔

1
我在13.3上有几个真实的设备(内部版本17C54),它们都能按需工作。由于几乎所有测试都是在真实设备上进行的,因此我不会经常使用模拟器。但是我只是在13.3模拟器上尝试了我的测试用例,测试确实在那里失败了。我确实注意到Xcode模拟器上的iOS 13.3是比公共更新更早的版本(17C45)。我想知道是否有人在真实设备上观察到故障行为。
Chuck H

12

这是一个主要的错误,我找不到解决它的正确方法。在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")
                    }
            )
        }
    }
}

希望苹果能解决这个问题,因为它肯定会破坏SwiftUI应用程序(包括我的)的负载。


哈哈...真棒。您已经导航到Text视图,在SwiftUI中是一个视图!是的,那应该导航回它的父母,不是吗?但是,事实并非如此。有趣的是,示例中的行为破坏了UI,但实际上并未导致致命的崩溃。
贾斯汀·颜

是的,SwiftUI(以及React Native / Flutter等)的可组合性令人难以置信。为您提供如此多的控制/灵活性(至少在可行时)。
詹姆斯

1
确认在卡特琳娜(10.15.1),Xcode中(11.2.1),这的iOS崩溃(13.2.2)
P.耳鼻喉科

它不再在13.3中崩溃,但是导航仅在您第一次触发它时才起作用🤦‍♂️
James

6

作为一种解决方法,基于上面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修复此问题后可以将其替换。


这似乎只能在iOS 13.3中首次使用时起作用。
詹姆斯

3

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

1
@FRIDDAY此示例在13.1中有效,但在13.2中崩溃。这是一个已知的错误,我的目的是尝试通过变通办法来帮助处于相同情况的某人
Julio Bailon

1

Xcode 11.2.1 Swift 5

得到它了!我花了几天的时间才弄清楚这个...

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


0

尽管我看不到任何崩溃,但是您的代码有一些问题:

通过设置前导项目,实际上可以杀死导航过渡的默认行为。(尝试从前端滑动以查看是否有效)。

因此,无需在此处设置按钮。只需保持原样,您就可以拥有免费的后退按钮。

并做按不要忘记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()
    }
}

1
嘿,谢谢你的回答。尽管我确实同意保留默认的后退按钮行为是可取的,但这仍然会导致崩溃。
罗伯特

您使用什么版本?发送之前我已经测试过了。也许您还有另一个问题。您可以提供一个示例项目吗?
Mojtaba Hosseini

1
Xcode 11.2 beta就像问题一样。我在问题中提供的示例是重现崩溃所需的全部。
罗伯特

我使用的是相同的版本和相同的代码,但没有崩溃🤔
莫哈塔巴·侯赛尼

1
确认在卡特琳娜(10.15.1),Xcode中(11.2.1),这的iOS崩溃(13.2.2)
P.耳鼻喉科

0

FWIW-以上建议隐藏隐藏的NavigationLink Hack的解决方案仍然是iOS 13.3b3中的最佳解决方法。为了后代的缘故,我也提交了FB7386339,并且与上述其他FB相似地被关闭:“已确定可能的修补程序-将来用于操作系统更新。”

手指交叉。


请避免添加评论作为答案。
卡西克·拉梅什

0

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


1
10.15.2上的Xcode 11.3(11C29)对我造成了不同的行为:向后导航有效,但是之后NavigationLink不再起作用。单击它没有任何作用。
malte,

@malte最好为此提出一个新问题。在检查代码之前,请提供NavigationLink .buttonStyle(PlainButtonStyle())修饰符,然后重试。让我知道你是否提了一个问题。
星期五

1
你是对的。原来已经有一个新问题:stackoverflow.com/questions/59279176/…–
malte
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.