Search code examples
iosswiftswiftuiswiftui-navigationlink

Pass a SwiftUI view that has its own arguments as a variable to another view struct


I'll try to outline my case here, I have a NavigationLink I'm wanting to turn into it's own structure so I can re-use it. The Label inside the NavigationLink is the same for all the cases I'm using, just different text and images. I'm trying to make that new struct that contains the NavigationLink have an argument I use for the destination View of the NavigationLink.

I found this link that got me most of the way, but I just don't seem to be able to get it the last mile.

How to pass one SwiftUI View as a variable to another View struct

Here is the re-usable NavigationLink struct I made:

struct MainMenuButtonView<Content: View>: View {
    
    var imageName: String
    var title: String
    var description: String
    var content: Content
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content()
    }
    
    var body: some View {
        VStack {
            NavigationLink(destination: content) {
                Image(imageName)
                    .resizable()
                    .frame(width: 100, height: 100)
                Text(title)
                    .font(.title)
                Text(description)
                    .foregroundColor(Color(UIColor.systemGray2))
                    .multilineTextAlignment(.center)
                    .font(.footnote)
                    .frame(width: 175)
            }
            .buttonStyle(PlainButtonStyle())
        }
    }
}

I currently don't get any errors on that part, but that doesn't mean there isn't something wrong with it.

And here is where I'm using it at, currently, I just shown one, but I'll have more once I get it working.

struct MainMenuView: View {
    
    var body: some View {
        NavigationView {
            MainMenuButtonView(imageName: "List Icon",
                               title: "Lists",
                               description: "Auto generated shopping lists by store",
                               content: TestMainView(testText: "Lists"))
            }
            .buttonStyle(PlainButtonStyle())
            .navigationBarTitle(Text("Main Menu"))
        }
    }
}

When I leave it as above, it tells me that there is an extra argument and that 'Contect' can't be inferred. Xcode does offer a fix, and it ends up looking like this after I do the fix

MainMenuButtonView<Content: View>(imageName: "List Icon",

But then I get an error that it cannot find 'Content' in scope. I know the main difference between my question and the example I linked above is my View I'm passing in also has arguments. I'm not sure if I'm also supposed to put all the arguments in the callout within the <>.

Thank you for any help you can give.


Solution

  • You need to correct the init in the MainMenuButtonView:

    struct MainMenuButtonView<Content: View>: View {
        var imageName: String
        var title: String
        var description: String
        var content: () -> Content // change to closure
    
        // add all parameters in the init
        init(imageName: String, title: String, description: String, @ViewBuilder content: @escaping () -> Content) {
            self.imageName = imageName // assign all the parameters, not only `content`
            self.title = title
            self.description = description
            self.content = content
        }
    
        var body: some View {
            VStack {
                NavigationLink(destination: content()) { // use `content()`
                    Image(imageName)
                        .resizable()
                        .frame(width: 100, height: 100)
                    Text(title)
                        .font(.title)
                    Text(description)
                        .foregroundColor(Color(UIColor.systemGray2))
                        .multilineTextAlignment(.center)
                        .font(.footnote)
                        .frame(width: 175)
                }
                .buttonStyle(PlainButtonStyle())
            }
        }
    }
    

    Also, you need to pass a closure to the content parameter (as you indicated in the init):

    struct MainMenuView: View {
        var body: some View {
            NavigationView {
                MainMenuButtonView(
                    imageName: "List Icon",
                    title: "Lists",
                    description: "Auto generated shopping lists by store",
                    content: { TestMainView(testText: "Lists") } // pass as a closure
                )
                .navigationBarTitle(Text("Main Menu"))
            }
        }
    }