2

I am trying to reproduce the behavior of a UISplitViewController in SwiftUI, specifically on an iPad. This SwiftUI code does not behave as I'd expect:

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Master")
            }.navigationBarItems(trailing: NavigationLink(destination: DetailView()) {
                Image(systemName: "plus")
            }).navigationBarTitle("Master List")
            Text("")
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail")
    }
}

On an iPhone device, it works as one expects. Tapping the plus button in the navigation bar pushes a DetailView on to the navigation stack and all seems fine.

On iPad, instead the plus pushes the detail view onto a navigation stack in the master view. It does this even if I explicitly tell it that the link should target the detail using isDetailLink(true).

Bug or am I going about this in the wrong way?

(This is leaving aside a secondary problem of how to avoid evaluating the destination View until it is tapped. This is desirable when the destination view takes an argument (an empty model, for example) and creating that model has side effects (inserting an object into a managed object context, say). This blog explains the situation pretty well.)

Steve Madsen
  • 13,465
  • 4
  • 49
  • 67

2 Answers2

4

Note that you will see the same problem on a large iPhone, when in landscape orientation (https://stackoverflow.com/a/57345540/7786555)

The isDetailLink(true) should have work (in my opinion). It is probably a bug affecting the navigation bar. I have seen many weird behaviors with it. It seems whatever you put in the navigation bar, loses some kind of context.

The workaround I found so far, is using the action of your navigation bar to trigger something in the "main" view. In this case, The NavigationLink is now located in the main view (although invisible). Note that I put it inside .background() so it does not affect the layout of other views.

struct ContentView: View {
    @State private var push = false

    var body: some View {
        NavigationView {

            VStack {
                Text("Master")
                    .background(NavigationLink(destination: DetailView(), isActive: $push) { EmptyView() })
            }.navigationBarItems(trailing:
                Button(action: { self.push = true }, label: {
                    Image(systemName: "plus")
                })
            ).navigationBarTitle("Master List")

            Text("")
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail")
    }
}

Finally, please note that there is a bug in the NavigationLink that will also affect you. Fortunately, there is a workaround for it too: SwiftUI: NavigationDestinationLink deprecated

kontiki
  • 37,663
  • 13
  • 111
  • 125
  • This works, but it feels hacky. I agree that it's probably a bug. I filed an issue (FB7201467); will see what Apple has to say about it. – Steve Madsen Sep 04 '19 at 01:45
0

The NavigationLink needs to be inside a List, try this:

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: DetailView()){
                    Text("Show Detail")
                }
            }.navigationBarTitle("Master")
        }
    }
}

struct DetailView: View {
    var body: some View {
        Text("Detail Label").navigationBarTitle("Detail")
    }
}

Screenshot of sample code

malhal
  • 26,330
  • 7
  • 115
  • 133
  • I explicitly don't want to put the "add new thing" control in the list, though. Convention on iOS for years has been that there is a + button in the navigation bar to add a new thing. It's fine if this isn't possible with SwiftUI right now, but it's a shortcoming that I hope they address. – Steve Madsen Sep 21 '19 at 14:47
  • Ah ok that wasn't clear. In that case tapping the add nav bar button should present a modal view controller not show detail, or if the item is just a text field you can use an alert, e.g. if it was a folder title, lastly you could add the new item to the list and then programatically select it, then if they go back from the unfinished item or cancel you can delete it again (same as what happens to a new note in Notes which is left empty when going back). – malhal Sep 21 '19 at 16:28