Home > Mobile >  How do you dismiss the detail view in SwiftUI on an iPad/split view layout?
How do you dismiss the detail view in SwiftUI on an iPad/split view layout?

Time:12-26

I'm attempting to dismiss the detail view in a master detail view on an iPad (e.g. if that item is deleted in the master view and needs to blank out the right hand side).


import SwiftUI

struct ContentView: View {
    @State var selection: String?
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink(tag: "item1", selection: $selection) {
                    DetailView(item: "Item 1", selection: $selection)
                } label: {
                    Text("Item 1")
                }

                NavigationLink(tag: "item2", selection: $selection) {
                    DetailView(item: "Item 2", selection: $selection)
                } label: {
                    Text("Item 2")
                }
                
                NavigationLink(tag: "item3", selection: $selection) {
                    DetailView(item: "Item 3", selection: $selection)
                } label: {
                    Text("Item 3")
                }
            }
            
            Text("Blank View")
        }
    }
}

struct DetailView: View {
    let item: String
    @Binding var selection: String?
    
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        VStack {
            Text("Detail for \(item)")
            Button {
                selection = nil
//                dismiss()
            } label: {
                Text("Dismiss")
            }
        }
    }
}

This is an example that demonstrates the issue. When dismiss is pressed it nils the selection which on iPhone dismisses the detail controller. On the iPad it keeps the detail view there, rather than returning to the blank view.

Here is the view before selecting an item. You can see the blank view on the right hand side. Before selecting

After selecting an item the left hand side is selected, and the right hand side shows the appropriate detail view. After selecting

After dismissing the view by nilling the selection (or using the environment dismiss) the selection on the left disappears as it should, but the detail view on the right stays put. This should have disappeared and the blank view should show again. After dismissing

CodePudding user response:

If you're willing to give up the splitView (having the items on that sidebar), you can override the default navigation settings to make it behave as it would on an iPhone (using stacked navigation views), then the dismiss button works just fine. Add this modifier .navigationViewStyle(StackNavigationViewStyle()) to NavigationView:

struct ContentView: View {
    @State var selection: String?
    
    var body: some View {
        NavigationView {
            List {
                NavigationLink(tag: "item1", selection: $selection) {
                    DetailView(item: "Item 1", selection: $selection)
                } label: {
                    Text("Item 1")
                }

                NavigationLink(tag: "item2", selection: $selection) {
                    DetailView(item: "Item 2", selection: $selection)
                } label: {
                    Text("Item 2")
                }
                
                NavigationLink(tag: "item3", selection: $selection) {
                    DetailView(item: "Item 3", selection: $selection)
                } label: {
                    Text("Item 3")
                }
            }
            
            Text("Blank View")
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

enter image description here

enter image description here

CodePudding user response:

I have worked out a solution that works however it requires some extra logic. It needs some sort of check to see if the data is there and if it isn't then conditionally show the main view or a blank view.

Here is the modified DetailView for the example I used in my question. If the selection binding is nil then display the blank view again.

struct DetailView: View {
    let item: String
    @Binding var selection: String?
    
    var body: some View {
        if selection != nil {
            VStack {
                Text("Detail for \(item)")
                Button {
                    selection = nil
                } label: {
                    Text("Dismiss")
                }
            }
        } else {
            Text("Blank View")
        }
    }
}

In the case of an NSManagedObject you can check object.managedObjectContext != nil. I decided to make this an extension to make this easier.

extension View {
    
    @ViewBuilder
    public func blankWithoutContext<BlankView>(_ object: NSManagedObject, blankView: () -> BlankView) -> some View where BlankView: View {
        if object.managedObjectContext != nil {
            self
        } else {
            blankView()
        }
    }
}

Which is used like this:

MyView()
    .blankWithoutContext(object) {
        Text("Blank View")
    }
  • Related