SwiftUI segurando a referência ao objeto de dados principais excluído, causando falha

8

Estou achando impossível usar dados principais com o SwiftUI, porque, como passo os dados principais para uma variável de objeto observada, a visualização do link de navegação manterá uma referência ao objeto mesmo depois que a visualização desaparecer, assim que eu excluir o objeto do contexto, o aplicativo falha, sem mensagens de erro.

Confirmei isso envolvendo a variável do objeto de dados do núcleo em um modelo de exibição como opcional e, em seguida, defina o objeto nulo logo após a ação de exclusão de contexto e o aplicativo funcione bem, mas isso não é uma solução, pois preciso do objeto de dados do núcleo vincular-se às visões rápidas da interface do usuário e ser a fonte da verdade. Como isso deve funcionar? Eu realmente não posso fazer nada remotamente complexo com o SwiftUI, ao que parece.

Tentei atribuir o objeto de dados principais passados ​​a um @State opcional, mas isso não funciona. Não consigo usar o @Binding porque é um objeto buscado. E não posso usar uma variável, pois os controles swiftui exigem ligações. Só faz sentido usar um @ObservedObject, mas isso não pode ser opcional, o que significa que, quando o objeto atribuído a ele é excluído, o aplicativo falha, porque não consigo defini-lo como nulo.

Aqui está o objeto de dados principal, que é um objeto observável por padrão:

class Entry: NSManagedObject, Identifiable {

    @NSManaged public var date: Date
}

Aqui está uma visão que passa um objeto de entrada de dados principal para outra visão.

struct JournalView: View {

    @Environment(\.managedObjectContext) private var context

    @FetchRequest(
        entity: Entry.entity(),
        sortDescriptors: [],
        predicate: nil,
        animation: .default
    ) var entries: FetchedResults<Entry>

    var body: some View {
        NavigationView {
            List {
                ForEach(entries.indices) { index in
                    NavigationLink(destination: EntryView(entry: self.entries[index])) {
                        Text("Entry")
                    }
                }.onDelete { indexSet in
                    for index in indexSet {
                        self.context.delete(self.entries[index])
                    }
                }
            }
        }
    }
}

Agora, aqui está a visualização que acessa todos os atributos do objeto principal de entrada de dados que foi passado. Depois que eu excluo esta entrada, de qualquer visualização, a propósito, ela ainda é referenciada aqui e faz com que o aplicativo falhe imediatamente. Acredito que isso também tenha algo a ver com o link de navegação que inicializa todas as visualizações de destino antes mesmo de serem acessadas. O que não faz sentido por que isso faria isso. Isso é um bug ou existe uma maneira melhor de conseguir isso?

Eu até tentei fazer a exclusão onDisappear sem sucesso. Mesmo se eu excluir o JournalView, ele continuará travando, pois o NavigationLink ainda fará referência ao objeto. Interessante não travará se excluir um NavigationLink que ainda não foi clicado.

struct EntryView: View {

    @Environment(\.managedObjectContext) private var context
    @Environment(\.presentationMode) private var presentationMode

    @ObservedObject var entry: Entry

    var body: some View {
        Form {

            DatePicker(selection: $entry.date) {
                Text("Date")
            }

            Button(action: {
                self.context.delete(self.entry)
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Delete")
            }
        }
    }
}

ATUALIZAR

A falha está me levando ao primeiro uso da entrada no EntryView e lê o Thread 1: EXC_BAD_INSTRUCTION (código = EXC_I386_INVOP, subcode = 0x0) .. essa é a única mensagem emitida.

A única solução possível é adicionar uma propriedade ao objeto de dados principal "isDeleted" e defini-la como true em vez de tentar excluir do contexto. Então, quando o aplicativo é encerrado, ou ao iniciar, posso limpar e excluir todas as entradas excluídas? Não é o ideal e prefere descobrir o que está errado aqui, pois parece que não estou fazendo nada diferente do exemplo do MasterDetailApp, que parece funcionar.

SybrSyn
fonte
que aborrecimento! qualquer atualização neste @SybrSyn ?!
Fattie 17/03

Respostas:

4

Eu basicamente tive o mesmo problema. Parece que o SwiftUI carrega todas as visualizações imediatamente, portanto, a visualização foi carregada com as propriedades do objeto CoreData existente. Se você excluí-lo na Visualização onde alguns dados são acessados ​​via @ObservedObject, eles travarão.

Minha solução alternativa:

  1. A ação Excluir - adiada, mas finalizada pela Central de notificações
    Button(action: {
      //Send Message that the Item  should be deleted
       NotificationCenter.default.post(name: .didSelectDeleteDItem, object: nil)

       //Navigate to a view where the CoreDate Object isn't made available via a property wrapper
        self.presentationMode.wrappedValue.dismiss()
      })
      {Text("Delete Item")}

Você precisa definir um Notification.name, como:

extension Notification.Name {

    static var didSelectDeleteItem: Notification.Name {
        return Notification.Name("Delete Item")
    }
}
  1. Na exibição apropriada, procure a mensagem Excluir

// Receive Message that the Disease should be deleted
    .onReceive(NotificationCenter.default.publisher(for: .didSelectDeleteDisease)) {_ in

        //1: Dismiss the View (IF It also contains Data from the Item!!)
        self.presentationMode.wrappedValue.dismiss()

        //2: Start deleting Disease - AFTER view has been dismissed
        DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(1)) {self.dataStorage.deleteDisease(id: self.diseaseDetail.id)}
    }
  1. Esteja seguro em suas Views, onde alguns elementos CoreData são acessados ​​- Verifique se há isFault!

    VStack{
         //Important: Only display text if the disease item is available!!!!
           if !diseaseDetail.isFault {
                  Text (self.diseaseDetail.text)
            } else { EmptyView() }
    }

Um pouco hacky, mas isso funciona para mim.

sTOOs
fonte
Incrível, obrigado por esta solução. Vou ter que tentar com isso. A solução alternativa que encontrei nesse meio tempo foi adicionar um atributo à entidade chamada "inTrash" e defini-lo como verdadeiro ao excluir, filtrar o lixo nas solicitações de busca e limpar todo o lixo no lançamento, não ideal, mas isso é trabalhando para mim também.
SybrSyn 6/12/19
0

Encontrei o mesmo problema e realmente não encontrei uma solução para o problema raiz. Mas agora "protejo" a exibição que usa os dados referenciados como este:

var body: some View {
    if (clip.isFault) {
        return AnyView(EmptyView())
    } else {
        return AnyView(actualClipView)
    }
}

var actualClipView: some View {
    // …the actual view code accessing various fields in clip
}

Isso também parece hacky, mas funciona bem por enquanto. É menos complexo do que usar uma notificação para "adiar" a exclusão, mas ainda assim graças aos sTOOs, responda a dica com .isFault!

Benjamin Graf
fonte