TextField in SwiftUI resets after every keystroke - swift

in this script the TextField is shown, but it resets to "player 1" after every keystroke. Can anyone help me?
import SwiftUI
class Player: ObservableObject {
#Published var playerData = "player 1"
}
let player = Player()
struct ContentView: View {
#ObservedObject var player: Player
var body: some View {
TextField("player", text: $player.playerData)
}
}
Tried with other code, but without success.

class Player: ObservableObject {
#Published var playerData = "player 1"
static let shared = Player()
}
struct ContentView: View {
#ObservedObject var player = Player.shared
var body: some View {
TextField("player", text: $player.playerData)
}
}
However, usually the model store would be an environmentObject PlayerStore and would contain an array of Player model structs.

Related

Using data with different files

I'm trying to display the name that the user enters in the text field, but when I launch the app, it only shows 'The player is'. I tried to assign the value of player1Name immediately to a string and it worked
This is my first view
class PlayerList : ObservableObject {
#Published var player1Name = ""
}
struct Player : Identifiable {
var name : String
let id = UUID()
}
struct ViewJouer: View {
#ObservedObject var player = PlayerList()
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
TextField("Enter player name", text: $player.player1Name)
}
This is my second view:
struct ViewTest: View {
#ObservedObject var player = PlayerList()
var body: some View {
Text("The player is \(player.player1Name)")
}
}

SwiftUI How to have class viewable from all views

I am fairly new to SwiftUI and I am trying to build an app where you can favorite items in a list. It works in the ContentView but I would like to have the option to favorite and unfavorite an item in its DetailView.
I know that vm is not in the scope but how do I fix it?
Here is some of the code in the views. The file is long so I am just showing the relevant code
struct ContentView: View {
#StateObject private var vm = ViewModel()
//NavigationView with a List {
//This is the code I call for showing the icon. The index is the item in the list
Image(systemName: vm.contains(index) ? "heart.fill" : "heart")
.onTapGesture{
vm.toggleFav(item: index)
}
}
struct DetailView: View {
Hstack{
Image(systemName: vm.contains(entry) ? "heart.fill" : "heart") //Error is "Cannot find 'vm' in scope"
}
}
Here is the code that that vm is referring to
import Foundation
import SwiftUI
extension ContentView {
final class ViewModel: ObservableObject{
#Published var items = [Biase]()
#Published var showingFavs = false
#Published var savedItems: Set<Int> = [1, 7]
// Filter saved items
var filteredItems: [Biase] {
if showingFavs {
return items.filter { savedItems.contains($0.id) }
}
return items
}
private var BiasStruct: BiasData = BiasData.allBias
private var db = Database()
init() {
self.savedItems = db.load()
self.items = BiasStruct.biases
}
func sortFavs(){
withAnimation() {
showingFavs.toggle()
}
}
func contains(_ item: Biase) -> Bool {
savedItems.contains(item.id)
}
// Toggle saved items
func toggleFav(item: Biase) {
if contains(item) {
savedItems.remove(item.id)
} else {
savedItems.insert(item.id)
}
db.save(items: savedItems)
}
}
}
This is the list view...
enter image description here
Detail view...
enter image description here
I tried adding this code under the List(){} in the ContentView .environmentObject(vm)
And adding this under the DetailView #EnvironmentObject var vm = ViewModel() but it said it couldn't find ViewModel.
To put the view model inside the ContentView struct is wrong. Delete the enclosing extension.
If the view model is supposed to be accessed from everywhere it must be on the top level.
In the #main struct create the instance of the view model and inject it into the environment
#main
struct MyGreatApp: App {
#StateObject var viewModel = ViewModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(viewModel)
}
}
}
And in any struct you want to use it add
#EnvironmentObject var vm : ViewModel
without parentheses.

SwiftUI not update view using #Published

I can't update the color of my Text base on the current status of my object.
The text should change color base on the variable status true or false.
I try below to simplify the code of where the data come from.
My contentview:
struct ContentView: View {
#StateObject var gm = GameManager()
#State var openSetting = false
var body: some View {
Button {
openSetting.toggle()
} label: {
Text("Setting")
}
}
}
ContentView has a SettingView where I'm selecting setting and where I want to update my textColor based on the status of object
struct SettingView: View {
#StateObject var gm : GameManager
var body: some View {
ScrollView(.horizontal, showsIndicators: true) {
HStack(spacing: 20) {
ForEach(gm.cockpit.ecamManager.door.doorarray) { doorName in
Button {
gm.close(door: doorName.doorName)
} label: {
Text(doorName.doorName)
// Here where I want to change color
.foregroundColor(doorName.isopen ? .orange : .green)
}
}
}
}
}
}
The data come from GameManager which inside has a variable called cockpit:
class GameManager: NSObject, ObservableObject, ARSessionDelegate, ARSCNViewDelegate {
#Published var cockpit = MakeCockpit() // create the cockpit
// do other stuff
}
MakeCockpit :
class MakeCockpit: SCNNode, ObservableObject {
#Published var ecamManager = ECAMManager()
// do other stuff
ECAMManager:
class ECAMManager: ObservableObject {
#Published var door = ECAMDoor()
#Published var stanby = ECAMsby()
}
And Finally... the Array I want to watch is in ECAMDoor class:
class ECAMDoor: ObservableObject {
#Published var doorarray : [Door] = [] // MODEL
}
Now everything work fine as expected but the #Publish of the door array not update my color in the setting view. I need to close the view and open again to se the color update.
Is someone can tell me where I mistake? I probably missed something .. hope I been clear (to many instance of class inside other class)

SwiftUI TextField resets value and ignores binding

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()
}
}
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?).
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())

Binding value from an ObservableObject

Aim:
I have a model which is an ObservableObject. It has a Bool property, I would like to use this Bool property to initialise a #Binding variable.
Questions:
How to convert an #ObservableObject to a #Binding ?
Is creating a #State the only way to initialise a #Binding ?
Note:
I do understand I can make use of #ObservedObject / #EnvironmentObject, and I see it's usefulness, but I am not sure a simple button needs to have access to the entire model.
Or is my understanding incorrect ?
Code:
import SwiftUI
import Combine
import SwiftUI
import PlaygroundSupport
class Car : ObservableObject {
#Published var isReadyForSale = true
}
struct SaleButton : View {
#Binding var isOn : Bool
var body: some View {
Button(action: {
self.isOn.toggle()
}) {
Text(isOn ? "On" : "Off")
}
}
}
let car = Car()
//How to convert an ObservableObject to a Binding
//Is creating an ObservedObject or EnvironmentObject the only way to handle a Observable Object ?
let button = SaleButton(isOn: car.isReadyForSale) //Throws a compilation error and rightly so, but how to pass it as a Binding variable ?
PlaygroundPage.current.setLiveView(button)
Binding variables can be created in the following ways:
#State variable's projected value provides a Binding<Value>
#ObservedObject variable's projected value provides a wrapper from which you can get the Binding<Subject> for all of it's properties
Point 2 applies to #EnvironmentObject as well.
You can create a Binding variable by passing closures for getter and setter as shown below:
let button = SaleButton(isOn: .init(get: { car.isReadyForSale },
set: { car.isReadyForSale = $0} ))
Note:
As #nayem has pointed out you need #State / #ObservedObject / #EnvironmentObject / #StateObject (added in SwiftUI 2.0) in the view for SwiftUI to detect changes automatically.
Projected values can be accessed conveniently by using $ prefix.
You have several options to observe the ObservableObject. If you want to be in sync with the state of the object, it's inevitable to observe the state of the stateful object. From the options, the most commons are:
#State
#ObservedObject
#EnvironmentObject
It is upto you, which one suits your use case.
No. But you need to have an object which can be observed of any change made to that object in any point in time.
In reality, you will have something like this:
class Car: ObservableObject {
#Published var isReadyForSale = true
}
struct ContentView: View {
// It's upto you whether you want to have other type
// such as #State or #ObservedObject
#EnvironmentObject var car: Car
var body: some View {
SaleButton(isOn: $car.isReadyForSale)
}
}
struct SaleButton: View {
#Binding var isOn: Bool
var body: some View {
Button(action: {
self.isOn.toggle()
}) {
Text(isOn ? "Off" : "On")
}
}
}
If you are ready for the #EnvironmentObject you will initialize your view with:
let contentView = ContentView().environmentObject(Car())
struct ContentView: View {
#EnvironmentObject var car: Car
var body: some View {
SaleButton(isOn: self.$car.isReadyForSale)
}
}
class Car: ObservableObject {
#Published var isReadyForSale = true
}
struct SaleButton: View {
#Binding var isOn: Bool
var body: some View {
Button(action: {
self.isOn.toggle()
}) {
Text(isOn ? "On" : "Off")
}
}
}
Ensure you have the following in your SceneDelegate:
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
.environmentObject(Car())
In my case i used .constant(viewModel) to pass viewModel to ListView #Binding var viewModel
Example
struct CoursesView: View {
#StateObject var viewModel = CoursesViewModel()
var body: some View {
ZStack {
ListView(viewModel: .constant(viewModel))
ProgressView().opacity(viewModel.isShowing)
}
}
}
struct ListView: View {
#Binding var viewModel: CoursesViewModel
var body: some View {
List {
ForEach(viewModel.courses, id: \.id) { course in
Text(couse.imageUrl)
}
}
}
}