Swift static value stuck at original value - ios

Can someone tell me why my static variable isn't changing when I use a += on it? I have been stuck on this bug for a while.
#State static var counter:Int = 2
#State static var increment:Int = 1
var body: some View {
NavigationView {
VStack {
Text("Value: " + String(ContentView.counter))
Button(action: {
ContentView.counter += ContentView.increment
print(ContentView.counter)

If you want to share state between different instances of your view, you should inject said state as a Binding rather than declaring an #State static property inside the View.
#State properties should only be updated from inside the (body of the) view itself, which isn't the case if you declare an #State property as static. To ensure the update can only happen from inside the View, all #State properties must be private (or at the very least private(set)).
Not sure how exactly you create different instances of your view, but here's an example of how you could introduce shared state between them in a correct way:
struct Cell: View {
#Binding private var counter: Int
#Binding private var increment: Int
init(counter: Binding<Int>, increment: Binding<Int>) {
self._counter = counter
self._increment = increment
}
var body: some View {
VStack {
Text("Value: \(counter)")
Button("Increment by \(increment)", action: { counter += increment })
}
}
}
struct Counter: View {
#State private var counter: Int = 2
#State private var increment: Int = 1
var body: some View {
VStack {
Cell(counter: $counter, increment: $increment)
Cell(counter: $counter, increment: $increment)
Cell(counter: $counter, increment: $increment)
}
}
}

Related

Importing the data typed by the user to a view?

In the process of making my first Finance App, I want the user to type their Credit Card Name and las four numbers (probably more info since this is a draft) into this Modally presented view, to then be seen in a cards index, widget-look-like.
struct CardListView: View {
#State var isPresentingAddModal = false
#State var emisorTarjeta = ""
#State var numeroTarjeta = ""
var headerView: some View {
HStack {
Text("Tus tarjetas")
Spacer()
Button("Añadir nueva") {
self.isPresentingAddModal.toggle()
}
.sheet(isPresented: $isPresentingAddModal, content: {
HStack {
Text("Emisor de tarjeta")
TextField("Seleccionar emisor de tarjeta", text: $emisorTarjeta)
}
HStack {
Text("Número de tarjeta")
TextField("Escribí tu número de tarjeta", text: $numeroTarjeta)
}
Button(action: {
self.isPresentingAddModal.toggle()
print("\(self.emisorTarjeta)")
}, label: {
Text("Añadir")
})
Spacer()
})
}
The question now is how to pass the info typed from the two textFields, to the view where the cards will be created. The button "Añadir" currently works as a dismiss button instead of an add one, since I don't know how to create that.
(Also, a lot of code like paddings and backgroundColors have been erased to make it clearer to see)
Enitre view of the homeView
Where the "añadir" button is
there are several ways to do this. One simple way is to use "#State" and "#Binding" like this:
In "CardListView" use this:
#Binding var emisorTarjeta: String
#Binding var numeroTarjeta: String
and in the "CardViewCreator" use:
#State var emisorTarjeta = ""
#State var numeroTarjeta = ""
Another way is to use "ObservableObject", create a class like this:
class CardModel: ObservableObject {
#Published var emisorTarjeta = ""
#Published var numeroTarjeta = ""
}
In the your "CardViewCreator" or some parent view:
#StateObject var cardModel = CardModel()
and pass it to the "CardListView" like this:
struct CardListView: View {
#ObservedObject var cardModel: CardModel
...
}
You can also use "EnvironmentObject" in a similar way.
It all depends on your case. I recommend reading up on "ObservedObject"
and using that.
A really simple way of doing this is to pass in a closure to run when the add button is tapped. Here's an example, which also shows how to dismiss the presented sheet
import SwiftUI
struct Card: Identifiable {
let id = UUID()
let provider: String
let number: String
}
struct ContentView: View {
#State private var cards = [Card]()
#State private var showingSheet = false
var body: some View {
VStack {
List(cards, rowContent: CardView.init)
.padding(.bottom, 10)
Button("Add") {
showingSheet = true
}
.padding()
}
.sheet(isPresented: $showingSheet) {
AddSheet(completion: addCard)
}
}
func addCard(provider: String, number: String) {
let newCard = Card(provider: provider, number: number)
cards.append(newCard)
}
}
struct CardView: View {
let card: Card
var body: some View {
VStack(alignment: .leading) {
Text(card.provider)
Text(card.number)
}
}
}
struct AddSheet: View {
#Environment(\.presentationMode) var presentationMode
#State private var provider = ""
#State private var number = ""
let completion: (String, String) -> Void
var body: some View {
VStack {
TextField("Provider", text: $provider).padding()
TextField("Number", text: $number).padding()
Button("Add") {
completion(provider, number)
presentationMode.wrappedValue.dismiss()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you want to actually save the information passed in the textfield you would have to save it somewhere and later fetch it when required But this is only if you want to be able to access the information passed into the cards index after you have closed down the application and opened it up once again.

SwiftUI: Reset TextField value from CoreData on change in value of another state variable

I have a ContentView and a NumericView. The selectedIndex is a #State in ContentView. What I want is that if selectedIndex changes, it should reset the answer field in NumericView from CoreData. Below is the code. "av" contains the value of the answer field from CoreData.
struct NumericEntry: View {
#EnvironmentObject var questionAnswerStore: QuestionAnswerStore
#State var answer: String
var questionIndex: Int
init(questionIndex: Int, av: String) {
self.questionIndex = questionIndex
_answer = State(initialValue: av)
}
var body: some View {
VStack {
TextField("Answer", text: $answer)
.textFieldStyle(CustomTextFieldStyle())
.onChange(of: answer) { newValue in
self.questionAnswerStore.getUserAttemptData(selectedIndex: questionIndex).answer = newValue
PersistenceController.shared.saveContext()
}
.keyboardType(.decimalPad)
.padding()
}
}
private struct CustomTextFieldStyle : TextFieldStyle {
public func _body(configuration: TextField<Self._Label>) -> some View {
configuration
.padding(10)
.background(
RoundedRectangle(cornerRadius: 5)
.strokeBorder(Color.secondary.opacity(0.5), lineWidth: 1))
}
}
}
struct ContentView: View {
#State var selectedIndex: Int = 0
#ObservedObject private var questionAnswerStore: QuestionAnswerStore = QuestionAnswerStore.sharedInstance
var body: some View {
NumericEntry(questionIndex: selectedIndex, av: self.questionAnswerStore.getUserAttemptData(selectedIndex: selectedIndex).answer ?? "")
// some code that keeps changing the value of selectedIndex
}
}
I read somewhere that _stateVariable = State(initialValue: "abcd") should set the state of #State stateVariable. In the above code the code
_answer = State(initialValue: av)
executes fine but when it reaches
TextField("Answer", text: $answer)
$answer is still "".
I would prefer a solution where I don't even have to send "av" from the parent component, just selectedIndex should check QuestionAnswerStore for the value of "answer". This could be solved using .onAppear but in my case, the NumericView appears only once and then its parent just keeps changing selectedIndex value, so onAppear doesn't get called again.
Of course, if that is not possible then what's the way out using "av" as above?

How does one use a Stepper to update a value in the model in SwiftUI?

I am trying to increment or decrement a value using Stepper, however I cannot fathom how to take the new value and update my model.
I've also tried to use the onIncrement, onDecrement version, but here there appears to be no option to disable the + or - when the range has been reached.
Here's my example code:
import SwiftUI
struct ContentView: View {
var body: some View {
MainView()
}
}
struct MainView: View {
#ObservedObject var updateAge = UpdateAge()
#State private var showAgeEditor = false
#State var age: Int = 0
var body: some View {
VStack {
/// Show the current age.
Text("Age \(age)")
Image(systemName: "keyboard")
.onTapGesture {
self.showAgeEditor = true
}
/// Present the sheet to update the age.
.sheet(isPresented: $showAgeEditor) {
SheetView(showAgeEditor: self.$showAgeEditor)
.environmentObject(self.updateAge)
.frame(minWidth: 300, minHeight: 400)
}
}
/// Calls the viewModel (UpdateAge) to fetch the age from the model.
.onAppear(perform: {self.age = self.updateAge.withAge})
}
}
struct SheetView: View {
#EnvironmentObject var updateAge: UpdateAge
#Binding var showAgeEditor: Bool
/// Steppers state.
#State private var age: Int = 18
let maxAge = 21
let minAge = 18
var body: some View {
return VStack {
Text("Age of person = \(age)")
/// When I increment or Decrement the stepper I want the Age to increase or decrease.
Stepper<Text>(value: $age, in: minAge...maxAge, step: 1) {
/// And when it does, to store the value in the model via the viewModel.
/// However I appear to have created an infinite loop. Just uncomment the line below.
// self.updateAge.with(age: age)
/// Then display the value in the label.
return Text("\(age)")
}
}.onAppear(perform: {self.age = self.updateAge.withAge})
}
}
class UpdateAge: ObservableObject {
#Published var model = Model()
func with(age value: Int) {
print("Value passed \(value)")
self.model.setDrinkingAge(with: value)
}
var withAge: Int {
get { model.drinkingAge }
}
}
struct Model {
var drinkingAge: Int = 18
mutating func setDrinkingAge(with value: Int) {
drinkingAge = value
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can bind to model directly via view model projected value, like below (so local state age is redundant)
Stepper(value: $updateAge.model.drinkingAge, in: minAge...maxAge, step: 1) {
Text("\(updateAge.model.drinkingAge)")
}

how to update SwiftUIView withOut #State variable? and segmentation fault issue

I want increment a variable on Button action and show the value on Text. I can do it with #State var i: Int = 0 variable. But I want to update the value from another class or swift file. So that file cannot access this variable unless it is static. But State can't be static as it fails build with segmentation fault error while building.
So I thought of making it a simple variable. Then updating from that class and then assigning it with a #State variable. But that is not happening because to update variable I'm using another static function on SwiftUIView struct as well.
I can update the static variable but I can't update the View.
heres the code for UIVIew:
struct ContentView: View {
//private var Arr = [String]()
#State static var x: Int = 0 //Causes segmentation fault, Removing static runs but can't access var x
static var testVar: Int = 0
var body: some View {
VStack{
Text("Hello, Time: \(ContentView.testVar)")
Button(action: {
_ = timer().testMe() // calling other class method, is this Okay?
}) {
Text("Button")
}
}
}
static func bigGuy(){
testVar += 1
print("Hello You clicked this time: \(testVar)") // Without next line it can run and print with no issue.
self.x = testVar // Don't want to use this, But how do I update View ?
}
So how do I update this simple view?
Don't use static, instead put that variable into view model and use instance of view model in both views, so one modifies it another presents it, like in below example
class ViewModel: ObservableObject {
#Published var x: Int = 0
}
struct View1: View {
#ObservedObject var vm: ViewModel
var body: some View {
Text("\(mv.x)")
}
}
struct View2: View {
#ObservedObject var vm: ViewModel
var body: some View {
Button("Modify") { self.mv.x += 1 }
}
}
struct ContentView: View {
let vm = ViewModel()
var body: some View {
VStack {
View1(vm: self.vm)
View2(vm: self.vm)
}
}
}

How to initialize derived variables in body methods in SwiftUI (or alternate approach)

I'm trying to figure out the right way to initialized derived variables in the body method for a SwiftUI view. An example would the string value for an editable integer which would then be edited in a TextField. The integer could for example be part of an #ObservedObject. I cannot figure out any remotely clean way to do this.
I've looked into using custom initializers but this does not seem like the right thing to do. I'm not even sure this code would be run at the appropriate time.
I've also tried using the .onAppear method for TextField, but this method does not appear to be re-executed when the view is rebuilt.
simplified example:
final class Values : ObservableObject {
#Published var count: Int = 0;
}
var sharedValues = Values()
struct ContentView : View {
#ObservedObject var values = sharedValues
var body: some View {
VStack {
Button(
action: { self.add() },
label: { Text("Plus")}
)
InnerView()
}
}
func add() { values.count += 1 }
}
struct InnerView : View {
#ObservedObject var values = sharedValues
#State private var text = ""
var body: some View {
// text = String(value.count) - what I want to do
TextField("", text: $text, onEditingChanged: updateCount)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
func updateCount(updated: Bool) { /* this isn't important in this context */}
}
I would hope to be able to update sharedValues externally and see the update in MyView. In this example, I would want pressing the button to update the text field with the updated text value. But I can't figure a way to have the string representation of the count value computed at the appropriate point in the execution of the code.
I've tried multiple approaches to achieving this type of result, but have come up short.
I'm not sure if I'm understanding your question correctly, but if you are just trying to be able to change a number with a button, have the number be displayed in a text field, and then be able to edit it there, you don't need an ObserverableObject or multiple views.
Here is an example of how you can do it:
struct ContentView: View {
#State var count = 0
#State var countStr = ""
var body: some View {
VStack {
Button(action: {
self.count += 1
self.countStr = "\(self.count)"
}) {
Text("Plus")
}
TextField("", text: $countStr, onEditingChanged: updateCount)
}
}
func updateCount(updated: Bool) { /* this isn't important in this context */ }
}
Use value init method of TextField. This take the value as 2 way Binding. So it automatically update count from both text field and buttons.
import SwiftUI
import Combine
final class Values : ObservableObject {
#Published var count: Int = 0;
}
var sharedValues = Values()
struct AndrewVoelkel : View {
#ObservedObject var values = sharedValues
var body: some View {
HStack {
InnerView()
VStack{
Button(
action: { self.add() },
label: { Text("+")}
)
Button(
action: { self.sub() },
label: { Text("-")}
)
}.font(.headline)
}.padding()
}
func add() { values.count += 1 }
func sub() { values.count -= 1 }
}
struct InnerView : View {
#ObservedObject var values = sharedValues
var body: some View {
TextField("", value: $values.count, formatter: NumberFormatter())
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}

Resources