Mengapa aplikasi SwiftUI saya mogok saat menavigasi mundur setelah menempatkan bagian `NavigationLink` dalam` navigationBarItems` dalam `NavigationView`?

47

Contoh minimal yang dapat direproduksi (Xcode 11.2 beta, ini berfungsi di 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()
    }
}

Masalahnya tampaknya terletak pada menempatkan NavigationLinkbagian dalam saya dari navigationBarItemspengubah yang bersarang di dalam tampilan SwiftUI yang tampilan root-nya adalah a NavigationView. Laporan kerusakan menunjukkan bahwa saya mencoba untuk pop ke controller tampilan yang tidak ada ketika saya menavigasi ke depan Childdan kemudian kembali ke Parent.

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

Jika saya sebaliknya menempatkan itu NavigationLinkdi tubuh tampilan seperti di bawah ini, itu berfungsi dengan baik.

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

Apakah ini bug SwiftUI atau perilaku yang diharapkan?

EDIT: Saya telah membuka masalah dengan Apple dalam asisten umpan balik mereka dengan ID FB7423964jika ada orang di luar sana dari Apple yang peduli untuk menimbang :).

EDIT: Tiket terbuka saya di asisten umpan balik menunjukkan ada 10+ masalah yang dilaporkan serupa. Mereka telah memperbarui resolusi dengan Resolution: Potential fix identified - For a future OS update. Jari-jari bahwa perbaikan tanah segera.

EDIT: Ini telah diperbaiki di iOS 13.3!

Robert
sumber
Contoh yang Anda berikan di atas berfungsi dengan baik dengan Xcode 11.2 beta. Apakah kita kehilangan sesuatu di sini?
Subramanian Mariappan
@ SubramanianMariappan Ini bekerja dengan baik untuk saya juga pada 11.2 beta.
Farhan Amjad
1
Menarik, itu crash untuk saya setiap saat. Saya bahkan mencoba membuat proyek baru dan menyalin kode yang tepat itu ContentView.swift. Saya akan mengedit posting, tetapi crash hanya terjadi ketika Anda menavigasi ke depan dan kemudian kembali.
Robert
Pertanyaan bagus! Contoh Anda di sini juga macet untuk saya. Saya baru saja memposting jawaban baru yang bekerja sangat baik untuk saya. Beri tahu saya jika itu bekerja untuk Anda juga. Terima kasih.
Chuck H
1
Terima kasih atas pembaruan terkait tiket apel!
malte

Jawaban:

20

Ini adalah titik yang cukup menyakitkan bagi saya! Saya meninggalkannya sampai sebagian besar aplikasi saya selesai dan saya memiliki ruang pikiran untuk menghadapi tabrakan itu.

Saya pikir kita semua bisa sepakat bahwa ada beberapa hal yang cukup luar biasa dengan SwifUI tetapi proses debugnya sulit.

Menurut pendapat saya, saya akan mengatakan bahwa ini adalah BUG. Inilah alasan saya:

  • Jika Anda membungkus panggilan pemutusan presentationMode dalam penundaan asinkron sekitar setengah detik, Anda akan menemukan bahwa program tidak akan lagi crash.

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.presentationMode.wrappedValue.dismiss()
    } 
  • Ini menunjukkan kepada saya bahwa bug adalah perilaku yang tidak terduga turun jauh dalam cara SwiftUI berinteraksi dengan semua kode UIKit lainnya untuk mengelola berbagai tampilan. Tergantung pada kode Anda yang sebenarnya, Anda mungkin menemukan bahwa jika ada beberapa kompleksitas kecil dalam tampilan, kerusakan itu sebenarnya tidak akan terjadi. Misalnya, jika Anda menolak dari tampilan ke tampilan yang memiliki daftar, dan daftar itu kosong, Anda akan mendapatkan crash tanpa penundaan asinkron. Di sisi lain, jika Anda hanya memiliki satu entri di tampilan daftar itu, memaksa iterasi loop untuk menghasilkan tampilan induk, Anda akan melihat bahwa crash tidak akan terjadi.

Saya tidak begitu yakin seberapa kuat solusi saya untuk menutup panggilan pemberhentian. Saya harus mengujinya lebih jauh. Jika Anda memiliki ide tentang ini, beri tahu saya! Saya akan sangat senang belajar dari Anda!

Justin Ngan
sumber
1
Sangat pintar! Saya tidak memikirkan itu. Berharap segera diperbaiki!
Robert
1
@ Robert Apakah itu memperbaiki masalah Anda? Ini adalah masalah yang sulit karena masalah yang tidak terkait yang saya temukan menggunakan Picker di dalam tampilan navigasi anak. Sementara gaya pemilih tersegmentasi berfungsi, default muncul untuk menyebabkan crash pada titik yang sama, ketika mengklik tombol kembali. Kita bisa berdiskusi lebih lanjut jika masih membuat Anda sedih. PS. Saya benci solusi saya. Ini peretasan, tetapi peretasan yang tidak memerlukan pembaruan kode jika Apple memperbaiki masalah waktu.
Justin Ngan
2
Saya setuju bahwa aspek waktu, bersama dengan fakta bahwa itu bekerja dengan baik di 11.1 dan bekerja di luar .navigationBarItems()poin untuk ini menjadi bug.
John M.
3
Ya, saya percaya ini adalah bug dan ini adalah kandidat utama saya saat ini untuk penghargaan hadiah. Karena saya memiliki 4 hari tersisa di karunia pada saat penulisan ini saya hanya bertahan kalau-kalau ada yang datang dengan info baru :).
Robert
1
Ini tip yang sangat menarik, terima kasih untuk itu! Sayangnya saya masih andal menabrak aplikasi di simulator 100% dari waktu: / Ini berfungsi lebih baik pada perangkat, tetapi bukan tanpa menabrak sama sekali. Tapi itu juga terjadi tanpa penundaan.
Kilian
15

Ini juga membuat saya frustasi selama beberapa waktu. Selama beberapa bulan terakhir, tergantung pada versi Xcode, versi simulator dan jenis perangkat nyata dan / atau versi, itu telah berubah dari bekerja menjadi gagal untuk bekerja lagi, tampaknya secara acak. Namun, baru-baru ini telah gagal secara konsisten untuk saya, jadi kemarin saya mengambil menyelam jauh ke dalamnya. Saat ini saya menggunakan Xcode Versi 11.2.1 (11B500).

Sepertinya masalah berputar di sekitar Nav Bar dan cara tombol ditambahkan ke dalamnya. Jadi alih-alih menggunakan NavigationLink () untuk tombol itu sendiri, saya mencoba menggunakan Tombol standar () dengan aksi yang menetapkan @State var yang mengaktifkan NavigationLink yang tersembunyi. Ini adalah pengganti untuk 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") }
             )
        }
    }
}

Bagi saya, ini bekerja sangat konsisten di semua simulator dan semua perangkat nyata.

Inilah pandangan pembantu saya:

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

Berikut ini contoh penggunaannya:

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()
            } )
        }
    }
}
Chuck H
sumber
Saya dapat mengkonfirmasi ini berfungsi (sangat bagus untuk hack ;-))! Namun Apple perlu segera memperbaikinya. Xcode 11.2.1, Catalina 10.15.2 (beta), iOS 13.2.2
P. Ent
1
Saya setuju 100%. Secara umum, sehubungan dengan navigasi di SwiftUI, ada banyak yang rusak atau hilang begitu saja. Yang tentu saja menuntun kita ke masalah sebenarnya. Tidak ada "sumber kebenaran" (yaitu dokumentasi dan contoh) dari Apple, hanya peretasan seperti kita. BTW, saya menggunakan teknik di atas begitu banyak, saya telah membuat dua pandangan utilitas yang banyak membantu dengan keterbacaan. Saya akan menambahkannya ke jawaban saya jika ada yang tertarik.
Chuck H
Terima kasih atas solusinya, itu hanya berfungsi!
Stanislav Poslavsky
1
Ini tidak berfungsi untuk saya untuk lebih dari satu navigasi. Setelah Anda kembali ke layar sebelumnya, tautan tak terlihat tidak lagi berfungsi.
Jon Shier
1
Saya memiliki beberapa perangkat nyata pada 13,3 (build 17C54) dan semuanya bekerja seperti yang diinginkan. Karena saya melakukan hampir semua pengujian pada perangkat nyata, saya tidak sering menggunakan simulator. Tapi saya baru saja mencoba test case saya pada simulator 13.3 dan tes tidak gagal di sana. Saya memang memperhatikan bahwa iOS 13.3 pada simulator Xcode adalah build sebelumnya (17C45) daripada pembaruan publik. Saya akan tertarik untuk mengetahui apakah ada yang mengamati perilaku gagal pada perangkat nyata.
Chuck H
12

Ini adalah bug utama dan saya tidak bisa melihat cara yang tepat untuk mengatasinya. Bekerja dengan baik di iOS 13 / 13.1 tetapi 13.2 macet.

Anda sebenarnya dapat mereplikasi dengan cara yang lebih sederhana (kode ini benar-benar yang Anda butuhkan).

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

Semoga Apple mengatasinya karena pasti akan memecah banyak aplikasi SwiftUI (termasuk milik saya).

James
sumber
Haha ... Itu luar biasa. Anda telah menavigasi ke tampilan Teks yang dalam SwiftUI, adalah tampilan! Ya, yang seharusnya menavigasi kembali ke orang tua itu bukan? Namun, tidak. Sangat menarik bahwa perilaku dari contoh Anda merusak UI tetapi sebenarnya tidak menyebabkan kerusakan fatal.
Justin Ngan 6-19
Ya kompabilitas dari SwiftUI (dan React Native / Flutter dll) luar biasa. Memberi Anda begitu banyak kontrol / fleksibilitas (saat itu bekerja setidaknya).
James
1
Konfirmasikan ini macet di Catalina (10.15.1), Xcode (11.2.1), iOS (13.2.2)
P. Ent
Tidak lagi mogok di 13,3, namun navigasi sepertinya hanya berfungsi saat pertama kali Anda memicunya 🤦‍♂️
James
6

Sebagai solusinya, berdasarkan jawaban Chuck H di atas, saya telah merangkum NavigationLink sebagai elemen tersembunyi:

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

Kemudian Anda dapat menggunakannya di dalam NavigationView (yang sangat penting) dan memicunya dari Tombol di bilah navigasi:

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

Bungkus ini dalam komentar "// HACK" sehingga ketika Apple memperbaikinya, Anda dapat menggantinya.

P. Ent
sumber
Ini sepertinya hanya berfungsi pada penggunaan pertama di iOS 13.3.
James
3

Berdasarkan informasi yang kalian berikan dan khususnya komentar yang dibuat oleh @Robert tentang di mana NavigationView ditempatkan, saya telah menemukan cara untuk menyelesaikan masalah setidaknya pada skenario spesifik saya.

Dalam kasus saya, saya memiliki TabView yang terlampir dalam NavigationView seperti ini:

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

Kode ini macet karena semua orang melaporkan di iOS 13.2 dan berfungsi di iOS 13.1. Setelah beberapa penelitian saya menemukan solusi untuk situasi ini.

Pada dasarnya, saya memindahkan NavigationView ke setiap layar secara terpisah pada setiap tab seperti ini:

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

Entah bagaimana bertentangan dengan premis kesederhanaan SwiftUI tetapi bekerja di iOS 13.2.

Julio Bailon
sumber
ini berfungsi tetapi, masalahnya adalah menghapus tabViews di NewView.
JUMAT
1
@FRIDDAY contoh ini berfungsi di 13.1 tetapi macet di 13.2. Ini adalah bug yang diketahui dan niat saya adalah untuk mencoba membantu seseorang dalam skenario yang sama dengan solusinya
Julio Bailon
1

Xcode 11.2.1 Swift 5

OKE! Butuh beberapa hari untuk memikirkan ini ...

Dalam kasus saya ketika menggunakan SwiftUI saya mendapatkan crash hanya jika bagian bawah daftar saya melampaui layar dan kemudian saya mencoba untuk "memindahkan" item daftar. Apa yang akhirnya saya temukan adalah bahwa jika saya memiliki terlalu banyak "barang" di bawah Daftar () maka crash di pindahkan. Misalnya, di bawah Daftar saya () saya punya Teks (), Spacer (), Tombol (), Spacer () Tombol (). Jika saya berkomentar SATU dari benda-benda itu maka tiba-tiba saya tidak dapat membuat ulang kecelakaan. Saya tidak yakin apa batasannya, tetapi jika Anda mendapatkan crash ini maka cobalah menghapus objek di bawah daftar Anda untuk melihat apakah itu membantu.

Dave Levy
sumber
0

Meskipun saya tidak dapat melihat crash, kode Anda memiliki beberapa masalah:

dengan mengatur item utama, Anda benar-benar membunuh perilaku default transisi navigasi. (coba geser dari sisi depan untuk melihat apakah itu berfungsi).

Jadi tidak perlu ada tombol di sana. Biarkan saja apa adanya dan Anda memiliki tombol kembali yang gratis.

Dan jangan lupa menurut HIG , judul tombol kembali harus menunjukkan ke mana ia pergi, bukan apa itu! Jadi cobalah untuk mengatur judul untuk halaman pertama untuk menunjukkannya salah satu tombol kembali yang muncul untuk itu.

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()
    }
}
Mojtaba Hosseini
sumber
1
Hei, terima kasih atas jawabannya. Sementara saya setuju bahwa meninggalkan perilaku tombol kembali default diinginkan, itu masih menghasilkan crash.
Robert
Versi apa yang Anda gunakan? Saya sudah mengujinya sebelum mengirim. Mungkin Anda memiliki masalah lain. Bisakah Anda memberikan contoh proyek?
Mojtaba Hosseini
1
Xcode 11.2 beta seperti kata pertanyaan. Contoh yang saya berikan dalam pertanyaan adalah yang Anda butuhkan untuk mereproduksi crash.
Robert
Saya menggunakan versi yang sama dan kode yang sama tetapi tidak ada crash 🤔
Mojtaba Hosseini
1
Konfirmasikan ini macet di Catalina (10.15.1), Xcode (11.2.1), iOS (13.2.2)
P. Ent
0

FWIW - Solusi di atas menyarankan hack NavigationLink yang tersembunyi masih merupakan solusi terbaik di iOS 13.3b3. Saya juga telah mengajukan FB7386339 untuk anak cucu, dan ditutup serupa dengan FB yang disebutkan di atas: "Perbaikan potensial yang diidentifikasi - Untuk pembaruan OS di masa mendatang".

Fingers Crossed.

Mike W.
sumber
Harap hindari menambahkan komentar sebagai jawaban.
Karthick Ramesh
0

Itu diselesaikan di iOS 13.3. Cukup perbarui OS dan xCode Anda.

JUMAT
sumber
1
Xcode 11.3 (11C29) pada 10.15.2 menghasilkan perilaku yang berbeda untuk saya: Navigasi mundur berfungsi, tetapi setelah itu NavigationLink tidak lagi berfungsi. Mengkliknya tidak menghasilkan apa-apa.
malte
@malte Lebih baik membuka pertanyaan baru untuk itu. Sebelum saya memeriksa kode Anda, berikan .buttonStyle(PlainButtonStyle())pengubah NavigationLink Anda dan coba lagi. beri tahu saya jika Anda mengajukan pertanyaan.
JUMAT,
1
Kamu benar. Ternyata sudah ada pertanyaan baru: stackoverflow.com/questions/59279176/…
malte