1

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)
                }
            }
        }
    }
}
Nizami
  • 728
  • 1
  • 6
  • 24
  • 1
    From the first glance, the $ should be used with the published property, not the model. I wouldn’t assign nil, might create an infinite loop. – cora Nov 23 '22 at 13:56
  • 1
    Probably better to have a @State property in the view for the selected transaction and use that instead in the `.sheet` call – Joakim Danielson Nov 23 '22 at 14:05
  • That Binding in the body seems a bit of a stretch for SwiftUI s capabilities what is the point of using Binding there? There is no need for a 2 way connection – lorem ipsum Nov 23 '22 at 15:36
  • @loremipsum in this scenario Binding + Map is using for safe unwrap variable without losing functionality – Nizami Nov 24 '22 at 05:12

1 Answers1

2

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