I'm searching for a way to simplify/refactor the addition of .onChange(of:) in a SwiftUI view that has MANY TextFields. If a solution were concise, I would also move the modifier closer to the appropriate field rather than at the end of, say, a ScrollView. In this case, all of the .onChange modifiers call the same function.
Example:
.onChange(of: patientDetailVM.pubFirstName) { x in
changeBackButton()
}
.onChange(of: patientDetailVM.pubLastName) { x in
changeBackButton()
}
// ten more times for other fields
I tried "oring" the fields. This does not work:
.onChange(of:
patientDetailVM.pubFirstName ||
patientDetailVM.pubLastName
) { x in
changeBackButton()
}
This is the simple function that I want to call:
func changeBackButton() {
withAnimation {
showBackButton = false
isEditing = true
}
}
Any guidance would be appreciated. Xcode 13.2.1 iOS 15
CodePudding user response:
Any time you are duplicating code you want to move it one level down so the same code can be reused.
Here is a solution, the parent view will hold a variable that will know if the "name" as a whole has changes.
import SwiftUI
class PatientDetailViewModel: ObservableObject{
@Published var pubFirstName: String = "John"
@Published var pubLastName: String = "Smith"
}
struct TrackingChangesView: View {
@StateObject var vm: PatientDetailViewModel = PatientDetailViewModel()
///Variable to know if there is a change
@State var nameHasChanges: Bool = false
var body: some View {
NavigationView{
NavigationLink("EditView", destination: {
VStack{
TrackingChangesTextFieldView(hasChanges: $nameHasChanges, text: $vm.pubFirstName, titleKey: "first name")
TrackingChangesTextFieldView(hasChanges: $nameHasChanges, text: $vm.pubLastName, titleKey: "last name")
Button("save", action: {
//Once you verify saving the object reset the variable
nameHasChanges = false
})//Enable button when there are changes
.disabled(!nameHasChanges)
}
//Or track the single variable here
.onChange(of: nameHasChanges, perform: {val in
//Your method here
})
//trigger back button with variable
.navigationBarBackButtonHidden(nameHasChanges)
})
}
}
}
struct TrackingChangesTextFieldView: View {
//Lets the parent view know that there has been a change
@Binding var hasChanges: Bool
@Binding var text: String
let titleKey: String
var body: some View {
TextField(titleKey, text: $text)
.onChange(of: text, perform: { _ in
//To keep it from reloading view if already true
if !hasChanges{
hasChanges = true
}
})
}
}
struct TrackingChangesView_Previews: PreviewProvider {
static var previews: some View {
TrackingChangesView()
}
}
CodePudding user response:
Another way you can do this is to make a combined publisher for both pubFirstName
and pubLastName
.
Add following function to your viewModel
var nameChanged: AnyPublisher<Bool, Never> {
$patientDetailVM.pubFirstName
.combineLatest($patientDetailVM.pubLastName)
.map { firstName, lastName in
if firstName != patientDetailVM.pubFirstName ||
lastName != patientDetailVM.pubLastName
{
return true
} else {
return false
}
}
.eraseToAnyPublisher()
}
and listen to nameChanged
publisher on onReceive
of your view
.onReceive(of: patientDetailVM.nameChanged) { hasNameChanged in
changeBackButton()
}
so you can listen to either of first or last name changed