swiftswiftuiswiftui-navigationlinkviewmodifier

SwiftUI ViewModifier not working as a NavigationLink


I have the following ViewModifier and it does not work:

import SwiftUI

struct NavLink: ViewModifier {
    let title: String
    
    func body(content: Content) -> some View {
        NavigationLink(destination: content) {
            Text(title)
        }
    }
}

extension View {
    func navLink(title: String) -> some View {
        modifier(NavLink(title: title))
    }
}

If I use that as follows:

import SwiftUI

struct MainScreen: View {
    var body: some View {
        NavigationStack() {
            VStack {
                // This does not work
                NavStackScreen()
                    .navLink(title: "Nav Stack")

                // But this does
                Helper.linked(to: NavStackScreen(), title: "Nav Stack 2")
            }
        }
    }
}

struct Helper {
    static func linked(to destination: some View, title: String) -> some View {
        NavigationLink(destination: destination) {
            Text(title)
        }
    }
}

It creates the link and pushes a new view onto the screen if tapped; however, the contents of the NavStackScreen are not displayed, only an empty screen. Any ideas about what is going on?

Contents of NavStackScreen for reference:

struct NavStackScreen: View {
    var body: some View {
        Text("Nav Stack Screen")
            .font(.title)
            .navigationTitle("Navigation Stack")
    }
}

If I use a static helper function within a helper struct, then it works correctly:

    static func linked(to destination: some View, title: String) -> some View {
        NavigationLink(destination: destination) {
            Text(title)
        }
    }

I added the full code of the MainView for reference and updated the example with more detail for easy reproduction.


Solution

  • The modifier doesn't work because the content argument is not the actual view being modified, but instead is a proxy:

    content is a proxy for the view that will have the modifier represented by Self applied to it.

    Reference.

    This is what a quick debugging over the modifier shows:

    (lldb) po content
    SwiftUI._ViewModifier_Content<SwiftUIApp.NavLink>()
    

    As the proxy is an internal type of SwiftUI, we can't know for sure why NavigationLink doesn't work with it.

    A workaround would be to skip the modifier, and only add the extension over View:

    extension View {
        func navLink(title: String) -> some View {
            NavigationLink(destination: content) {
                Text(title)
            }
        }
    }