Navigation, sheet, or overlay?

Hi, I'm working on a SwiftUI Table based app for macOS (primarily, maybe other devices if I can make it work). My data consists of 14 fields, and since the SwiftUI ViewBuilder can only deal with 10 items, I have to show a subset. But I want to make them all available to users. Likewise, it does not appear that SwiftUI tables allow live editing of the data, so I will also need a way to provide that feature.

But this question deals with the display of all the data. I have tried using sheets and Navigation, but I am not happy with the results to date.

I created a column which has an HStack with buttons for viewing and editing. To create a sheet, I used this code:

 TableColumn("View/Edit") { channel in
                 HStack {
                     Button {
                         showingViewSheet.toggle()
                    } label: {
                        Label("", systemImage: "eye")
                    }
                .sheet(isPresented:$showingViewSheet) {
                    VStack {
                        ChannelDetailView(channel: channel)
                        Spacer()
                        Button("Dismiss") {
                            showingViewSheet.toggle()
                        }
                    }
                    .padding()
                }
                    Button {
                        editChannel(channel: channel)
                    } label: {
                        Label("", systemImage: "pencil")
                    }
                }
             }

The result would be acceptable:

But there is a big problem. It is showing the info for the zeroth item in the array. If I click on the eye in the second row, the zeroth info still is displayed. There is also a weird artifact that on dismissal, the sheet vanishes, then reappears, then finally vanishes.

So, the Navigation code looks like this:

var body: some View {
        NavigationView {
            Table(channels, selection: $selection, sortOrder: $sortOrder) {
.
.
.
                TableColumn("View/Edit") { channel in
                     HStack {
                         NavigationLink(destination: ChannelDetailView(channel: channel)) {
                             Label("", systemImage: "eye")
                         }
.
.
.

Initially the result of this puzzled me:

But after some thought, I realized that NavigationView was stuffing my entire table into a sidebar. When I resized the sidebar, I got something like what I want:

This at least displayed the detail correctly. But it is not what I want. The detail is not reflective of the document in total, so should not have the title bar over it.

I' thinking overlay or ZStack, but I thought I would ask to see if I am doing something wrong with the sheet, or if there is a way to make Navigation work the way I want to, or if there is some other best practice I am missing.

Many thanks!

Don Carlile

Answered by decarlile in 709416022

I figured out my own solution. Before I share that for the benefit of anyone else who is having this problem, I want to explain why a sheet will not work in this situation, at least not the way I have implemented it.

The key to understanding is to note that the table is actually a loop, looping over the array of channels. When the showingViewSheet State variable is toggled, that kicks off a redraw of the view. The first item in the array is then displayed as a sheet, as it is the first item to be looped. The Dismiss button toggles that state variable again, but it does not stop the redraw, so the loop continues through the array, resulting in the flashing of the other items that I observed (see my comment).

I don’t think I can pull that sheet code out of the inferred loop, so I decided to go with a ZStack instead.Here is the relevant code:

    @State private var selectedChannel = blankChannels[0]
    var body: some View {
        ZStack {
            Table(channels, selection: $selection, sortOrder: $sortOrder) {
.
.
.
                TableColumn("View/Edit") { channel in
                     HStack {
                         Button {
                             selectedChannel = channel
                             showingView.toggle()
                         } label: {
                            Label("", systemImage: "eye")
                        }
.
.
.
                   }
                 }
            }
            if(showingView)
            {
                Color.white.opacity(1.0)
                VStack {
                    ChannelDetailView(channel: selectedChannel)
                    Spacer()
                    Button("Dismiss") {
                        showingView.toggle()
                        selectedChannel = blankChannels[0]
                    }
                }
                .padding()
            }
        }
    }

This worked, showing the info for the channel in which the eye was clicked:

It's not completely optimal, but it will do for this application.

Hope this helps someone else.

Don Carlile

I looked more carefully at the sheet dismissal. I found that the the reappearance of the sheet after dismissal showed the info for the second row. In fact, when I added another row, I got two reappearances, so the info for all the rows appears quickly. I have no idea why I am getting an iteration over teh entire table rather than the information for the one row which I clicked.

Accepted Answer

I figured out my own solution. Before I share that for the benefit of anyone else who is having this problem, I want to explain why a sheet will not work in this situation, at least not the way I have implemented it.

The key to understanding is to note that the table is actually a loop, looping over the array of channels. When the showingViewSheet State variable is toggled, that kicks off a redraw of the view. The first item in the array is then displayed as a sheet, as it is the first item to be looped. The Dismiss button toggles that state variable again, but it does not stop the redraw, so the loop continues through the array, resulting in the flashing of the other items that I observed (see my comment).

I don’t think I can pull that sheet code out of the inferred loop, so I decided to go with a ZStack instead.Here is the relevant code:

    @State private var selectedChannel = blankChannels[0]
    var body: some View {
        ZStack {
            Table(channels, selection: $selection, sortOrder: $sortOrder) {
.
.
.
                TableColumn("View/Edit") { channel in
                     HStack {
                         Button {
                             selectedChannel = channel
                             showingView.toggle()
                         } label: {
                            Label("", systemImage: "eye")
                        }
.
.
.
                   }
                 }
            }
            if(showingView)
            {
                Color.white.opacity(1.0)
                VStack {
                    ChannelDetailView(channel: selectedChannel)
                    Spacer()
                    Button("Dismiss") {
                        showingView.toggle()
                        selectedChannel = blankChannels[0]
                    }
                }
                .padding()
            }
        }
    }

This worked, showing the info for the channel in which the eye was clicked:

It's not completely optimal, but it will do for this application.

Hope this helps someone else.

Don Carlile

Navigation, sheet, or overlay?
 
 
Q