'Pass @Published var by reference to function from View

I am working on a registration screen which has a ViewModel attached to it.

The ViewModel is just a class which extends my registration screen class, where I place all of my business logic.

extension RegistrationScreen {
    @MainActor class ViewModel : ObservableObject {
         //business logic goes here
    }
}

The ViewModel has @Published variables to represent two states for every text field in the screen: text and validationText.

 @Published var first: String = ""
 @Published var firstValidationMessage: String = ""
      

within that ViewModel I have a call to a helper function from another class which checks to see if the field's text is empty and if so it can set the fields validation text to an error, that looks like this:

class FieldValidation: Identifiable {


func isFieldEmptyAndSetError(fieldText:String, fieldValidationText:Binding<String>) -> Bool {
    
    if(fieldText.isEmpty){
        fieldValidationText.wrappedValue = "Required"
        return true
    }else{
        fieldValidationText.wrappedValue = ""
        return false
    }
}
}

to call that function from my viewModel I do the following:

FieldValidation().isFieldEmptyAndSetError(fieldText:first,fieldValidationText: firstValidationMessage)

This is throwing runtime errors which are hard to decrypt for me since I am new to Xcode and iOS in general.

My question is, how can I get this passing by reference to work? alternatively if the way I am doing is not possible please explain what is going on.



Solution 1:[1]

It's kind of weird having a function that receives a @Published as a parameter, if you want to handle all the validations in the viewModel, this seems as an easy solution for Combine. I would suggest you to create an extension to validate if the string is empty, since .isEmpty does not trim the text.

The class

  class FieldValidation: Identifiable {
    
    
    static func isFieldEmptyAndSetError(fieldText:String) -> String {
        return fieldText.isEmpty ?  "Required" : ""
    }
}

The view Model

    import Foundation
import Combine

class viewModel: ObservableObject {
    @Published var text1 = ""
    @Published var text2 = ""
    @Published var text3 = ""
    @Published var validationText1 = ""
    @Published var validationText2 = ""
    @Published var validationText3 = ""
    
    init() {
        self.$text1.map{newText in  FieldValidation.isFieldEmptyAndSetError(fieldText: newText)}.assign(to: &$validationText1)
        
        self.$text2.map{newText in  FieldValidation.isFieldEmptyAndSetError(fieldText: newText)}.assign(to: &$validationText2)
        
    }

     func validateText(value: String) {
        self.validationText3 = FieldValidation.isFieldEmptyAndSetError(fieldText: value)
    }
}

The view would look like this:

      import SwiftUI

    struct ViewDate: View {
        @StateObject var myviewModel = viewModel()
        
        var body: some View {
            
            VStack{
                Text("Fill the next fields:")
                
                TextField("", text: $myviewModel.text1)
                    .border(Color.red)
                Text(myviewModel.validationText1)
                
                TextField("", text: $myviewModel.text2)
                    .border(Color.blue)
                Text(myviewModel.validationText2)
                
                TextField("", text: $myviewModel.text3)
                    .border(Color.green)
                    .onChange(of: myviewModel.text3) { newValue in
                        if(newValue.isEmpty){
                            self.myviewModel.validateText(value: newValue)
                        }
                    }
                Text(myviewModel.validationText3)
            }.padding()
        }
    }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1