swiftswiftuicombine

Swift Combine Pipeline to compare draft model


I have a Model in SwiftUI handling my person model. To be able to store draft persons in the editor in the View(s), I have two objects:

@Published var person: Person
@Published var draftPerson: Person

In the UI, I am only changing the draftPersons until the user clicks on "Save", which stores the draftPerson as the person. In the onAppear method of the editor, I reset the draftPerson to the person.

Now I want to disable the "Save" button of the Editor and therefor introduced a bool "modified" in the Model. Using a pipeline, I want to set the modified to true, if and as long as the draftPerson is not equal to person, by doing the following:

    $draftPerson.map { draftPerson in
        return draftPerson != self.person
    }
    .assign(to: \.modified, on: self)
    .store(in: &cancellables)

It looks like it is working on first glance, but if I change something in a textField, the value of modified is only set to true after the second change in the field. Vice versa, if I delete the typed values, it is only set back to false after I delete one more character as were originally there.

Question 1: Is there another "best practice" to handle changes in draft objects and deactivating the "Save" button in SwiftUI?

Question 2: Why is the pipeline "one change behind"?

Thanks a lot for your input.

Edit: I created a separate part of the App focusing only on the pipeline and realized that it is indeed working as intended if I remove my other pipelines. I have to check now in detail. Nevertheless, I will stick with my first question: Is there anything I can do better?

Please find the code here on Github


Solution

  • You could declare another @Published property and combine the two person and draftPerson publishers and publish whether they are the same, like this:

    @Published var person: Person
    @Published var draftPerson: Person
    @Published var saveDisabled: Bool = true
    
    public init() {
        
        // all init code
        
        Publishers.CombineLatest($person, $draftPerson)
            .map { $0 == $1 }
            .assign(to: &$saveDisabled)
        
    }
    

    But essentially it is not needed and a computed property will do the same job:

    var saveDisabled: Bool {
        person == draftPerson
    }
    

    Because both person and draftPerson are marked @Published each time one of them changes the View will be notified of the change so it will also pick new value of saveDisabled.