TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

How to disabled a button when a core data variable changes in SwiftUI?

Forums > SwiftUI

I am trying to disable a button based on a computed property from the View Model, but is only disabled after the view is reloaded.

This is the View Model :

  class VerifyFieldViewModel : ObservableObject {
    @ObservedObject var coreDataViewModel = CoreDataViewModel()
    func isValidFirstName() -> Bool {
        guard coreDataViewModel.savedDetails.first?.firstName?.count ?? 0 > 0 else {
            return false
        }
        return true
    }
    func isValidLastName() -> Bool {
        guard coreDataViewModel.savedDetails.first?.lastName?.count ?? 0 > 0 else {
            return false
        }
        return true
    }

    var isFirstNameValid : String {
        if isValidFirstName() {
       return ""
        } else {
        return "Name is empty"
        }
    }

    var isLastNameValid : String {
        if isValidLastName() {
       return ""
        } else {
        return "Surname is empty"
        }
    }

   var isSignUpComplete: Bool {
        if !isValidFirstName() || !isValidLastName() {
            return false
        }
        return true
    }
    }

This is how I am disabling the button .

struct CartsView: View {
    @State var onboardingState: Int = 0
    @StateObject var coreDataViewModel = CoreDataViewModel()
    @ObservedObject var verifyFieldViewModel = VerifyFieldViewModel()
    var body: some View {
        ZStack {
            switch onboardingState {

            case 0 :
                VStack {
                detailOrder
                    .transition(transition)
                Spacer()
                bottomButton
                    .padding(30)
                }
            case 1 :
                VStack {
                detailOrder2. -> This is LivrareView
                    .transition(transition)
                    Spacer()
                    bottomButton
                        .padding(30)
                        .opacity(verifyFieldViewModel.isSignUpComplete ? 1 : 0.6)
                        .disabled(!verifyFieldViewModel.isSignUpComplete)
                }
            default:
                EmptyView()
            }
        }
}
}

This is the Core Data View Model :

class CoreDataViewModel  : ObservableObject {
    let manager = CoreDataManager.instance
    @Published var savedDetails : [Details] = []
    init() {
        fetchSavedDetails()
    }
    func fetchSavedDetails() {
        let request = NSFetchRequest<Details>(entityName: "Details")
        do {
            savedDetails = try manager.context.fetch(request)
        } catch let error {
            print("Error fetching \(error)")
        }
    }

    func saveContext() {
        DispatchQueue.main.async {
            self.manager.save()
            self.fetchSavedDetails()
        }
        }

}
struct LivrareView: View {
    @StateObject var coreDataViewModel = CoreDataViewModel()
    @EnvironmentObject var syncViewModel : SyncViewModel
    @ObservedObject var verifyFieldsViewModel = VerifyFieldsViewModel()
    var body: some View {
        let firstName = Binding(
            get: {coreDataViewModel.savedDetails.first?.firstName ?? ""},
            set: {coreDataViewModel.savedDetails.first?.firstName = $0})

        let lastName = Binding(
            get: {coreDataViewModel.savedDetails.first?.lastName ?? ""},
            set: {coreDataViewModel.savedDetails.first?.lastName = $0})

        ScrollView {
            VStack(alignment: .leading) {
                Text("First Name")
                    .padding(.top)
                    .foregroundColor(.orange)
                EntryField(placeHolder: "Name", prompt: $verifyFieldsViewModel.isFirstNameValid, field: firstName)
                    .onSubmit {
                        coreDataViewModel.saveContext()
                    }
                Text("Last Name")
                    .padding(.top)
                    .foregroundColor(.orange)
                EntryField(placeHolder: "Last Name", prompt: $verifyFieldsViewModel.isLastNameValid, field: lastName)

                    .onSubmit {
                        coreDataViewModel.saveContext()
                    }
            }
        }
    }

NOTE : It works, but only when the view is reloaded.

How can i make this work?. I've tried

2      

hi,

one obvious problem is that you are writing

@ObservedObject var coreDataViewModel = CoreDataViewModel()

or

@ObservedObject var verifyFieldViewModel = VerifyFieldViewModel()

you should never be setting the value of an @ObservedObject directly inside a View, but rather it should reference a model that was created outside the View and passed into it.

another problem is that there are too many instances of models:

  • a VerifyFieldViewModel has its own copy of a CoreDataViewModel
  • CartsView has its own copies of a CoreDataViewModel and a VerifyFieldViewModel
  • LivrareView also has its own copies of a CoreDataViewModel and a VerifyFieldViewModel

none of these models is being shared across views.

so i think you need to work a little bit on your idea of modeling the data.

my suggestion to start: if CartsView and LivrareView are both contained inside, say, a TabView, then it's the TabView that should be creating the model(s) as @StateObject and passing them into CartsView and LivrareView as @ObservedObjects so that these views share the same data.

hope that helps,

DMG

2      

This is an observation I would share with my development team during a code review.

My team would require you to rewrite this awkward code:

var isFirstNameValid : String {
    if isValidFirstName() {
       return ""
        } else {
        return "Name is empty"
        }
    }

The main issue here is the var name isFirstNameValid suggests that this var is a Bool. Yet, you're returning a String. Furthermore, the variable's name suggests the name is valid, or not. And you're testing only if it has a value or is empty.

This is exactly the type of business logic that belongs in your data model. The model is the best place to determine if the name has content or is empty. Then you can decide if a non-empty value is valid. For example, X3k2b] probably is not a valid first name.

What I think you're going for is a person's first name as a string. If this is the case, put all that logic in your data model.

Then, whenever you need to display a person's first name you just reference the person's object from the data model. Maybe something like this:

// tell the view what you want to display
 Text( "First name: \(vm.person.firstName)" ) 
     // let the model determine if it has a valid name, or provide a placeholder.

2      

@twostraws has a great article here about value objects. He uses authentication as an example.

See > User Authentication

This can help you get back on track.

2      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.