SwiftUI View Not Updating to Environment Object

3.3k Views Asked by At

I am having an issue getting the ContentBodyView to update properly when a button is pressed in the MenuView.

When I print out the @Published currentPage var, it changes on button press, but doesn't appear to get carried across into ContentBodyView. Am I missing something?

I wrote the code based off of this tutorial: https://blckbirds.com/post/how-to-navigate-between-views-in-swiftui-by-using-an-environmentobject/

I've tried doing an .onReceive event on ContentBodyView, but the view still did not change.

App.swift:

@main
struct App: App {
    
    @StateObject var viewRouter = ViewRouter()
    
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(viewRouter)
        }
    }
}

ContentView:

struct ContentView: View {
    // MARK: - PROPERTIES
    
    // for future use...
    @State var width = UIScreen.main.bounds.width - 90
    // to hide view...
    @State var x = -UIScreen.main.bounds.width + 90
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    // MARK: - BODY
    var body: some View {

            
        VStack {
            ZStack(alignment: Alignment(horizontal: .leading, vertical: .top)) {

                ContentBodyView(x: $x)
                    .environmentObject(ViewRouter())
                
                MenuView(x: $x)
                    .shadow(color: Color.black.opacity(x != 0 ? 0.1 : 0), radius: 5, x: 5, y: 0)
                    .offset(x: x)
                    .background(Color.black.opacity(x == 0 ? 0.5 : 0).ignoresSafeArea(.all, edges: .vertical).onTapGesture {
                        // hiding the view when back is pressed...
                        withAnimation {
                            x = -width
                        }
                    }) //: background
                    .environmentObject(ViewRouter())
            } //: ZSTACK
            
            // adding gesture or drag feature...
            .gesture(DragGesture().onChanged({ (value) in
                withAnimation {
                    if value.translation.width > 0 {
                        // disabling over drag...
                        if x < 0 {
                            x = -width + value.translation.width
                        }
                    } else {
                        if x != -width {
                            x = value.translation.width
                        }
                    }
                }
            }).onEnded({ (value) in
                withAnimation {
                    // checking if half the value of menu is dragged means setting x to 0...
                    if -x < width / 2 {
                        x = 0
                    } else {
                        x = -width
                    }
                }
            })) //: GESTURE 
        } //: VSTACK
    }
}

MenuView:

struct MenuView: View {
    // MARK: - PROPERTIES
    
    var edges = UIApplication.shared.windows.first?.safeAreaInsets
    
    // for future use...
    @State var width = UIScreen.main.bounds.width - 90
    // to hide view...
    @Binding var x: CGFloat
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    // MARK: - BODY
    
    var body: some View {
        HStack(spacing: 0) {
            VStack(alignment: .leading) {
                
                HStack{
                    
                    Button(action: {
                        withAnimation {
                            x = -width
                        }
                    }) {
                        Image(systemName: "xmark")
                            .resizable()
                            .frame(width: 18, height: 18)
                            .padding()
                            .padding(.top, 25)
                            .foregroundColor(Color.black)
                    }
                    
                    Spacer()
                }
                
                ForEach(menuData) { item in
                    Button(action: {
                        withAnimation {
                            if (item.router == "shareables") {
                                viewRouter.currentPage = .shareables
                            } else {
                                viewRouter.currentPage = .home
                            }
                            x = -width
                        }
                    }) {
                        Text("\(item.label)")
                    } //: BUTTON
                } //: FOREACH
                
            } //: VSTACK
            .padding(.horizontal,20)
            // since vertical edges are ignored....
            .padding(.top,edges!.top == 0 ? 15 : edges?.top)
            .padding(.bottom,edges!.bottom == 0 ? 15 : edges?.bottom)
            // default width...
            .frame(width: UIScreen.main.bounds.width - 90)
            .background(Color.white)
            .ignoresSafeArea(.all, edges: .vertical)
            
            Spacer(minLength: 0)
        } //: HSTACK
    }
}

ContentBodyView:

struct ContentBodyView: View {
    // MARK: - PROPERTIES
    
    @EnvironmentObject var viewRouter: ViewRouter
    
    @Binding var x : CGFloat
    
    // MARK: - BODY
    var body: some View{
        
        VStack {
            switch viewRouter.currentPage {
                case .home:
                    NavigationBarView(x: $x, title: "Home")
                    Spacer()
                    HomeView()
                        .transition(.scale)
                case .shareables:
                    NavigationBarView(x: $x, title: "Shareables")
                    Spacer()
                    ShareablesView()
                        .transition(.scale)
            } //: SWITCH
           
        } //: VSTACK
        // for drag gesture...
        .contentShape(Rectangle())
        .background(Color.white)

    }
}

ViewRouter:

final class ViewRouter: ObservableObject {
    @Published var currentPage: Page = .home
}

enum Page {
    case home
    case shareables
}
1

There are 1 best solutions below

2
On BEST ANSWER

Try using the same ViewRouter instance in all views.

The following code creates new instances of ViewRouter:

ContentBodyView(x: $x)
                .environmentObject(ViewRouter())

MenuView(x: $x)
                ...
                .environmentObject(ViewRouter())

Replace them with:

@EnvironmentObject var viewRouter: ViewRouter

...

.environmentObject(viewRouter)

But in reality you usually need to inject .environmentObject only once per environment, so all these calls may be unnecessary.

Injecting the ViewRouter once in ContentView should be enough (if you're not using sheet etc):

@StateObject var viewRouter = ViewRouter()

...

ContentView().environmentObject(viewRouter)