2

Using a TextField on a mac app, when I hit 'return' it resets to its original value, even if the underlying binding value is changed.

import SwiftUI

class ViewModel {
    let defaultS = "Default String"
    var s = ""
    
    var sBinding: Binding<String> {
        .init(get: {
            print("Getting binding \(self.s)")
            return self.s.count > 0 ? self.s : self.defaultS
        }, set: {
            print("Setting binding")
            self.s = $0
        })
    }
    
}

struct ContentView: View {
    @State private var vm = ViewModel()
    
    var body: some View {
        TextField("S:", text: vm.sBinding)
            .padding()
    }
}

gif showing the textfield resetting

Why is this? Shouldn't it 'get' the binding value and use that? (i.e. shouldn't I see my print statement "Getting binding" in the console after I hit 'return' on the textfield?).

4
  • 1
    For a cleaner code, I think you should subclass ViewModel with ObservableObject and add @Published var s = "Default String". In The view replace State with StateObject and you should be good to go! Commented Sep 22, 2021 at 14:16
  • @NoeOnJupiter you're right, that works! If you want to edit the code, I'll accept your answer; otherwise I'll do it later today. Thank you. Commented Sep 22, 2021 at 14:27
  • You cannot use @State for shared values (i.e. sharing it with a ViewModel). If you want to use a ViewModel (or "DataModel" or "Model"), you have to use either a @StateObject or an @ObservedObject. Think of @State as a private variable. Commented Sep 22, 2021 at 14:27
  • You may get some inconsistent results the way you declared the @State now @StateObject. By declaring it as '@StateObject private var vm = ViewModel()` you create a NEW ViewModel and do not share one model. This is fine if you only have one view using it, but if you have multiple different views, you are not using a single source of truth for them. Commented Sep 22, 2021 at 14:56

1 Answer 1

4

Here you go!

class ViewModel: ObservableObject {
    @Published var s = "Default String"
}

struct ContentView: View {
    @StateObject private var vm = ViewModel()
    var body: some View {
        TextField("S:", text: $vm.s)
            .padding()
    }
}

For use in multiple views, in every view where you'd like to use the model add:

@EnvironmentObject private var vm: ViewModel 

But don't forget to inject the model to the main view:

ContentView().environmentObject(ViewModel())
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.