SwiftUI example of programmatic navigable views for NavigationView hierarchies

NavTest

SwiftUI example of programmatic navigable views for NavigationView hierarchies

This example takes 3 View structs, A,B and C that want to live in a stack navigation view hierarchy. Sometimes we may want to pop the initial view open to the 3rd View C, while maintaining the nice navigation that the stack navigation view provides, the back buttons and forward navigation. The following code has A,B and C conform to a few protocols and adds them to a containing model called the class NavModel: ObservableObject. Additonally, the A,B and C are ‘wrapped’ by 3 separate View structs to maintain some additional state about what has appeared or disappeared. While not strictly needed to get programmatic navigation working, these wrappers can be helpful in more complex situations where one does not want to instantiate the underlying A,B and C multiple times. One can remove the Wrapped(A|B|C) and push the onAppear/onDisappear elsewhere. The Wrappers have proven to be useful in other situations and so I’ve kept them here.

Two protocols NavView and NavViewModel along with an class NavModel: ObservableObject offer a simple structure for building SwiftUI View hierarchies that utilize the NavigationView along with a .navigationViewStyle(StackNavigationViewStyle()). The NavModel knows about the optional Views for the hierarchy. Generally, we want to lay the Views out in a well known fashion, A before B before C or some such. These may also be optional. Perhaps A, B and C represent some SwiftUI package with reusable views. There may be occassions for configuring them into various hiearchies, like A, C or B, C or just B or just C.

protocol NavView {
    var navigationModel: NavModel { get }
    var viewModel: NavViewModel { get }
}

protocol NavViewModel {
    var name: String { get }
    var selected: Int? { get set }
    var subSelected: Int? { get set }
    var isVisible: Bool { get set }
}

class NavModel : ObservableObject {
    
    var a: A?
    var b: B?
    var c: C?
    ...
}

The NavModel is asked to consume a [NavTo]? upon appearing in order to execute the programmatic navigation async fashion.

.onAppear {
      Task.detached {
           await navModel.navigateOnAppear()
      }
}

The NavViewModel in this example supports a single select key, var selected: Int? per View and a single sub selection, var subSelected: Int? for triggering NavigationLink instances that will match on some tag. One can make more complex selection mechanisms. Often these two work for many situations, but extend as necessary. The per View selected could be used to select a list item or as in this example, a button. The subSelected is really where the navigation between Views takes place. One could have multiple subSelected to match tags as needed: subSelected1, subSelectedX, etc.

NavigationLink(destination: WrappedB(navModel: navModel, toSelect: 1), tag: 1, selection: $vModel.subSelected) {
                Text("Let's go to B")
}

An iOS App, NavTestApp ties things together for this example by setting up 3 Views, A,B and C and programmatically navigating to C upon open, while preserving all the back buttons and hierarchy for stack navigation. The navigation is guided by the [NavTo]:

let navTo = [
            NavTo(view: navModel.a!, subId: 1),
            NavTo(view: navModel.b!, subId: 1)
]

GitHub

View Github