What is the best practice to share variables between views? My app has only one view. But as it gets more and more complicated, I think I should separate it into several views. Also to separate the methods. I started with something like this:
struct ContentView: View {
@State var str: String = "String"
var body: some View {
VStack(alignment: .leading) {
Text(str)
TextField("Input", text: $str)
Button("button", action: { doSomething() })
}.padding()
}
func doSomething() {
str = str + " " + str
}
}
And want to go there:
class GlobalVars: ObservableObject {
@Published var str: String = "Initial String"
}
struct ContentView: View {
@ObservedObject var globalvars = GlobalVars()
var body: some View {
VStack(alignment: .leading) {
DisplayView()
EditView()
ControlView()
}.padding()
}
}
struct DisplayView: View {
@Binding var str: String
var body: some View {
Text(self.globalvars.str)
}
}
struct EditView: View {
@Binding var str: String
var body: some View {
TextField("Input", text: self.$str)
}
}
struct ControlView: View {
@Binding var str: String
var body: some View {
Button("button", action: { doSomething() })
}
}
func doSomething() {
@Binding var str: String
self.str = self.str + " " + self.str
}
I tried with @Published, @ObservedObject and @Binding. But don't get it. Thank you for any pointer in advance.
There are a number of ways to approach this.
My choice would probably be passing the binding just to the variable that you need access to. That might look like this:
class GlobalVars: ObservableObject {
@Published var str: String = "Initial String"
}
struct ContentView: View {
@ObservedObject var globalvars = GlobalVars()
var body: some View {
VStack(alignment: .leading) {
DisplayView(str: globalvars.str) //don't need a binding here since it doesn't get modified
EditView(str: $globalvars.str)
ControlView(str: $globalvars.str)
}.padding()
}
}
struct DisplayView: View {
var str: String //don't need a binding here since it doesn't get modified
var body: some View {
Text(str)
}
}
struct EditView: View {
@Binding var str: String
var body: some View {
TextField("Input", text: $str)
}
}
struct ControlView: View {
@Binding var str: String
var body: some View {
Button("button", action: { doSomething() })
}
func doSomething() {
str = str + " " + str
}
}
Note that now in ContentView
, there's a parameter passed to each of the subviews, containing a binding (using the $
sign) to the GlobalVars
str
property.
Also, doSomething
got moved into the body of ControlView
You could also use EnvironmentObject to handle this. I'm personally not as big of a fan of this approach because I'd rather see explicitly where my parameters are going. It also gives the subviews access to the entire ObservableObject, which isn't really necessary. But, it shows you the principal:
class GlobalVars: ObservableObject {
@Published var str: String = "Initial String"
}
struct ContentView: View {
@ObservedObject var globalvars = GlobalVars()
var body: some View {
VStack(alignment: .leading) {
DisplayView()
EditView()
ControlView()
}.padding()
.environmentObject(globalvars)
}
}
struct DisplayView: View {
@EnvironmentObject var globalvars : GlobalVars
var body: some View {
Text(globalvars.str)
}
}
struct EditView: View {
@EnvironmentObject var globalvars : GlobalVars
var body: some View {
TextField("Input", text: $globalvars.str)
}
}
struct ControlView: View {
@EnvironmentObject var globalvars : GlobalVars
var body: some View {
Button("button", action: { doSomething() })
}
func doSomething() {
globalvars.str = globalvars.str + " " + globalvars.str
}
}
Note that now, globalvars
is passed to the children by being placed in the view hierarchy with .environmentObject
. Each subview has access to it by declaring a property of @EnvironmentObject var globalvars : GlobalVars
You could also do kind of a hybrid model where you explicitly pass the ObservableObject as a parameter to the child view:
struct ContentView: View {
@ObservedObject var globalvars = GlobalVars()
var body: some View {
VStack(alignment: .leading) {
DisplayView(globalvars: globalvars)
}.padding()
.environmentObject(globalvars)
}
}
struct DisplayView: View {
@ObservedObject var globalvars : GlobalVars
var body: some View {
Text(globalvars.str)
}
}