Learn Courses My Dashboard

How to make a view accepts a variable view as its parameter

Hi

My objective is like this:

I created a view called “ShortcutIconView”

struct ShortcutIconView: View {
    
    @State var color : Color
    @State var label : String

    var destination : View // 
    
    var body: some View {
        
        VStack {
            
            NavigationLink {
                destination
            } label: {
                RoundedRectangle(cornerRadius: 10)
                    .fill(color)
                    .frame(width: 60.0, height: 60.0, alignment: .center)
                    .shadow(color: .gray, radius: 8.0, x: 10.0, y: 10.0)
                    .padding(10)
                Text(label)
            }

        }
    }
}

each of the square in the list represents a ShortcutIconView, but I like to indicate where to go if the square icon is clicked

the parent view looks something like this

struct Shortcuts : Identifiable {
        
    var id: UUID = UUID()
    var color : Color
    var label : String
}

struct HScrollListLink: View {
    
    let shortcuts : [Shortcuts] = [ Shortcuts(color: .indigo, label: "Collection"),
                                    Shortcuts(color: .blue, label: "Words"),
                                    Shortcuts(color: .pink, label: "Properties"),
                                    Shortcuts(color: .green, label: "Tags")
    ] // TODO: Add destination view as property
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack {
                
                ForEach(shortcuts) { shortcut in
                    ShortcutIconView(color: shortcut.color, label: shortcut.label)
                }
            }
            
        }
    }
}

As of now I could not figure out how to implement it…
I tried to use AnyView, any View, @ViewBuilder but with no luck yet

Still not solve, but here is my latest code

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    
    let shortcuts : [Shortcuts] = [ Shortcuts(color: .indigo, label: "Collection", messageView: Text("destination 1")),
                                    Shortcuts(color: .blue, label: "Words", messageView: Text("destination 2")),
                                    Shortcuts(color: .pink, label: "Properties", messageView: Text("destination 3")),
                                    Shortcuts(color: .green, label: "Tags", messageView: Text("destinatino 4"))
    ]
    
    

    var body: some View {
        NavigationStack {
            
            HScrollListLink(shortcuts: shortcuts)
            
        }
    }
}

struct Shortcuts : Identifiable {
        
    var id: UUID = UUID()
    var color : Color
    var label : String
    var messageView: any View      // <-------- it went wrong here, I don't know what to put
    
}

struct HScrollListLink: View {
    
    let shortcuts : [Shortcuts]
    
    var body: some View {
                
        ScrollView(.horizontal, showsIndicators: false) {
            HStack {

                ForEach(shortcuts) { shortcut in
                    GenericView(backgroundColor: shortcut.color, title: shortcut.label) {
                        shortcut.messageView // <---- there is error here
                    }
                }
            }

        }
    }
}

struct GenericView<Content>: View where Content: View {
  
    let destination: Content
    @State var color : Color = .black
    @State var label : String = ""

    init( backgroundColor : Color = .black, title : String = "" ,@ViewBuilder messageView: () -> Content) {
        self.destination = messageView()
        self.label = title
        self.color = backgroundColor
    }
    
    var body: some View {
        
        VStack{
            
            NavigationLink {
                destination
                
                
            } label: {
                RoundedRectangle(cornerRadius: 10)
                    .fill(color)
                    .frame(width: 60.0, height: 60.0, alignment: .center)
                    .shadow(color: .gray, radius: 8.0, x: 10.0, y: 10.0)
                    .padding(10)
                Text(label)
            }

            
            
            
        }
        
        
        
    }
    
    
    
}

here I can make now the destination as parameter to a view

but what I am struggling now is to make a structure that accepts a destination view as its property. So that I can just define an array instance of this struct, set each member with icon and destination then I can navigate upon click

Error is: Type ‘any View’ cannot conform to ‘View’

Hey @mj1240, it’s great to see you try out stuff and experiment.

I can recommend the use of AnyView as the type of the message in your Shortcuts model.

This would somehow suppress the errors, but you’ll encounter a more serious problem in the runtime inherent to the risk of using AnyView, more into this later.

Code example of using AnyView:

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    
    let shortcuts : [Shortcuts] = [ Shortcuts(color: .indigo, label: "Collection", messageView: AnyView(Text("destination 1"))),
                                    Shortcuts(color: .blue, label: "Words", messageView: AnyView(Text("destination 2"))),
                                    Shortcuts(color: .pink, label: "Properties", messageView: AnyView(Text("destination 3"))),
                                    Shortcuts(color: .green, label: "Tags", messageView: AnyView(Text("destinatino 4")))
    ]
    
    var body: some View {
        NavigationStack {
            HScrollListLink(shortcuts: shortcuts)
        }
    }
}

struct Shortcuts: Identifiable {
    var id: UUID = UUID()
    var color : Color
    var label : String
    var messageView: AnyView
}

struct HScrollListLink: View {
    let shortcuts: [Shortcuts]
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack {
                ForEach(shortcuts) { shortcut in
                    GenericView(backgroundColor: shortcut.color, title: shortcut.label) {
                        shortcut.messageView
                    }
                }
            }
        }
    }
}

struct GenericView<Content>: View where Content: View {
    let destination: Content
    @State var color : Color = .black
    @State var label : String = ""
    
    init(backgroundColor : Color = .black, title : String = "" ,@ViewBuilder messageView: () -> Content) {
        self.destination = messageView()
        self.label = title
        self.color = backgroundColor
    }
    
    var body: some View {
        VStack{
            NavigationLink {
                destination
            } label: {
                RoundedRectangle(cornerRadius: 10)
                    .fill(color)
                    .frame(width: 60.0, height: 60.0, alignment: .center)
                    .shadow(color: .gray, radius: 8.0, x: 10.0, y: 10.0)
                    .padding(10)
                Text(label)
            }
        }
    }
}

I would recommend to not include a View into your models, instead have a clear separation between the Model and the View.

You actually have created the correct approach of using Views with generic type constraints as a way to address this problem by creating the GenericView.

If you need to read more about the dangers of using AnyView, and to have a better understanding of the possible solutions, you may want to read: How to avoid anyview in SwiftUI by John Sundell.

Hope you solve it anytime soon.

Happy coding!

Thanks inane, It worked! I will check also the link you mentioned above about the danger of using AnyView