Search code examples
swiftuiswiftui-navigationlink

How would you create an array of views to loop through, to create a navigation link to each one of the views?


I have 8 views for where in a horizontal scroll group, the user would tap the image and go to the corresponding view. I know I could do this manually but using a ForEach loop will save a lot of code, I've done similar things with text as you can see below and I tried to do so with the array of views, but the parameter requirements for a navigation link makes it difficult to refer to the view itself, as it would be ie. [dogs_and_cats but the navigation link wants it to be dogs_and_cats()]. Yet this doesn't work due to the errors: Type 'Any' has no member 'init' in the nav link line and Cannot convert value of type 'Barcelona_Museum_of_Contemporary_Art.Type' to expected element type 'Any.Protocol' for each of the array elements. If you were in my shoes how would you create a array of view objects if that is possible, and loop through them for each nav link?


let museumNamesForLink = [Whitney_Museum_of_American_Art, 
The_Andy_Warhol_Museum, 
Museum_of_Modern_Art, Nakamura_Keith_Haring_Collection, 
Tate_Modern, 
The_Broad_Museum, 
Museum_fu_r_Moderne_Kunst, 
Barcelona_Museum_of_Contemporary_Art]

                     
                    ScrollView(.horizontal, showsIndicators: false) {
                        
                        HStack(alignment: .top, spacing: 0) {
                            
                            ForEach(museumNames.indices) { index in
                                
                                VStack {
                                    NavigationLink(destination: museumNamesForLink[index]()) {
                                            Image(museumNames[index])
                                                .resizable()
                                                .aspectRatio(contentMode: .fit)
                                        
                                    }
                                    Text(museumNames[index])
                                         .bold()
                                         .font(.callout)
                                         .foregroundColor(.white)
                                         .multilineTextAlignment(.center)
                                         .padding(.leading)
                                }
                            }
                        }
                    }

Solution

  • I'd probably approach this with an enum to represent the different museum types and then a @ViewBuilder function with a switch statement that can give you a different View based on which enum is fed to it.

    struct ContentView: View {
        
        enum MuseumTypes: CaseIterable {
            case whitney, warhol, moma
            
            var name : String {
                switch self {
                case .whitney:
                    return "Whitney Museum"
                case .warhol:
                    return "Warhol Musuem"
                case .moma:
                    return "Museum of Modern Art"
                }
            }
        }
        
        @ViewBuilder func museumViewForType(type: MuseumTypes) -> some View {
            switch type {
            case .whitney:
                WhitneyView()
            case .warhol:
                WarholView()
            case .moma:
                MomaView()
            }
        }
        
        var body: some View {
            NavigationView {
                VStack {
                    ForEach(MuseumTypes.allCases, id: \.self) { museum in
                        NavigationLink(destination: museumViewForType(type: museum)) {
                            Text(museum.name)
                        }
                    }
                }
            }
        }
    }
    
    struct WhitneyView : View {
        var body: some View {
            Text("Whitney")
        }
    }
    
    struct WarholView : View {
        var body: some View {
            Text("Warhol")
        }
    }
    
    struct MomaView : View {
        var body: some View {
            Text("MOMA")
        }
    }
    

    An alternate approach is to store all of your views in the array and wrap them with AnyView() so that they're homogeneous types (see https://stackoverflow.com/a/66057289/560942), but I think that's not as clean or as clear as the approach I detailed above). Plus, by using an enum instead of an array, you'll get warnings from the compiler if you forget a case and it's guaranteed you won't miss something and go outside the bounds of your array indexes.