Search code examples
swiftuiuikit

Pass EnvironmentObject array value to children in iOS 13


I have a menu in an app that mixes a couple of UIKit and Swift. I need to access and update the menu state from different SwiftUI views that are different children from a parent view in UIKit. I am able to do it by using a EnvironmentObject, but I am struggling to pass it again a single value in a ForEach loop.

Here is the EnvironmentObject I created:

class LateralMenuStatus: ObservableObject {
    @Published var status: [FilterButtonStatus] = [.off, .off, .off, .off, .off]
}

Then I pass this and iterate over it in a ForEach loop for passing it again to a child component:

Parent: UIKit View --> Child 1: SwiftUI View (I pass the whole status array) --> Children of Child 1: ForEach (pass a single value for each component)

Which is the proper way of passing a single value and modify inside the SwiftUI? I read that recent iOS 14 introduced a couple of cool updates like AppState() that could solve this but I would like to solve it with iOS 13.

Many thanks,


Solution

  • Once you have an object in the environment of your main app you can use an @EnvironmentObject variable to get a read/write reference in any view.

    To pass a single value from this object to a second child view you can use @Binding. That is read/write so your child views can also modify the environment object without having a reference to the parent directly.

    I built out the code below (note I'm using the new iOS14 App to load in the env object but using the older SceneDelegate will work as well. Pretty sure the below ForEach child view logic below is what you were asking...

    enum FilterButtonStatus {
        case off, on
    }
    
    class LateralMenu: ObservableObject {
        @Published var status: [FilterButtonStatus] = [.off, .off, .off, .off, .off]
    }
    
    @main
    struct StackRepApp: App {
        @ObservedObject var lateralMenu:LateralMenu = LateralMenu()  // declare at App level
        
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environmentObject(lateralMenu) // append to first SwiftUI view environment
            }
        }
    }
    
    struct ContentView: View {
        @EnvironmentObject var lateralMenu:LateralMenu // retrieve env using @EnvironmentObject
        
        var body: some View {
            VStack {
                // loop passing each button status to child view
                ForEach(0 ..< lateralMenu.status.indices.count) { idx in
                    ChildView(status: $lateralMenu.status[idx])
                }
            }
            .onAppear(perform: {
                // example of changing values on env object
                // @envobject is read/write reference
                lateralMenu.status[1] = .on
                lateralMenu.status[3] = .on
            })
        }
    }
    
    struct ChildView: View {
        @Binding var status:FilterButtonStatus  // binding lets child view change value
        
        var body: some View {
            Button(action:{status = status == .off ? .on : .off}) {
                Text("BUTTON")
                    .foregroundColor(.white)
                    .padding(20)
                    .background(status == .on ? Color.green : Color.gray)
            }
        }
    }
    

    enter image description here