How can I mimic the calling signature of NavigationLink

127 views Asked by At

I have code like this:

    List(datalist) { data in
        NavigationLink(destination: View1(data: data).headless()) {          
            Text(data.name)
        }
    }

where headless() is a way to avoid all the default top views of a NavigationLink and its corresponding init:

    extension View {
        
        // https://www.hackingwithswift.com/forums/swiftui/removing-unwanted-and-unknown-whitespace-possibly-a-navigation-bar-above-a-view/7343
        func headless() -> some View {
            return self.navigationBarTitle("")
                .navigationBarHidden(true)
                .navigationBarBackButtonHidden(true)
        }
    }

What I want to do is have a View struct that can be a customized call to NavigationLink that always calls the headless() modifier. I have written this, copying from the declaration of NavigationLink:

struct SimpleNavLink<Destination, Label>: View where Label : View, Destination : View {
    
    private let label: () -> Label
    private let destination: () -> Destination
    
    init(@ViewBuilder destination: @escaping () -> Destination, @ViewBuilder label: @escaping () -> Label) {
        self.label = label
        self.destination = destination
    }
    
    var body: some View  {
        NavigationLink(destination: destination().headless, label: label)
    }
    
}

With that in place I changed the NavigationLink line to this:

SimpleNavLink(destination: View1(data: data)) {

But that gave me the error

Cannot convert value of type 'View1' to expected argument type '() -> Destination'

That was easy enough to solve by just wrapping the destination in { }:

SimpleNavLink(destination: { View1(data: data) } ) {

But WHY? I didn't have to do that for the NavigationLink. When I tried adding @autoclosure to the destination parameter, the compiler said that didn't go with @ViewBuilder

1

There are 1 answers

1
rob mayoff On

You said

I have written this, copying from the declaration of :

I assume you meant “the declaration of NavigationLink”. But your original code uses a NavigationLink.init that is declared like this:

    @available(iOS, introduced: 13.0, deprecated: 100000.0, message: "Pass a closure as the destination")
    @available(macOS, introduced: 10.15, deprecated: 100000.0, message: "Pass a closure as the destination")
    @available(tvOS, introduced: 13.0, deprecated: 100000.0, message: "Pass a closure as the destination")
    @available(watchOS, introduced: 6.0, deprecated: 100000.0, message: "Pass a closure as the destination")
    public init(destination: Destination, @ViewBuilder label: () -> Label)

This version of init takes the Destination by value instead of as a function. It's also going to be deprecated at some point.

So, if you want to mimic the (eventually deprecated) syntax, you need to change your init to take the Destination by value instead of as a function. Note also that NavigationLink does not require @escaping closures, so perhaps you shouldn't either. Thus:

struct SimpleNavLink<Destination: View, Label: View>: View {
    private let label: Label
    private let destination: Destination

    init(
        destination: Destination,
        @ViewBuilder label: () -> Label
    ) {
        self.label = label()
        self.destination = destination
    }

    var body: some View  {
        NavigationLink(
            destination: destination.headless(),
            label: { label }
        )
    }
}