I'm running into an issue where, eventually, something goes "wonky" on some detail items won't present the DetailView
when they are clicked on.
At first, everything is fine, I can edit (and see results reflected in realtime). I can reorder the list, and I can add Detail
items to the list. However, after a while, I start seeing the default detail view (Text("Select a detail item to view")
) whenever I click on some items. I've not been able to predict where a failure will occur.
In the real app I have a persisted JSON document that never seems to get corrupted. And reflects the edits I perform.
With this sample app, I can pretty reliably trigger the issue by moving a detail item in the list, and then adding a detail item, but, it will eventually fail without those actions.
Am I doing something that's "forbidden"?
(You can create a new Mac app and replace the ContentView.swift with the code below to run this and play with it)
import SwiftUI
struct ContentView: View {
@State var main = Main()
var body: some View {
NavView(main: $main)
.onAppear {
for index in 1..<11 {
main.details.append(
Detail(
title: "Detail \(index)",
description: "Description \(index)"))
}
}
}
}
struct NavView: View {
@Binding var main: Main
var body: some View {
NavigationSplitView {
ListView(main: $main)
} detail: {
Text("Select a detail item to view")
}
}
}
struct ListView: View {
@Binding var main: Main
@State private var addedDetailCount = 0
var body: some View {
List($main.details, editActions: .move) { $detail in
NavigationLink(destination: DetailView(detail: $detail)) {
Text("\(detail.title)")
}
}
.toolbar {
Button(
LocalizedStringKey("Add Detail"), systemImage: "plus"
) {
addedDetailCount += 1
main.details.append(
.init(title: "new Detail \(addedDetailCount)", description: "description"))
}
}
}
}
struct DetailView: View {
@Binding var detail: Detail
var body: some View {
Form {
Text("id: \(detail.id)")
TextField("Title", text: $detail.title)
TextField("Detail", text: $detail.description)
}
.padding()
}
}
struct Main {
var details = [Detail]()
}
struct Detail: Identifiable {
var id = UUID()
var title: String
var description: String
}
#Preview {
ContentView()
}
per Apple Developer support, "Mixing and matching view-destination links with List selection as you have in your code snippet will result in undefined behavior"
I needed to use the selection
parameter on the List
view.
This revised code, seems to work without issue:
import SwiftUI
struct ContentView: View {
@State var main = Main()
var body: some View {
NavView(main: $main)
.onAppear {
for index in 1..<11 {
main.details.append(
Detail(
title: "Detail \(index)",
description: "Description \(index)"))
}
}
}
}
struct NavView: View {
@Binding var main: Main
@State private var selection: UUID?
var body: some View {
NavigationSplitView {
ListView(main: $main, selection: $selection)
} detail: {
if let selection {
DetailView(
detail: $main.details.first(where: { $0.id == selection })!)
} else {
Text("Select a detail item to view")
}
}
}
}
struct ListView: View {
@Binding var main: Main
@State private var addedDetailCount = 0
@Binding var selection: UUID?
var body: some View {
// primary change is here --------------\/
List($main.details, editActions: .move, selection: $selection) {
$detail in
NavigationLink(detail.title, value: detail)
}
.toolbar {
Button(
LocalizedStringKey("Add Detail"), systemImage: "plus"
) {
addedDetailCount += 1
main.details.append(
.init(
title: "new Detail \(addedDetailCount)",
description: "description"))
}
}
}
}
struct DetailView: View {
@Binding var detail: Detail
var body: some View {
Form {
Text("id: \(detail.id)")
TextField("Title", text: $detail.title)
TextField("Detail", text: $detail.description)
}
.padding()
}
}
struct Main {
var details = [Detail]()
}
struct Detail: Identifiable, Hashable {
var id = UUID()
var title: String
var description: String
}
#Preview {
ContentView()
}