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))
}
}