'ForEach nested in List - stop producing rows when row count hits particular point

I have a list with a nested ForEach loop that should take the data and stop producing rows once the row counter hits three, but I'm receiving the following error message

Type '()' cannot conform to 'View

Is it possible to cut off the loop once we have three rows displaying? My end goal is once three rows are displayed, to show a "View More" button.

import SwiftUI

struct RecipeListView: View {
    @State var listofRecipes: [RecipeListModel] = RecipeList.recipes
    @State private var recipeCounter = 0
    
    init(){
        UITableView.appearance().backgroundColor = .clear
    }
    
    var body: some View {
        VStack{
            Text("Recipes")
                .padding(.bottom, -20)
                //.padding(.top, 40)
                .font(.title2)
            
            List{
                ForEach(listofRecipes, id: \.id){ recipe in
                recipeCounter += 1
                    if(recipeCounter < 3){
                        
                        HStack{
                            Image(recipe.image)
                                .resizable()
                               .frame (width: 70, height:70)
                                .cornerRadius(15)
                                
                            VStack{
                                Text(recipe.name)
                                    .font(.title2)
                                //temp solution until I can center it
                                    .padding(.trailing, 25)
                              
                                NavigationLink(destination: {RecipeController(name: recipe.name, image: recipe.image)}, label: {
                                   Text("View Recipe")
                                       .padding(5)
                                       .foregroundColor(.black)
                                       .background(Color("completeGreen"))
                                       .frame(maxWidth: .infinity)
                                       .cornerRadius(14)
                                     
                                    })
                                    .padding(4)
                                    
                                }
                            
                            }
                        }
                    }
                }
            }
        }
    }


Solution 1:[1]

Your type cannot conform to view because you can't change the variables in the view. You can declare let, use if or switch, but you can't declare a var or change the value directly - so, recipeCounter += 1 is not allowed.

One solution is to create a temporary list that contains only the first 3 items, then change it to the full list after the user decides to see the rest.

The example below has some explanations of what each part of the code does:

struct Example: View {
    
    // The full list is a let constant
    let fullList = [Recipe("First"),
                    Recipe("Second"),
                    Recipe("Third"),
                    Recipe("Fourth"),
                    Recipe("Fifth")]
    
    // The list is a @State variable: .onAppear will set
    // it to contain the first 3 items, then it'll change
    // to the full list
    @State private var listofRecipes = [Recipe]()
    
    var body: some View {
        VStack{
            Text("Recipes")
                .font(.title2)
                .padding()
            
            List{
                ForEach(listofRecipes, id: \.id){ recipe in
                    Text(recipe.text)
                }
            }
            
            // "Show more" button: will change the list
            // to make it contain all the items
            Button {
                withAnimation {
                    listofRecipes = fullList
                }
            } label: {
                Text("Show more")
            }
        }
        
        // When the view appears, set the list to contain only the
        // first 3 items
        .onAppear {

            // Handle case when the list contains only 3 elements or less
            if fullList.count > 3 {
                listofRecipes = fullList.dropLast(fullList.count - 3)
            }
        }
    }
    
    struct Recipe {
        let id = UUID()
        let text: String
        init(_ text: String) {
            self.text = text
        }
    }
}

Solution 2:[2]

As @HunterLion mentioned, the problem is caused by recipeCounter += 1.

Another way to solve this is to use the list index directly in your condition:

ForEach(listofRecipes.indices) { index in
    if(index < 3) {
       HStack {
          Image(listofRecipes[index].image)
           ....

Solution 3:[3]

Another alternative to what the two others said is to use the .prefix modifier on listofRecipes. This will return a subsequence of your array with the first n elements.

ForEach(listofRecipes.prefix(3)) { recipe in
     HStack {
         ...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 tromgy
Solution 3 Dmp3268