I have the following code below and am not sure why the listview is not refreshing on clicking save and dismissing the modal popup.
Content View has the following code which calls NewFlightView modally.
Button {
//Show Modal New Flight Window
isPresented = true
} label: {
ZStack {
Circle ()
.foregroundColor(.white)
.frame(width: 50, height: 50)
.shadow(radius: 4)
Image(systemName: "plus.circle.fill")
.resizable()
.foregroundColor(.cyan)
.frame(width: 45, height: 45)
}
.offset(y: -15)
}
.buttonStyle(TabButtonStyle())
.sheet(isPresented: $isPresented, onDismiss: {
flightListVM.getAllFlights()
}, content: {
NewFlightView()
})
.onAppear(perform: {
flightListVM.getAllFlights()
})
The NewFlightView is a simple input view as such
struct NewFlightView: View {
@StateObject private var newFlightVM = NewFlightViewModel()
@State private var isPresented: Bool = false
@Environment(\.presentationMode) var presetationMode
var body: some View {
Form {
DatePicker("Flight Date", selection: $newFlightVM.date)
Spacer()
TextField("Enter origin airport", text: $newFlightVM.origin)
.textFieldStyle(.roundedBorder)
TextField("Enter destination airport", text: $newFlightVM.destination)
.textFieldStyle(.roundedBorder)
TextField("Enter altitude", value: $newFlightVM.altitude, format: .number)
.textFieldStyle(.roundedBorder)
.padding()
TextField("Enter flight duration", value: $newFlightVM.duration, format: .number)
.textFieldStyle(.roundedBorder)
.padding()
Button ("Save / Get Dose") {
newFlightVM.save()
presetationMode.wrappedValue.dismiss()
}
Spacer()
}
.navigationTitle("New Flight")
}
}
Below is the ListView that displays the flights after they are added to CoreData.
struct FlightsListView: View {
@StateObject private var flightListVM = FlightListViewModel()
@State private var isPresented: Bool = false
@AppStorage("isDarkMode") private var isDarkMode: Bool = false
@State private var isShowingSettings: Bool = false
@State private var isShowingAddNewFlight: Bool = false
@State private var animatingButton: Bool = false
@State var activeSheet: ActiveSheet?
// @ObservedObject private var appSetting = AppSetting.shared
private func deleteFlight(at indexSet: IndexSet) {
indexSet.forEach { index in
let flight = flightListVM.flights[index]
//delete the flight
flightListVM.deleteFlight(flight: flight)
//get all flights
flightListVM.getAllFlights()
}
}
var body: some View {
//Displays all flights in the database
List {
ForEach(flightListVM.flights, id: \.id) { flight in
NavigationLink(destination: FlightDetailView(flight: flight )) {
FlightCell(flight: flight)
} //: Link
}.onDelete(perform: deleteFlight)
}
.listStyle(PlainListStyle())
.navigationTitle("Flights")
.sheet(isPresented: $isPresented, onDismiss: {
flightListVM.getAllFlights()
}, content: {
NewFlightView()
})
.onAppear {
flightListVM.getAllFlights()
}
}
}
And finally the ViewModels that I am using to control the MVVM side.
NewFlightViewModel
class NewFlightViewModel: ObservableObject {
var date: Date = Date()
var origin: String = ""
var destination: String = ""
var altitude: Double = 0.0
var duration: Double = 0.0
func save() {
let manager = CoreDataManager.shared
let flight = Flight(context: manager.persistentContainer.viewContext)
flight.date = date
flight.origin = origin
flight.destination = destination
flight.altitude = altitude
flight.duration = duration
manager.save()
}
}
FlightListViewModel
class FlightListViewModel: ObservableObject {
@Published var flights = [FlightViewModel]()
func deleteFlight(flight: FlightViewModel) {
let flight = CoreDataManager.shared.getFlightById(id: flight.id)
if let flight = flight {
CoreDataManager.shared.deleteFlight(flight)
}
}
func getAllFlights () {
let flights = CoreDataManager.shared.getAllFlights()
DispatchQueue.main.async {
self.flights = flights.map(FlightViewModel.init)
}
}
}
struct FlightViewModel {
let flight: Flight
var id: NSManagedObjectID {
return flight.objectID
}
var date: String? {
return flight.date?.asFormattedString()
}
var origin: String {
return flight.origin ?? ""
}
var destination: String {
return flight.destination ?? ""
}
var altitude: Double? {
return Double(flight.altitude)
}
var duration: Double? {
return Double(flight.duration)
}
}
Okay. I need some guidance as to why when dismissing the model NewFlightView and it triggers the onDismiss handle of the ContentView. How can I refresh the FlightListView without having to click on another tab and then back to the Flights tab thus triggering its onAppear event?
Remove the view model object (we don't use MVVM in SwiftUI) and use @FetchRequest
. That will call body
automatically when the results change.
Move your helpers to an extension of NSManagedObjectContext
and use it from the View
struct via @Environment(\.managedObjectContext) var viewContext
.
Looks to me like you are missing your NSManagedObject
file for Flight. Generate it in the model editor choosing Editor->Create NSManagedObject subclass. Choose Codegen Category/Extension. Put your computed properties in there, but don't do any formatting that must go in body otherwise SwiftUI won't be able to update UILabels when region settings change.