2

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.

Cristik
  • 30,989
  • 25
  • 91
  • 127
AD Progress
  • 4,190
  • 1
  • 14
  • 33
  • Well, that's an interesting case... theoretically (only theoretically) in linear procedural programming it should be worked, but... I think in our, SwiftUI, case the rendering engine just think that your modifier replaces one view with another (because this is possible) and just removes first one and, as content in modifier is really not a view, but just a reference-wrapper around real view (specifically for such cases), it detects replacement and shows EmptyView. – Asperi Jun 12 '22 at 17:23
  • How would you even trigger it? This modifier could only be placed on the destination view itself... – Yrb Jun 12 '22 at 21:13
  • To address some of the questions: I am not trying to navigate from a view to itself, rather convert the view into a navLink using a modifier and only display a text label which when clicked will push the view to the screen. – AD Progress Jun 13 '22 at 09:28
  • By the way I used a static helper method which works I added it to the question – AD Progress Jun 13 '22 at 09:30
  • I tried as you suggested @Cristik and that resulted in the same empty view. As I understood viewModifiers return a new view which in this instance should be a NavigationLink with a title passed in and the content should be the destination. – AD Progress Jun 14 '22 at 12:40

1 Answers1

1

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)
        }
    }
}
Cristik
  • 30,989
  • 25
  • 91
  • 127