I am new to Swift and SwiftUI and working on an app that requires passing nested bindings through various views. I’m encountering an issue where a NavigationLink in a subview passing a binding to a child detail view causes the app to completely freeze both when tested on a real device and simulator. However, it does not freeze within the SwiftUI preview canvas.
I created a thinned down project to test this and the issue persists. I also tried presenting the child detail view via a sheet but the binding does not update within the sheet view.
Can anyone see if there is anything obviously wrong that is causing the issue?
The NavigationLink in the ParentDetail view causes the freeze. Here is the sample code:
Basic Models:
import SwiftUI
class ParentStore: ObservableObject {
@Published var parents = [ParentObject.parentExample]
func binding(for parentID: UUID) -> Binding<ParentObject> {
Binding {
guard let index = self.parents.firstIndex(where: { $0.id == parentID }) else {
fatalError()
}
return self.parents[index]
} set: { updatedParent in
guard let index = self.parents.firstIndex(where: { $0.id == parentID}) else {
fatalError()
}
return self.parents[index] = updatedParent
}
}
}
struct ParentObject: Identifiable {
var id = UUID()
var name: String
var children: [Child]
static let parentExample = ParentObject(name: "Matt", children: [.sasha, .brody])
static let emptyParent = ParentObject(name: "Empty", children: [])
}
struct Child: Identifiable {
var id = UUID()
var name: String
var grandkids: [Grandkid]
static let sasha = Child(name: "Sasha", grandkids: [.peter, .meagan])
static let brody = Child(name: "Brody", grandkids: [.michelle])
}
struct Grandkid: Identifiable {
var id = UUID()
var name: String
static let peter = Grandkid(name: "Peter")
static let meagan = Grandkid(name: "Meagan")
static let michelle = Grandkid(name: "Michelle")
}
MainView:
struct ContentView: View {
@StateObject var parentStore = ParentStore()
var body: some View {
TabView {
ParentList()
.environmentObject(parentStore)
.tabItem {
Label("List", systemImage: "list.bullet")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ParentList:
struct ParentList: View {
@EnvironmentObject var parentStore: ParentStore
var body: some View {
NavigationStack {
List {
ForEach($parentStore.parents) { $parent in
NavigationLink(parent.name, value: parent.id)
}
}
.navigationDestination(for: UUID.self) { parentID in
ParentDetail(parent: parentStore.binding(for: parentID))
}
}
}
}
struct ParentList_Previews: PreviewProvider {
static var previews: some View {
ParentList()
.environmentObject(ParentStore())
}
}
ParentDetailView:
struct ParentDetail: View {
@Binding var parent: ParentObject
@State private var child: Binding<Child>?
var body: some View {
List {
Section("Parent") {
Text(parent.name)
}
Section("Children") {
ForEach($parent.children) { $child in
// This navigation link causes the freeze
NavigationLink(child.name) {
ChildDetail(child: $child)
}
// Testing sheet presentation...
Button(child.name) {
self.child = $child
}
}
}
}
.sheet(item: $child) { $child in
ChildDetail(child: $child)
}
}
}
struct ParentDetail_Previews: PreviewProvider {
static var previews: some View {
ParentDetail(parent: .constant(.parentExample))
}
}
Child Detail View:
struct ChildDetail: View {
@Binding var child: Child
var body: some View {
VStack {
TextField("Name", text: $child.name)
ForEach(child.grandkids) { grandkid in
Text(grandkid.name)
}
}
}
}
struct ChildDetail_Previews: PreviewProvider {
static var previews: some View {
ChildDetail(child: .constant(.sasha))
}
}
2
Answers
You need to replace this:
With the same value/destination new API as you used in the parent because I don’t think you can use it along with the old View based version, e.g.
Better make this edit too:
I would also recommend re-working your data model to have a
Person
struct instead of Parent and Child which are essentially the same thing. Then you can store the child ID to ID relations in an array. We have to organise things differently when using value types for data instead of objects.This is an ongoing problem that I am also facing. I am new to posting so my apologies if I’ve left out something important.
In the below code, there is a NavigationStack in my root view and all other child views contain NavigationLinks. I found I could not use the NavigationLink(value:destination:) form with a .navigationDestination since freezing would occur with a Binding as well. I have reduced this code to the minimum to demonstrate the freezing that occurs only with a Binding within my destination view of the NavigationLink.
Here is my common code (only relevant fields included):
Here is the code using a Binding that causes indefinite freezing (and a recursive loop but no errors):
}
The next code works, with the only change being not using a Binding for the patient:
}
So the problem seems to be with NavigationLink and a Binding within the destination view. It did not happen with NavigationView but surfaced only when using the combination of a NavigationStack and NavigationLink.