Hi,
I already had a problem with the NavigationSplitView before, and I'm still working on it.
The NavigationSplitView issue on the phone occurs when trying to navigate back from the detail view to the content view; the app automatically navigates straight back to the detail view.
I reproduced the issue in a small program and identified the critical section. However, I do not know why the problem is occurring.
I did the same thing again, but this time with @Query, and there was no problem.
The categories, recipes and single recipe, all loaded by @Query and no side effects.
Requirements for the content:
- A state variable used in the view, e.g.,
isLoading - A task that loads the data from a ModelActor in a ViewModel, setting
isLoadingtotruebeforehand and back tofalseupon completion
Subsequently, it is only possible to exit the detail view after many attempts, because the app immediately navigates back to the detail view automatically upon trying to leave it.
One issue is changing the isLoading status variable from within the task. That in itself isn't the problem; the issue only arises when reading data via a ModelActor.
If the status variable is changed and, for example, the list is modified manually, there is no problem. It only occurs when a ModelActor is used.
I based it on the NavigationCookbook:
This is the view I use for the content section (list):
@State private var vm: RecipeListViewModel = .init()
@State var isLoading: Bool = false
if isLoading {
ProgressView("Loading...")
} else {
List(selection: $selectedRecipe) {
ForEach(vm.recipes) { recipe in
NavigationLink(recipe.name, value: recipe)
}
}
}
.task {
// bug, when used in UI, because of ModelActor
isLoading = true
await vm.list(context, categoryName: selectedCategory.name)
isLoading = false
}
.task {
// works, because manually modifying the list
isLoading = true
await vm.addRecipe(context, categoryName: selectedCategory.name)
isLoading = false
}
inside ViewModel
// bug
func list(_ modelContext: ModelContext, categoryName: String) async {
recipes.removeAll()
let dh: RecipeDataHandler = .init(modelContainer: modelContext.container)
let values = await dh.listByName(categoryName: categoryName)
for v in values {
recipes.append(.init(recipeId: v.recipeId, name: v.name))
}
}
// works
func addRecipe(_ modelContext: ModelContext, categoryName: String) async {
isLoading = true
defer { isLoading = false }
recipes.removeAll()
recipes.append(.init(recipeId: "a", name: "Pambazo"))
}
ModelActor
@ModelActor
actor RecipeDataHandler {
func listByName(categoryName: String) async -> [RecipeData] {
var list: [RecipeData] = []
do {
let fd = FetchDescriptor<Recipe>(predicate: #Predicate { $0.category.name == categoryName }, sortBy: [SortDescriptor(\Recipe.name)])
let data = try modelContext.fetch(fd)
for item in data {
let rec: RecipeData = await .init(recipeId: item.recipeId, name: item.name)
list.append(rec)
}
} catch {
print("error: \(error.localizedDescription)")
}
return list
}
}
Recipe is a @Model and RecipeData just a struct.
In principle, everything should run on the MainActor, but the inclusion of the ModelActor still leads to this strange behavior.
Any idea what the problem might be?
Christian