1

I have a segmented control on my toolbar that switches the @published selectedTab variable. this code does not switch like it should between accounts and budgets. if I change the @State var ledger variable on the LedgerView to a var and not State it works just fine. What am I doing wrong. it was my understanding state should be used like this. its the source of truth and most people even recommend marking it private?

import SwiftUI struct ContentView:View { @EnvironmentObject var shared:SharedObject var body: some View { VStack { if shared.selectedTab == 0 { LedgerView(ledger: .Accounts) } else { LedgerView(ledger: .Budgets) } }.frame(maxWidth: .infinity, maxHeight: .infinity) } } struct LedgerView:View { @EnvironmentObject var shared:SharedObject @State var ledger:LedgerType var body:some View { Text(ledger.name) } } 
3
  • Is the LedgerView just a label with a ledger name? If so why not just pass the string? Commented Mar 11, 2020 at 4:09
  • @Watermamal sorry no its not I just did just the label so that I could throw up a quick sample to show the issue that I was having. Commented Mar 11, 2020 at 4:11
  • in this sample the use of State makes no sense, State is used so you can have mutating variables inside a struct(swiftui view), and without more code or an explanation of what that view is actually suposed to do, I'm not sure what the actual question is. Commented Mar 11, 2020 at 4:24

2 Answers 2

1

As explained by Muhand Jumah, @State variable usage is incorrect. I'm just trying to add to Muhand Jumah's answer. In your code the LedgerType is created based on the shared variable (selected tab). In a sense, the shared variable is the source of truth. Therefore, I've modified your code to switch the ledger type based on the shared environment variable. That way, you don't have to pass the ledger type to the ledger view. Here's the code. Note that I've set the initial value of selected tab to 0 in SharedObject.init.

struct ContentView:View { @EnvironmentObject var shared: SharedObject var body: some View { VStack { Picker(selection: $shared.selectedTab, label: Text("")) { ForEach(0 ..< LedgerType.allCases.count) { index in Text(LedgerType.allCases[index].rawValue).tag(index) } } .pickerStyle(SegmentedPickerStyle()) Text("Value: \(String(shared.selectedTab))") LedgerView() } .frame(maxWidth: .infinity, maxHeight: .infinity) } } struct LedgerView:View { @EnvironmentObject var shared: SharedObject var ledger: LedgerType { shared.selectedTab == 0 ? .Accounts : .Budgets } var body:some View { Text(ledger.rawValue) } } enum LedgerType: String, CaseIterable { case Accounts = "Accounts" case Budgets = "Budgets" } class SharedObject: ObservableObject { @Published var selectedTab: Int init(selectedTab: Int = 0) { self.selectedTab = selectedTab } } 
Sign up to request clarification or add additional context in comments.

Comments

0

The problem is that you are always initializing a new view with a Ledger.

@State was meant to change the view on the spot, not re-initialize it.

For example

import SwiftUI struct SwiftUIView: View { @State counter: Int = 0 var body: some View { VStack { Button(action:{ self.counter += 1 }) { Text("Increment") } Text("counter: \(self.counter)") } } } 

In the above code you can see a use for state, where the variable and the command that change the variable are both in the same view.

In your example, if what changes the tabs is in the same view then you can use @State and 1 LedgerView but with the @State attached to it.

For example

import SwiftUI struct SwiftUIView: View { @State ledger: LedgerType = 0 var body: some View { VStack { LedgerView(ledger: self.ledger) HStack { Button(action:{ self.ledger = .Account }) { Text("Account") } Button(action:{ self.ledger = .Budget }) { Text("Budget") } } } } } struct LedgerView:View { @EnvironmentObject var shared:SharedObject var ledger:LedgerType var body:some View { Text(ledger.name) } } 

Yes you can just remove the @State from your example and it should work fine for you but you lose things such as animations between views.

However, as I understand you are don't have the controls on the same view hence I would recommend using EnvironmentObject the way you are using it. But instead of keeping track of the index, I would keep track of the actual LedgerType chosen. Or you can keep track of both.

If you keep track of the page the ledger on shared then you can do this easily

import SwiftUI struct SwiftUIView: View { @EnvironmentObject var shared:SharedObject var body: some View { VStack { LedgerView(ledger: shared.ledger) } } } struct LedgerView:View { @EnvironmentObject var shared:SharedObject var ledger:LedgerType var body:some View { Text(ledger.name) } } 

What you were doing doesn't make sense, because again you want to use @State when you want to change something in the same page. In your example inside LedgerView you are creating a variable @State var ledger:LedgerType but in reality if the value of this variable changes INSIDE this instance, it doesn't mean anything to you. You only care about the change of the ledger value in a different view (such as root or parent view) hence using @State needs to be in the parent/root view.

I hope this makes sense.

4 Comments

thank you that is helpful. I will just leave the ledger var as a standard variable on the ledgerview. I can't change the contentview the way you suggested thought. the selectedtab variable will have tabs that won't have the ledgerview eventually like reportview and others I just have not implemented them yet because im still building the ledgerview.
Glad I was able to help. If you still need help with this, leave another comment and I will edit my answer as needed. Good luck!
so now I am having a problem with the same view I added a variable that should be state matches the criteria that you listed. but it is not working as I expected. see here stackoverflow.com/questions/60629576/…
I have posted my answer on that post

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.