Search code examples
swiftswiftuibindingpublisher

SwiftUI: @Published for trigger .sheet is crashing when try to save changed value


I wanna make some changes in ViewModel by passing items into the .sheet and decide to save it or leave without changes. But faced the problem when changing the item and dismissing .sheet with @Environment(\.dismiss) the app is crushing. I can't figure out why it gives this behavior. My goal is make changes into the item on a .sheet and control save it or not by pressing button. Would be thankful for help!

struct Transport: Identifiable {
    var id = UUID().uuidString
    var name: String
}

class TransportViewModel: ObservableObject {
    @Published var transports = [Transport(name: "Airplane"), Transport(name: "Car"), Transport(name: "Ship")]
    @Published var editedTransport: Transport?
    
    func saveTransport() {
        if let unwrappedTransport = editedTransport {
            if let editedTransportIndex = transports.firstIndex(where: { $0.id == unwrappedTransport.id }) {
                transports[editedTransportIndex] = unwrappedTransport
            }
        }
    }
}

struct TransportListView: View {
    
    @StateObject var transportViewModel = TransportViewModel()
    
    var body: some View {
        List {
            ForEach(transportViewModel.transports) { transport in
                Text(transport.name)
                    .onTapGesture {
                        transportViewModel.editedTransport = transport
                    }
            }
        }
        .sheet(item: $transportViewModel.editedTransport) {
            transportViewModel.editedTransport = nil
        } content: { _ in
            TransportEditSheet()
        }
        .environmentObject(transportViewModel)
    }
}

struct TransportEditSheet: View {
    
    @EnvironmentObject var transportViewModel: TransportViewModel
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        Binding($transportViewModel.editedTransport).map { transport in
            List {
                TextField("", text: transport.name)
                Button(action: { dismiss() }) {
                    Text("Cancel").foregroundColor(.red)
                }
                Button(action: {
                    transportViewModel.saveTransport()
                    dismiss() }) {
                    Text("Save").foregroundColor(.green)
                }
            }
        }
    }
}

Solution

  • Take a look at this, seems to be working as expected

        struct Transport: Identifiable {
            var id = UUID().uuidString
            var name: String
        }
    
        class TransportViewModel: ObservableObject {
            @Published var transports = [Transport(name: "Airplane"), Transport(name: "Car"), Transport(name: "Ship")]
            @Published var editedTransport: Transport?
            
            func saveTransport() {
                
                DispatchQueue.main.async {
                    
                    if let unwrappedTransport = self.editedTransport {
                        if let editedTransportIndex = self.transports.firstIndex(where: { $0.id == unwrappedTransport.id }) {
                            self.transports[editedTransportIndex] = unwrappedTransport
                        }
                    }
                }
            }
        }
    
        struct TransportListView: View {
            
            @StateObject var transportViewModel = TransportViewModel()
            @State var editSheet = false
            
            var body: some View {
                List {
                    ForEach(transportViewModel.transports) { transport in
                        Text(transport.name)
                            .onTapGesture {
                                transportViewModel.editedTransport = transport
                                editSheet = true
                            }
                    }
                }
                .sheet(isPresented: $editSheet) {
                   TransportEditSheet()
                }
                .environmentObject(transportViewModel)
            }
        }
    
        struct TransportEditSheet: View {
            
            @EnvironmentObject var transportViewModel: TransportViewModel
            @Environment(\.dismiss) var dismiss
            
            var body: some View {
                Binding($transportViewModel.editedTransport).map { transport in
                    List {
                        TextField("", text: transport.name)
                        Button(action: { dismiss() }) {
                            Text("Cancel").foregroundColor(.red)
                        }
                        Button(action: {
                            transportViewModel.saveTransport()
                            dismiss() }) {
                            Text("Save").foregroundColor(.green)
                        }
                    }
                }
            }
        }
    

    enter image description here