Home > Blockchain >  How to declare a variable to work project-wide in Swift?
How to declare a variable to work project-wide in Swift?

Time:07-20

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:

enter image description here

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:

enter image description here

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

  • Related