I have a small project that needs to bind one variable to several SwiftUI views. How can I perform that action?
Thanks in advance
Update
I have this code in first view
import SwiftUI
struct ContentView : View {
@Environment(\.presentationMode)
private var presentationMode
@State var mutq = 0
var body: some View {
VStack(spacing: 20) {
Text("Testing this") //First line text
.navigationTitle("The program")
TextField("Enter number", value: $mutq , formatter: NumberFormatter())
.frame(width: 150.0, height: 20.0)
Button(action: {
OpenWindows.DetailView.open()
presentationMode.wrappedValue.dismiss()
}){
Text("Open Detail Window")
}
}
.frame(width: 400.0, height: 200.0)
}
}
enum OpenWindows: String, CaseIterable {
case DetailView = "DetailView"
func open(){
if let url = URL(string: "myapp://\(self.rawValue)") {
NSWorkspace.shared.open(url)
}
}
}
And second view this code
import SwiftUI
struct DetailView: View {
@Binding var mutq : Int
var body: some View {
VStack(spacing:30) {
Text("Test OK") //First line text
.navigationTitle("The program")
.position(x: 200, y:20)
TextField("Enter number", value: self.$mutq , formatter: NumberFormatter())
}
.frame(width: 400.0, height: 200.0)
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(mutq: .constant(0))
}
}
but as I can understand this doesn't quiet suits my objective that one variable should be taken from one window (var called "mutq") and by button click pass it to next window as a result. I can guess that I miss something under button click function AND the point is not only to pass Integer values but mainly String value(s). Maybe I am not using @Binding correct way , I don't know yet. Expecting your assistance :)
CodePudding user response:
It took me a while before I realized you were building a macOS app and your problem is a bit more specific.
First the easy part. If you only read an object in one window, that has been changed in another, you don't need a binding. You can pass it as an object. Your problem is a bit more complicated, since you are passing it through windows. So in your example, you should start defining your variable at the app level, not in the content view. Then pass it through the program. Your code then looks like this: at the app level:
import SwiftUI
@main
struct ObjectPassingApp: App {
@State private var thatPassableObject = ""
var body: some Scene {
WindowGroup {
ContentView(somePassableString: $thatPassableObject)
.frame(width: 400, height: 200)
}
WindowGroup("DetailView") {
DetailView(thatPassableObject: thatPassableObject)
.handlesExternalEvents(
preferring: Set(arrayLiteral: "DetailView"),
allowing: Set(arrayLiteral: "*")
)
}
.handlesExternalEvents(matching: Set(arrayLiteral: "DetailView"))
}
}
enum OpenWindows: String, CaseIterable {
case DetailView = "DetailView"
func open(){
if let url = URL(string: "ObjectPassing://\(self.rawValue)") {
NSWorkspace.shared.open(url)
}
}
}
Then you pass it in as a Binding to the ContentView, like this:
import SwiftUI
struct ContentView: View {
@Environment (\.dismiss) var dismiss
@Binding var somePassableString: String
var body: some View {
VStack(spacing: 20) {
Text("Testing this \(somePassableString)")
TextField("Enter whatever", text: $somePassableString)
.frame(height: 40)
.border(.gray)
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
.padding()
Button {
OpenWindows.DetailView.open()
dismiss()
} label: {
Text("Open Detail Window")
}
}
.navigationTitle("The program")
}
}
Here you can change the text or whatever you want to change in the TextField. It then looks like this:
The code for your DetailView is quite simple, since you only want to view it:
import SwiftUI
struct DetailView: View {
let thatPassableObject: String
var body: some View {
VStack(spacing: 30) {
Text("Test OK")
.navigationTitle("The Program")
Text("\(thatPassableObject)")
}
.frame(width: 400, height: 200)
}
}
The window then looks like this:
By the way, if you need to change the text in the second window, you pass it in as a binding, so you can also change it in the second window.
However, this is not the most elegant way to pass objects between windows in a macOS app, especially not, when your program gets bigger. You better declare an Observalble object and pass it in as an environment object.
Your code then looks a bit different. First you need a model class as an observable object:
import Foundation
class MyModel: ObservableObject {
@Published var myPassableString = ""
}
Then at the app level your code becomes:
import SwiftUI
@main
struct ObjectPassingApp: App {
@StateObject var myModel = MyModel()
var body: some Scene {
WindowGroup {
ContentView()
.frame(width: 400, height: 200)
.environmentObject(myModel)
}
WindowGroup("DetailView") {
DetailView()
.handlesExternalEvents(
preferring: Set(arrayLiteral: "DetailView"),
allowing: Set(arrayLiteral: "*")
)
.environmentObject(myModel)
}
.handlesExternalEvents(matching: Set(arrayLiteral: "DetailView"))
}
}
enum OpenWindows: String, CaseIterable {
case DetailView = "DetailView"
func open(){
if let url = URL(string: "ObjectPassing://\(self.rawValue)") {
NSWorkspace.shared.open(url)
}
}
}
Now your ContentView looks like:
import SwiftUI
struct ContentView: View {
@Environment (\.dismiss) var dismiss
@EnvironmentObject var myModel: MyModel
@State private var myText = ""
var body: some View {
VStack(spacing: 20) {
Text("Testing this \(myModel.myPassableString)")
TextField("Enter whatever", text: $myText)
.frame(height: 40)
.border(.gray)
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
.padding()
.onChange(of: myText) { newValue in
myModel.myPassableString = myText
}
Button {
OpenWindows.DetailView.open()
dismiss()
} label: {
Text("Open Detail Window")
}
}
.navigationTitle("The program")
}
}
And your DetailView looks like:
import SwiftUI
struct DetailView: View {
@EnvironmentObject var myModel: MyModel
var body: some View {
VStack(spacing: 30) {
Text("Test OK")
.navigationTitle("The Program")
Text("\(myModel.myPassableString)")
}
.frame(width: 400, height: 200)
}
}
By passing the class as an observable object through the environment, you can also change the content of the class freely in any window of your program.
This code works on my MacBook.
Kind regards, MacUserT
CodePudding user response:
You can append Variables to Views with @Binding declaration. Read this article here: What is binding in swiftui how to use it
Enter this in the view you want your variable
@Binding var valueFromParent : Int
And this where you call your view
BindingView(valueFromParent: $currentValue)
You can also use EnvironmentObject to share data how to use environmentObject