'@State var not updating SwiftUI View

I want to have my content view display data that is global to the app and manipulated outside of the content view itself.

Does swift have a binding to allow outside variables?

I have created what I think is the most basic of applications:

//
//  myTestxApp.swift
//  myTestx

import SwiftUI

var myStng = "Hello\n"
var myArray = ["Hello\n"]

func myTest(){
    myStng.append("Hello\n")
    myArray.append("Hello\n")
    print(myStng,myArray)
}

@main
struct myTestxApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

//
//  ContentView.swift
//  myTestx
import SwiftUI
struct ContentView: View {
    @State var i = myStng
    @State var j = myArray
    
    var body: some View {
        VStack{
        Button( action: myTest ){ Text("Update") }
        List{ Text(i).padding() }
        List{ ForEach(myArray, id: \.self)
            {  i in Text(i).padding()} }
        } //end VStack
    } //end View
} //end ContentView

I declare two app global variables, have an external function where they are updated, and for this example, a view with a calling button to the function and List areas for the updated results tied via @State variables. In my planned app, the update functions would be part of the data processing activity. I want to be able to edit data and have the content view(s) update displayed data when that data item is updated. In this example:

Code compiles and runs, with the console showing two variables being updated, but the view controller is not responding to the state change? Is @State the appropriate binding to use or should I use some other method to cause the content view items to recognize content change?

Any pointers would be greatly appreciated.



Solution 1:[1]

As other fellows suggested, you have to be very careful about using global variables, you should expose them only to the scope needed.

The problem is that you are treating @State var i = myStng thinking this would create a reactive connection between i and myStng, but that is not true. This line is creating a reactive connection between i and a memory address that SwiftUI manages for you, with its first value being what myStng is at that exact moment.

Anyway, I am posting an example of how can you achieve your goal using Environment Object with your provided code.

import SwiftUI

class GlobalVariables: ObservableObject{
    @Published var myStng = "Hello\n"
    @Published var myArray = ["Hello\n"]
}

func myTest(variables: GlobalVariables){
    variables.myStng.append("Hello\n")
    variables.myArray.append("Hello\n")
}

@main
struct myTestxApp: App {
    @StateObject var globalEnvironment = GlobalVariables()
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(globalEnvironment)
        }
    }
}

//
//  ContentView.swift
//  myTestx
//import SwiftUI
struct ContentView: View {
    @EnvironmentObject var global: GlobalVariables
    
    var body: some View {
        VStack{
            Button {
                myTest(variables: global)
            } label: {
                Text("Update")
            }

            List{ Text(global.myStng).padding() }
            List{ ForEach(global.myArray, id: \.self)
            {  i in Text(i).padding()} }
        } //end VStack
    } //end View
} //end ContentView

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 kildos