SwiftUI - Force recognition of two changes to an @EnvironmentObject programmatically

So say we have a set of views, which uses an enum and Environment Objects in order to navigate globally, like the following (if copying remember to add .environmentObject(StatusNavigation()) to SceneDelegate):


//
//  ResetNavigation.swift
//  SwiftUI Playground Project
//
//  Created by O'Donnell, Troy(AWF) on 12/30/19.
//  Copyright © 2019 O'Donnell, Troy(AWF). All rights reserved.
//

import SwiftUI

enum StatusViews {
    case firstView
    case secondView
    case thirdView
    case emptyView
}

final class StatusNavigation: ObservableObject {
    
    @Published var statusNavigation: StatusViews = StatusViews.firstView
}

struct UniversalNavigation: View {
    
    @EnvironmentObject var statusNavigation: StatusNavigation
    
    var body: some View {
        if statusNavigation.statusNavigation == .firstView {
            return AnyView(FirstView())
        }
        else if statusNavigation.statusNavigation == .secondView {
            return  AnyView(SecondView())
        }
        else if statusNavigation.statusNavigation == .thirdView {
            return AnyView(ThirdView())
        }
        else if statusNavigation.statusNavigation == .emptyView {
            return AnyView(EmptyView())
        }
        else {
            return AnyView(FirstView())
        }
    }
}

struct FirstView: View {
    
    @EnvironmentObject var statusNavigation: StatusNavigation
    
    var body: some View {
        
        NavigationView {
            
            VStack(spacing: 0) {
                
                Text("First View")
                    .font(.title)
                    .fontWeight(.bold)
                    .foregroundColor(Color.black)
                
                Text("Go to Second View")
                    .foregroundColor(Color.blue)
                    .onTapGesture {self.statusNavigation.statusNavigation = .secondView}
                
                NavigationLink(destination: ChildView(idxView: "First")) {
                    
                    Text("Navigate to Child View")
                }
            }
        }
    }
}

struct SecondView: View {
    
    @EnvironmentObject var statusNavigation: StatusNavigation
    
    var body: some View {
        
        NavigationView {
            
            VStack(spacing: 0) {
                
                Text("Second View")
                    .font(.title)
                    .fontWeight(.bold)
                    .foregroundColor(Color.black)
                
                Text("Go to Third View")
                    .foregroundColor(Color.blue)
                    .onTapGesture {self.statusNavigation.statusNavigation = .thirdView}
                
                NavigationLink(destination: ChildView(idxView: "Second")) {
                    
                    Text("Navigate to Child View")
                }
            }
        }
    }
}

struct ThirdView: View {
    
    @EnvironmentObject var statusNavigation: StatusNavigation
    
    var body: some View {
        
        NavigationView {
            
            VStack(spacing: 0) {
                
                Text("Third View")
                    .font(.title)
                    .fontWeight(.bold)
                    .foregroundColor(Color.black)
                
                Text("Go to First View")
                    .foregroundColor(Color.blue)
                    .onTapGesture {self.statusNavigation.statusNavigation = .firstView}
                
                NavigationLink(destination: ChildView(idxView: "Third")) {
                    
                    Text("Navigate to Child View")
                }
            }
        }
    }
}

struct ChildView: View {
    
    var idxView: String
    @EnvironmentObject var statusNavigation: StatusNavigation
    
    var body: some View {
        
        VStack(spacing: 0) {
            
            Text("\(idxView) Child View")
                .font(.title)
                .fontWeight(.bold)
                .foregroundColor(Color.black)
            
            Text("Go back to First Parent View")
                .foregroundColor(Color.blue)
                .onTapGesture {self.statusNavigation.statusNavigation = .firstView}
            
            Text("Go back to Second Parent View")
                .foregroundColor(Color.blue)
                .onTapGesture {self.statusNavigation.statusNavigation = .secondView}
            
            Text("Go back to Third Parent View")
                .foregroundColor(Color.blue)
                .onTapGesture {self.statusNavigation.statusNavigation = .thirdView}
            
            
        }
    }
}


As it is now, we can rotate between the three main views just fine, but I also want to be able to navigate back from the children (and more importantly, children of children!) in order to have a more dynamic and flexible navigation system. For example, if we added more children views to the already-existing children views, and set self.statusNavigation.statusNavigation to the first, second or third views, we can move back to programmer-defined views more easily. However, I have had one problem with this. Say we are in the first child view. We can navigate to the second and third parent view using Universal Navigation just fine, since we aren't in those places right now, but since the first child view is the child of the first parent view, setting the environment object to .firstView again doesn't change it, and thus doesn't update it. What I've done to fix that is by changing the ChildView slightly, like this:


struct ChildView: View {
    
    var idxView: String
    @EnvironmentObject var statusNavigation: StatusNavigation
    
    var body: some View {
        
        VStack(spacing: 0) {
            
            Text("\(idxView) Child View")
                .font(.title)
                .fontWeight(.bold)
                .foregroundColor(Color.black)
            
            Text("Go back to \(idxView) Parent View")
                .foregroundColor(Color.blue)
                .onTapGesture {self.statusNavigation.statusNavigation = .emptyView; DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) {
                    self.statusNavigation.statusNavigation = self.changeDestination(idx: self.idxView)}}
        }
    }
    
    func changeDestination(idx: String) -> StatusViews {
        
        if idx == "First" {
            return .firstView
        }
        else if idx == "Second" {
            return .secondView
        }
        else if idx == "Third" {
            return .thirdView
        }
        else {
            return .firstView
        }
    }
}

Now while this works, A. It's kinda hacky, with using the DispatchMainQueue, and B. Delays aren't a great solution as it will eat up the phone's memory really quick. My questions is this: Is there some function we can call in between the first and second variable change, to make SwiftUI recognize the first change before it sees the second one?

Replies

using the DispatchMainQueue


I cannot find it in your code ?

The second code replaces the ChildView structure and has the DispatchMainQueue, you can copy that structure and replace it from the top code