Displaying an editing hierarchy in macOS

The SwiftUI Navigation structures work in ways that are not intuitive to me. For example, I am trying to display a set of data that represents rankings contained in a balloting system that I have created. The ballots all have candidates that are ranked from highest preference to lowest.

Normally, I try to work backwards in SwiftUI, so I built the ballot editor to take a binding to the ballot itself:

struct BallotEditor: View {
    @Binding var ballot: Election.Ballot
    var maxRank: Int
    
    var body: some View {
        VStack {
            ForEach($ballot.rankings) { $ranking in
                CandidateRankingPicker(maxRanking: maxRank, ranking: $ranking)
            }
        }
    }
}

This is embedded into a view with a list of ballots:

struct BallotsView: View {
    @Binding var document: ElectionDocument
        
    var body: some View {
        List($document.ballots) { $ballot in
            NavigationLink {
                BallotEditor(ballot: $ballot, maxRank: document.election.candidates.count)
                    .padding()
            } label: {
                BallotListElementView(ballot: ballot)
            }
        }
    }
}

This portion works in the editor. When the ballot is selected, the editor populates the selected candidate choices, and the editing works.

However, when I attempt to insert BallotsView into a TabView, the NavigationLink stops working as expected. I didn't think NavigationLink was the proper way to do this, but it had been working.

TabView {
    Tab("Ballots", systemImage: "menucard") {
        BallotsView(document: $document)
    }
    
    Tab {
        CandidateView() 
    } label: {
        Text("Candidates")
    }
    .tabViewStyle(.sidebarAdaptable)
}

This is my third iteration. I have tried using a List with selection, but in that case, I am unable to pass the binding to the detail view. I just don't understand how this works, and I am preparing a version in Cocoa so that I don't have to deal with it anymore.

Hi @mmuszynski ,

It doesn't seem like you're using a NavigationStack, which you would need for the navigation links to work. Can you try with that like:

struct BallotsView: View {
    @Binding var document: ElectionDocument
        
    var body: some View {
NavigationStack {
        List($document.ballots) { $ballot in
            NavigationLink {
                BallotEditor(ballot: $ballot, maxRank: document.election.candidates.count)
                    .padding()
            } label: {
                BallotListElementView(ballot: ballot)
            }
        }
    }
}
}

Thanks for your reply. I have ended up going a different direction because I don't understand the way the layout is working in the background still (it might be nice to have a session where I can sketch something and ask an engineer how they would achieve it, but that's beyond the scope of this at the moment).

Here's the new form. I think this is much more concise, but I don't know how to pass the selected ballot as a binding in the inspector (see the portion marked with ***, as I have included the type that the Ballot Inspector requires, but not the variable because I don't know what goes there):

enum Section: String, Identifiable, CaseIterable {
        case ballots
        case candidates
        
        var id: Section { self }
        
        var systemImageName: String {
            switch self {
            case .ballots:
                return "list.number"
            case .candidates:
                return "person.3.sequence.fill"
            }
        }
    }
    
    var body: some View {
        TabView(selection: $selectedSection) {
            ForEach(Section.allCases) { section in
                Tab(section.rawValue.capitalized, systemImage: section.systemImageName, value: section) {
                    switch section {
                    case .ballots:
                        BallotListView(document: $document,
                                       selectedBallotID: $selectedBallotID)
                            .listStyle(.sidebar)
                            .inspector(isPresented: .constant(true)) {
                                BallotEditor(ballot: ***BINDING<Election.Ballot>***, maxRank: document.election.candidates.count)
                            }
                    case .candidates:
                        Text("Candidates")
                    }
                }
            }
        }
        .tabViewStyle(.sidebarAdaptable)
        
    }
Displaying an editing hierarchy in macOS
 
 
Q