web-dev-qa-db-ja.com

`NavigationView`の` navigationBarItems`内に `NavigationLink`を配置した後に後方に移動すると、SwiftUIアプリがクラッシュするのはなぜですか?

最小限の再現可能な例(Xcode 11.2ベータ、これは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であるSwiftUIビュー内にネストされているnavigationBarItems修飾子の内側に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で修正されています!

47
Robert

これは私にとってかなり苦痛でした!ほとんどのアプリが完成し、クラッシュに対処するためのマインドスペースができるまで、私はそれを残しました。

SwifUIにはかなり素晴らしいものがあるが、デバッグが難しい場合があることは皆同意できると思います。

私の意見では、これはバグだと思います。ここに私の理論的根拠があります:

  • 約0.5秒の非同期遅延でpresentationMode dismiss呼び出しをラップすると、プログラムがクラッシュしないことがわかります。

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.presentationMode.wrappedValue.dismiss()
    } 
    
  • これは、このバグがSwiftUIが他のすべてのUIKitコードとインターフェイスしてさまざまなビューを管理する方法のかなり下にある予期しない動作であることを示唆しています。実際のコードによっては、ビューに多少の複雑さがある場合、実際にはクラッシュが発生しない場合があります。たとえば、ビューからリストのあるビューを閉じ、そのリストが空の場合、非同期遅延なしでクラッシュします。一方、リストビューにエントリが1つしかない場合は、ループを繰り返し実行して親ビューを生成すると、クラッシュが発生しないことがわかります。

Dismiss呼び出しを遅延でラップする私のソリューションがどれほど堅牢であるかはよくわかりません。もっとテストしなければならない。これについてアイデアがあれば教えてください!私はあなたから学ぶことをとても嬉しく思います!

18
Justin Ngan

これもかなり長い間私を苛立たせてきました。過去数か月間、Xcodeのバージョン、シミュレーターのバージョン、実際のデバイスのタイプやバージョンに応じて、ランダムに動作するようになり、正常に機能しなくなり、再び機能するようになりました。しかし、最近は一貫して失敗しているので、昨日は深く掘り下げました。現在Xcodeバージョン11.2.1(11B500)を使用しています。

問題はナビゲーションバーと、ボタンが追加された方法に関係しているようです。したがって、ボタン自体にNavigationLink()を使用する代わりに、非表示のNavigationLinkをアクティブにする@State変数を設定するアクションで標準のButton()を使用してみました。これはロバートの親ビューの代わりです:

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()
            } )
        }
    }
}
15
Chuck H

これは大きなバグであり、適切に回避する方法がわかりません。 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アプリ(私のものを含む)の負荷が確実に壊れるので、それを整理してください。

12
James

回避策として、上記のチャック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(これは重要です)内で使用して、ナビゲーションバーのボタンからトリガーできます。

VStack {
    HiddenNavigationList(destination: SearchView(), activateLink: self.$searchActivated)
    ...
}
.navigationBarItems(trailing: 
    Button("Search") { self.searchActivated = true }
)

これを "// HACK"コメントで囲み、Appleでこれを修正したら、置き換えることができます。

6
P. Ent

皆さんから提供された情報と、特にNavigationViewが配置されている場所について@Robertが作成したコメントに基づいて、少なくとも特定のシナリオで問題を回避する方法を見つけました。

私の場合、次のようにNavigationViewで囲まれたTabViewがありました。

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で動作します。

3
Julio Bailon

クラッシュは確認できませんが、コードに問題があります:

先頭のアイテムを設定することで、ナビゲーション遷移のデフォルトの動作を強制終了します。 (それが機能するかどうかを確認するために先頭側からスワイプしてみてください)。

したがって、そこにボタンを配置する必要はありません。そのままにしておくと、無料の戻るボタンがあります。

そして[〜#〜] hig [〜#〜]に従って忘れないでください。戻るボタンのタイトルはどこに行くかを表示する必要がありますnotそれは何ですか!したがって、最初のページのタイトルを設定して、ポップアップする戻るボタンを表示してみてください。

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

IOS 13.3で解決されています。 OSとxCodeを更新するだけです。

0
FRIDDAY

Xcode 11.2.1 Swift 5

とった!これを理解するのに数日かかりました...

私の場合、SwiftUIを使用しているときに、リストの下部が画面からはみ出し、リストアイテムを「移動」しようとした場合にのみ、クラッシュが発生します。最終的に見つけたのは、List()の下に「もの」が多すぎると、移動中にクラッシュするということです。たとえば、私のList()の下には、Text()、Spacer()、Button()、Spacer()Button()がありました。これらのオブジェクトのいずれか1つをコメントアウトした場合、突然クラッシュを再現できなくなりました。どのような制限があるのか​​はわかりませんが、このクラッシュが発生する場合は、リストの下にあるオブジェクトを削除して、効果があるかどうかを確認してください。

0
Dave Levy

FWIW-非表示のNavigationLinkハックを提案する上記のソリューションは、iOS 13.3b3での最善の回避策です。私は後世のためにFB7386339も提出しましたが、他の前述のFBと同様に「潜在的な修正が特定されました-将来のOSアップデートのために」閉鎖されました。

成功を祈っている。

0
Mike W.