Perché la mia app SwiftUI si arresta in modo anomalo durante la navigazione all'indietro dopo aver posizionato un `NavigationLink` all'interno di un 'navigationBarItems` in un' NavigationView`?


47

Esempio riproducibile minimo (Xcode 11.2 beta, funziona in 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()
    }
}

Il problema sembra risiedere nel posizionare il mio NavigationLinkinterno di un navigationBarItemsmodificatore nidificato all'interno di una vista SwiftUI la cui vista radice è a NavigationView. Il rapporto sugli arresti anomali indica che sto tentando di visualizzare un controller di visualizzazione che non esiste quando navigo avanti Childe poi di nuovo Parent.

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

Se dovessi invece posizionarlo NavigationLinknel corpo della vista come il seguente, funziona perfettamente.

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

È un bug di SwiftUI o un comportamento previsto?

EDIT: ho aperto un problema con Apple nel loro assistente di feedback con l'ID FB7423964nel caso in cui qualcuno di Apple si preoccupi di pesare :).

EDIT: il mio ticket aperto nell'assistente di feedback indica che ci sono più di 10 problemi simili segnalati. Hanno aggiornato la risoluzione con Resolution: Potential fix identified - For a future OS update. Le dita incrociarono che la correzione atterra presto.

EDIT: questo è stato risolto in iOS 13.3!


L'esempio fornito sopra funziona perfettamente con Xcode 11.2 beta. Ci manca qualcosa qui?
Subramanian Mariappan,

@SubramanianMariappan Funziona bene anche per me sull'11.2 beta.
Farhan Amjad,

1
Interessante, si blocca ogni volta. Ho anche provato a creare un nuovo progetto e a copiare quel codice esatto al posto di ContentView.swift. Apporterò una modifica al post, ma l'incidente si verifica solo quando navighi avanti e poi indietro.
Robert,

Ottima domanda! Il tuo esempio qui si blocca anche per me ogni volta. Ho appena pubblicato una nuova risposta che funziona molto bene per me. Fammi sapere se funziona anche per te. Grazie.
Chuck H,

1
Grazie per gli aggiornamenti sui biglietti Apple!
malte,

Risposte:


20

Questo è stato un punto doloroso per me! L'ho lasciato fino a quando la maggior parte della mia app non è stata completata e ho avuto lo spazio mentale per affrontare l'incidente.

Penso che possiamo essere tutti d'accordo sul fatto che ci sono cose davvero fantastiche con SwifUI ma che il debug può essere difficile.

Secondo me, direi che questo è un ERRORE. Ecco la mia logica:

  • Se si avvolge la chiamata di eliminazione di PresentationMode in un ritardo asincrono di circa mezzo secondo, è necessario rilevare che il programma non si arresterà più in modo anomalo.

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
        self.presentationMode.wrappedValue.dismiss()
    } 
  • Questo mi suggerisce che il bug è un comportamento imprevisto in profondità nel modo in cui SwiftUI si interfaccia con tutto l'altro codice UIKit per gestire le varie viste. A seconda del codice effettivo, potresti scoprire che se la vista presenta una complessità minore, l'arresto anomalo non si verificherà effettivamente. Ad esempio, se si sta chiudendo da una vista a una con un elenco e tale elenco è vuoto, si verificherà un arresto anomalo senza il ritardo asincrono. D'altra parte, se hai anche solo una voce in quella vista elenco, forzando un'iterazione di ciclo per generare la vista padre, vedrai che l'arresto anomalo non si verificherà.

Non sono così sicuro di quanto sia solida la mia soluzione per racchiudere in ritardo la chiamata di rifiuto. Devo testarlo molto di più. Se avete idee su questo, per favore fatemi sapere! Sarei molto felice di imparare da te!


1
Molto intelligente! Non ci avevo pensato. Spero che venga risolto presto!
Robert,

1
@Robert Hai risolto il tuo problema? Questo è difficile poiché un problema non correlato che ho riscontrato è l'utilizzo di un selettore nelle visualizzazioni di navigazione figlio. Mentre uno stile di selezione segmentato funziona, l'impostazione predefinita sembra causare un arresto nello stesso punto, quando si fa clic sul pulsante Indietro. Possiamo discutere ulteriormente se ti sta ancora dando dolore. PS. Odio la mia soluzione. È un trucco ma non dovrebbe richiedere l'aggiornamento del codice se Apple risolve il problema di temporizzazione.
Justin Ngan,

2
Concordo sul fatto che l'aspetto del tempismo, insieme al fatto che ha funzionato bene in 11.1 e funziona al di fuori dei .navigationBarItems()punti in quanto questo è un bug.
John M.,

3
Sì, credo che sia un bug e questo è il mio attuale candidato principale per il premio Bounty. Dal momento che mi restano 4 giorni per la taglia al momento della stesura di questo articolo, sto solo tenendo duro nel caso in cui qualcuno arrivi con nuove informazioni :).
Robert,

1
Questo è stato un suggerimento molto interessante, grazie per quello! Sfortunatamente sto ancora bloccando in modo affidabile l'app nel simulatore il 100% delle volte: / Funziona meglio sul dispositivo, ma non è affatto senza crash. Ma è stato anche così senza indugio.
Kilian,

15

Anche questo mi ha frustrato per un bel po 'di tempo. Negli ultimi mesi, a seconda della versione di Xcode, della versione del simulatore e del tipo di dispositivo reale e / o versione, è passato dal funzionare al non funzionare nuovamente, apparentemente a caso. Tuttavia, recentemente ha fallito costantemente per me, quindi ieri mi sono tuffato in profondità. Attualmente sto usando Xcode versione 11.2.1 (11B500).

Sembra che il problema riguardi la barra di navigazione e il modo in cui i pulsanti sono stati aggiunti ad essa. Quindi, invece di utilizzare un NavigationLink () per il pulsante stesso, ho provato a utilizzare un Button standard () con un'azione che imposta una var @State che attiva un NavigationLink nascosto. Ecco un sostituto per la vista principale di Robert:

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

Per me, questo funziona in modo molto coerente su tutti i simulatori e tutti i dispositivi reali.

Ecco le mie visualizzazioni di aiuto:

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

Ecco un esempio dell'uso:

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

Posso confermare che funziona (davvero bene per un hack ;-))! Tuttavia, Apple deve risolvere questo problema al più presto. Xcode 11.2.1, Catalina 10.15.2 (beta), iOS 13.2.2
P. Ent

1
Sono d'accordo al 100%. In generale, per quanto riguarda la navigazione in SwiftUI, c'è molto che è rotto o semplicemente mancante. Il che ovviamente ci porta al vero problema. Non esiste una "fonte di verità" (ovvero documentazione ed esempi) di Apple, solo hack come noi. A proposito, uso così tanto la tecnica sopra, ho creato due viste di utilità che aiutano molto con la leggibilità. Li aggiungerò alla mia risposta nel caso qualcuno fosse interessato.
Chuck H,

Grazie per la soluzione alternativa, funziona e basta!
Stanislav Poslavsky,

1
Questo non funziona per me per più di una navigazione. Una volta tornato alla schermata precedente, il collegamento invisibile non funziona più.
Jon Shier,

1
Ho diversi dispositivi reali a 13.3 (build 17C54) e funzionano tutti come desiderato. Poiché eseguo quasi tutti i miei test su dispositivi reali, non utilizzo molto spesso il simulatore. Ma ho appena provato il mio caso di test su un simulatore 13.3 e il test non ha esito positivo. Ho notato che iOS 13.3 sul simulatore Xcode è una build precedente (17C45) rispetto all'aggiornamento pubblico. Sarei interessato a sapere se qualcuno osserva il comportamento difettoso su un dispositivo reale.
Chuck H,

12

Questo è un grosso bug e non riesco a vedere un modo corretto di aggirare il problema. Ha funzionato bene in iOS 13 / 13.1 ma 13.2 si arresta in modo anomalo.

Puoi effettivamente replicarlo in un modo molto più semplice (questo codice è letteralmente tutto ciò di cui hai bisogno).

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

Spero che Apple lo risolva perché sicuramente spezzerà un sacco di app SwiftUI (inclusa la mia).


Haha ... È fantastico. Sei passato a una vista di testo che in SwiftUI è una vista! Sì, dovrebbe tornare al suo genitore, no? Eppure no. È interessante notare che il comportamento del tuo esempio interrompe l'interfaccia utente ma in realtà non causa un arresto irreversibile.
Justin Ngan,

Sì, la componibilità di SwiftUI (e React Native / Flutter ecc.) Sono incredibili. Ti dà così tanto controllo / flessibilità (quando almeno funziona).
James,

1
Conferma che si arresta in modo anomalo su Catalina (10.15.1), Xcode (11.2.1), iOS (13.2.2)
P. Ent,

Non si blocca più in 13.3, tuttavia la navigazione sembra funzionare solo la prima volta che lo attivi 🤦‍♂️
James

6

Come soluzione alternativa, sulla base della risposta di Chuck H sopra, ho incapsulato NavigationLink come elemento nascosto:

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

Quindi puoi usarlo in un NavigationView (che è cruciale) e attivarlo da un pulsante in una barra di navigazione:

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

Inserisci questo nei commenti "// HACK", quindi quando Apple risolve questo problema puoi sostituirlo.


Questo sembra funzionare solo al primo utilizzo in iOS 13.3.
James,

3

Sulla base delle informazioni che avete fornito e specialmente di un commento che @Robert ha fatto su dove si trova NavigationView, ho trovato un modo per aggirare il problema almeno sul mio scenario specifico.

Nel mio caso avevo un TabView che era racchiuso in un NavigationView in questo modo:

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

Questo codice si blocca quando tutti segnalano in iOS 13.2 e funzionano in iOS 13.1. Dopo alcune ricerche ho scoperto una soluzione a questa situazione.

Fondamentalmente, sto spostando NavigationView su ciascuna schermata separatamente su ogni scheda in questo modo:

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

In qualche modo va contro la premessa di semplicità di SwiftUI ma funziona su iOS 13.2.


funziona ma il problema è rimuovere tabView su NewView.
VENERDI

1
@FRIDDAY questo esempio funziona in 13.1 ma si blocca in 13.2. È un bug noto e la mia intenzione era quella di cercare di aiutare qualcuno nello stesso scenario con una soluzione alternativa
Julio Bailon,

1

Xcode 11.2.1 Swift 5

FATTO! Mi ci sono voluti un paio di giorni per capire questo ...

Nel mio caso quando utilizzo SwiftUI ottengo un arresto anomalo solo se la parte inferiore dell'elenco si estende oltre lo schermo e quindi provo a "spostare" qualsiasi elemento dell'elenco. Quello che ho scoperto è che se ho troppe "cose" sotto la Lista (), allora si blocca in movimento. Ad esempio, sotto la mia lista () avevo un pulsante di testo (), spaziatore (), pulsante (), spaziatore () (). Se avessi commentato UNO di quegli oggetti, all'improvviso non avrei potuto ricreare l'incidente. Non sono sicuro di quali siano le limitazioni, ma se si verifica questo arresto, provare a rimuovere gli oggetti sotto l'elenco per vedere se aiuta.


0

Anche se non riesco a vedere alcun crash, il tuo codice ha alcuni problemi:

impostando l'elemento principale, si elimina effettivamente il comportamento predefinito delle transizioni di navigazione. (prova a scorrere dal lato principale per vedere se funziona).

Quindi non c'è bisogno di avere un pulsante lì. Lascialo così com'è e hai un pulsante Indietro gratuito.

E non dimenticare secondo HIG , il titolo del pulsante Indietro dovrebbe mostrare dove va, non di cosa si tratta! Quindi prova a impostare un titolo per la prima pagina per mostrarlo uno qualsiasi pulsante indietro che si apre ad esso.

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
Ehi, grazie per la risposta. Mentre sono d'accordo che è auspicabile lasciare il comportamento del pulsante Indietro predefinito, produce comunque un arresto anomalo.
Robert,

Quale versione stai usando? L'ho provato prima di inviarlo. Forse hai un altro problema. Potete fornire un progetto di esempio per favore?
Mojtaba Hosseini,

1
Xcode 11.2 beta come dice la domanda. L'esempio che ho fornito nella domanda è tutto ciò che serve per riprodurre il crash.
Robert,

Sto usando la stessa versione e lo stesso codice ma nessun arresto anomalo 🤔
Mojtaba Hosseini,

1
Conferma che si arresta in modo anomalo su Catalina (10.15.1), Xcode (11.2.1), iOS (13.2.2)
P. Ent,

0

FWIW - Le soluzioni sopra suggerite che suggeriscono un NavigationLink Hack nascosto sono ancora la soluzione alternativa migliore in iOS 13.3b3. Ho anche presentato un FB7386339 per amor dei posteri, e sono stato chiuso in modo simile ad altri FB di cui sopra: "Potenziale correzione identificata - Per un futuro aggiornamento del sistema operativo".

Dita incrociate.


Si prega di evitare di aggiungere commenti come risposte.
Karthick Ramesh il

0

È risolto in iOS 13.3. Aggiorna semplicemente il tuo sistema operativo e xCode.


1
Xcode 11.3 (11C29) su 10.15.2 comporta un comportamento diverso per me: la navigazione all'indietro funziona, ma in seguito NavigationLink non ha più alcuna funzione. Cliccandolo non fa nulla.
malte,

@malte È meglio aprire una nuova domanda per questo. Prima di controllare il tuo codice, dai il .buttonStyle(PlainButtonStyle())modificatore NavigationLink e riprovo. fammi sapere se hai fatto una domanda.
VENERDI '23

1
Hai ragione. Si scopre che c'è già una nuova domanda: stackoverflow.com/questions/59279176/…
malte,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.