I have this issue where the navigation link pops back when a variable is updated. It's a bit confusing because this behavior doesn't occur elsewhere in the application, but I am doing the same thing.
To start, there is a Vehicle struct that conforms to Identifiable. In content view, it's displayed in a NavigationView in a scrollview.
ScrollView(showsIndicators: false) {
VStack {
ForEach(user.vehicles) { vehicle in
VehicleListItem(user: $user,
vehicle: vehicle)
}
}
}
That list item has a navigation link that takes the user to a different view. Vehicle is passed as a binding to the next view.
NavigationLink(destination: {
VehicleView(user: $user,
vehicle: $user.vehicles[user.vehicles.firstIndex(where: {$0.id == vehicle.id})!],
make: vehicle.make,
model: vehicle.model,
year: vehicle.year
)
}, label: {
Image(systemName: "car")
.font(.system(size: 100))
}).padding(.bottom, 20)
In Vehicle View, the user can edit the vehicles information (make, model, year) and it works fine. It doesn't pop back to the previous view when the user makes a change to the Vehicle.
Section() {
VehicleHandler(make: $make, model: $model, year: $year)
Button(action: {
//Update
vehicle.make = make
vehicle.model = model
vehicle.year = year
}, label: {
ButtonView(label: "Update")
}).disabled(make.isEmpty ||
model.isEmpty ||
(make == vehicle.make && model == vehicle.model && year == vehicle.year))
}
Vehicle Handler
struct VehicleHandler: View {
enum Field: Hashable {
case make
case model
}
@FocusState private var field: Field?
@Binding var make: String
@Binding var model: String
@Binding var year: Int //Set to current year
var body: some View {
TextField("Make", text: $make)
.submitLabel(.next)
.focused($field, equals: .make)
.onSubmit {
field = .model
}
TextField("Model", text: $model)
.submitLabel(.done)
.focused($field, equals: .model)
Picker("Year", selection: $year, content: {
ForEach((1900...Date().year()).reversed(), id: \.self) { year in
Text(String(year))
.tag(year as Int)
}
})
}
}
Now, the user can add maintenance records. Once they've added a record, it's displayed using a ForEach. The records also conform to identifiable.
//Only show 5
ForEach(vehicle.maintenanceRecords.prefix(5)) { record in
NavigationLink(destination: {
MaintenanceView(user: $user,
record: $vehicle.maintenanceRecords[vehicle.getMaintenanceIndex(id: record.id)],
date: record.date,
mileage: record.mileage ?? 0,
note: record.note,
cost: record.cost ?? 0,
tasks: record.tasks)
}, label: {
Text("\(record.date.formattedNoYear()) - \(record.note)")
})
}
The same concept is being used to edit the maintenance record. Record is passed as a binding, and the record information is passed to update the various fields. When the user presses update, it updates the record. It's at this point where it will pop back to VehicleView anytime you make an edit and press Update. I used to have this issue when I didn't use IDs, however, every struct conforms to Identifiable and the IDs aren't being modified. I've checked to ensure they stay the same, and that includes the IDs for the Vehicle, and Record. Does anyone have any idea why it keeps popping back here, but it doesn't when the Vehicle information is updated? Let me know if you need more information.
RecordHandler(user: $user,
date: $date,
mileage: $mileage,
note: $note,
cost: $cost,
task: $task,
tasks: $tasks)
Section() {
Button(action: {
//Update record
record.date = date
record.note = note
record.tasks = tasks
if mileage.isZero == false {
record.mileage = mileage
}
if cost.isZero == false {
record.cost = cost
}
//Clear task
task = ""
}, label: {
ButtonView(label: "Update")
}).disabled(note.isEmpty ||
(date == record.date && mileage == record.mileage && note == record.note && cost == record.cost && tasks == record.tasks)
)
}
Record Handler
struct RecordHandler: View {
enum Field: Hashable {
case note
case mileage
case cost
case task
}
@FocusState var field: Field?
@Binding var user: User
@Binding var date: Date
@Binding var mileage: Float
@Binding var note: String
@Binding var cost: Float
@Binding var task: String
@Binding var tasks: [Vehicle.Record.Task]
@State var suggestion: String = ""
var body: some View {
Section() {
DatePicker("Date", selection: $date)
TextField("Note", text: $note)
.submitLabel(.next)
.focused($field, equals: .note)
.onSubmit {
field = .mileage
}
FieldWithLabel(label: "Mileage", value: $mileage, formatter: NumberFormatter.number)
.submitLabel(.next)
.focused($field, equals: .mileage)
.onSubmit {
field = .cost
}
FieldWithLabel(label: "Cost", value: $cost, formatter: NumberFormatter.currency)
.submitLabel(.next)
.focused($field, equals: .cost)
.onSubmit {
field = .task
}
}
//For task
Section() {
HStack {
TextField("Task", text: $task)
.submitLabel(.done)
.focused($field, equals: .task)
.onSubmit {
addTask()
field = .task
}
.toolbar(content: {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Close") {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
})
.onChange(of: task, perform: { value in
//See if any of the saved task are similar
if user.tasks.contains(where: {$0.contains(value)}) {
//Set suggestion
suggestion = user.tasks.first(where: {$0.contains(value)})!
} else {
//Leave it empty if there's nothing
suggestion = ""
}
})
Spacer()
Button(action: {
addTask()
}, label: {
Image(systemName: "plus.circle.fill")
.font(.system(size: 22))
}).buttonStyle(BorderlessButtonStyle())
}
//To show task suggestions
if suggestion.isEmpty == false {
Button(action: {
//Set task to suggestion
task = suggestion
//Add task
addTask()
//Focus field
field = .task
}, label: {
HStack {
Text("Suggestion")
Spacer()
Text(suggestion)
}
})
}
ForEach(tasks) { task in
Text(task.name)
}
.onDelete(perform: delete)
}
}
func addTask() {
//Create
let task = Vehicle.Record.Task(name: task)
//Add to array
tasks.append(task)
//Clear
self.task = ""
}
func delete(at offsets: IndexSet) {
tasks.remove(atOffsets: offsets)
}
}