Restoring a NavigationStack path (NavigationPath) from a codable representation, crashes the app with the following error:
Failed to decode item in navigation path at index 0. Perhaps the navigationDestination declarations have changed since the path was encoded?
Fatal error: throw through?
Note that the error is thrown when the view appears, not when the NavigationPath(representation) is initialized.
In this example, navigation declarations have not changed and restoration should succeed. However, even there was an error restoring the path, this should not be a fatal error. It should be handled gracefully. Consider a new app version being distributed where the destinations did changed. The stack restoration should fail and navigate to the root, but not crash.
Problem occurs both in iOS 16 beta 3 and macOS 13 beta 3.
struct Movie: Identifiable, Hashable, Codable {
let id: Int
let name: String
let year: Int
}
struct TVShow: Identifiable, Hashable, Codable {
let id: Int
let name: String
let episodes: Int
}
class Model: ObservableObject {
let movies = [
Movie(id: 1, name: "Blade Runner", year: 1982),
Movie(id: 2, name: "Back to the Future", year: 1985),
Movie(id: 3, name: "Mission Impossible", year: 1996),
]
let shows = [
TVShow(id: 1, name: "Cheers", episodes: 275),
TVShow(id: 2, name: "Taxi", episodes: 114)
]
@Published var path: NavigationPath
init() {
if let data = UserDefaults.standard.object(forKey: "path") as? Data {
do {
let representation = try JSONDecoder().decode(NavigationPath.CodableRepresentation.self, from: data)
self.path = NavigationPath(representation)
} catch {
print("Unable to decode path \(error)")
self.path = NavigationPath()
}
} else {
self.path = NavigationPath()
}
}
func save() {
guard let representation = path.codable else { return }
do {
let encoder = JSONEncoder()
let data = try encoder.encode(representation)
UserDefaults.standard.set(data, forKey: "path")
} catch {
print("Unable to encode path \(error)")
}
}
}
struct ContentView: View {
@Environment(\.scenePhase) private var scenePhase
@StateObject var model = Model()
var body: some View {
NavigationStack(path: $model.path)
{
List {
Section("Movies") {
ForEach(model.movies) { movie in
NavigationLink(movie.name, value: movie)
}
}
Section("TV Shows") {
ForEach(model.shows) { show in
NavigationLink(show.name, value: show)
}
}
}
.navigationDestination(for: Movie.self, destination: { MovieView(movie: $0) })
.navigationDestination(for: TVShow.self, destination: { TVShowView(show: $0) })
}
.onChange(of: scenePhase) { phase in
if phase == .background {
model.save()
}
}
}
}
struct MovieView: View {
let movie: Movie
var body: some View {
Form {
LabeledContent("Title", value: movie.name)
LabeledContent("Year" , value: "\(movie.year)")
}
}
}
struct TVShowView: View {
let show: TVShow
var body: some View {
Form {
LabeledContent("Title", value: show.name)
LabeledContent("Episodes" , value: "\(show.episodes)")
}
}
}