BLACK FRIDAY SALE: Save big on all my Swift books and bundles! >>

Make Custom SwiftUI Views with ObservedObjects compatible with NavigationLink iOS 16

Forums > SwiftUI

In the learning content for iOS 16 and SwiftUI, it is shown that

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List(1..<50) { i in
                NavigationLink(value: i) {
                    Label("Row \(i)", systemImage: "\(i).circle")
                }
            }
            .navigationDestination(for: Int.self) { i in
                Text("Detail \(i)")
            }
            .navigationTitle("Navigation")
        }
    }
}

This infers the use of a simple Hashable or Equatable model, as inferred, since this example uses native types.

In my case, something I had developed for iOS 15, no longer works when I migrate my view hierarchy to NavigationStack. Specificially, as shown below (this code is now broken). How can I adapt my code to work with the new NavigationStack & NavigationLink paradigms?

I don't see how I can easily make my SwiftUI views, which require use of ObservedObjects (could also be derived from StateObjects, but that's neither here nor there for the question), adapt to the new NavigationLink usage specified in iOS 16.

This is my minimally producible example:

struct ContentView: View {

    var body: some View {
        NavigationStack {
            ProfileView()

            // there is other logic for other root views, but
            // this problem can just focus on ProfileView in NavigationStack
        }
    }

}

struct ProfileView: View {

    @ObservedObject var progressViewModel = ProgressViewModel()

    @ObservedObject var challengesViewModel = ChallengesViewModel()

    @ObservedObject var subscriptionViewModel = SubscriptionViewModel() 

    var body: some View {
        VStack {

            // Below is the code that is broken, and I'm not sure how to adapt it 
            // to the new iOS 16 navigation patterns since I am not reusing the same view
            // as in the above example
            NavigationLink(destination: ProgressView(progressViewModel: self.progressViewModel)) {
                Text("User progress nav link")
            }

            NavigationLink(destination: ChallengesView(challengesViewModel: self.challengesViewModel)) {
                Text("Challenges nav link")
            }

            NavigationLink(destination: SubscriptionView(subscriptionViewModel: self.subscriptionViewModel)) {
                Text("Subscription nav link")
            }
        }
    }

}

struct ProgressView: View {
    @ObservedObject var progressViewModel: ProgressViewModel

    var body: some View {
        VStack {
            Text(progressViewModel.overallUserProgressString)
        }
    }
}

struct ChallengesView: View {
    @ObservedObject var challengesViewModel: ChallengesViewModel

    var body: some View {
        VStack {
            Text(challengesViewModel.numberOfChallengesPerformedString)
        }
    }
}

struct SubscriptionView: View {
    @ObservedObject var subscriptionViewModel: SubscriptionViewModel

    var body: some View {
        VStack {
            Text(subscriptionViewModel.localSubscriptionPriceString)
        }
    }
}

   

I believe I have an initial answer that works - but certainly not sure if its a best practice. Definitely open to opinions and improvement suggestions! Thank you for viewing.

https://stackoverflow.com/questions/73696898/make-custom-swiftui-views-with-observedobjects-compatible-with-navigationlink-io

enum CustomNavTypes: String, Hashable {
    case viewA = "View A"
    case viewB = "View B"
    case viewC = "View C"
}

struct CustomView: View {
    @State var navigationViewStack: [CustomNavTypes] = []
    var body: some View {
        NavigationStack {
            VStack {
                Button(action: {
                    navigationViewStack.append(.viewA)
                }) {
                    Text("View A")
                }
                Button(action: {
                    navigationViewStack.append(.viewB)
                }) {
                    Text("View B")
                }
                Button(action: {
                    navigationViewStack.append(.viewC)
                }) {
                    Text("View C")
                }
            }
            .navigationDestination(for: ProfileNavTypes.self) { value in
                switch value {
                    case .viewA: ViewA() // can still pass in view models objects if needed
                    case .viewB: ViewB()
                    case .viewC: ViewC()
                }
            }
        }
    }
}

   

FYI, when Apple identifies a feature as "deprecated", that means they intend to eliminate it in some unspecified future version of the OS. Deprecated features generally continue to work for at least an additional year, sometimes two or three. The Apple documentation for NavigationView says it is supported in iOS 16 and MacOS 13. So you can take your time learning NavigationStack without any immediate imperative to migrate existing projects.

1      

Hacking with Swift is sponsored by RevenueCat

SPONSORED In-app subscriptions are a pain to implement, hard to test, and full of edge cases. RevenueCat makes it straightforward and reliable so you can get back to building your app. Oh, and it's free if your app makes less than $10k/mo.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.