SwiftUI onTapGesture displays new view

Viewed 2360

I am using SwiftUI to make my own Pizza delivery application. I am now stuck cause I do not know how I could display the same image view (including a pizza's description) related to his own index from the array. Instead of the lines {self.images print("ok")}, I would like that each time the user tap on an image from the array it displays the same image with a short text description. Is the description of my issue clear enough? I would be extremely grateful if anyone could help me out with this. Thanks a lot for reading it. This is the code:

HStack {
    ForEach(images, id: \.id) { post in
        ForEach(0..<1) { _ in
            ImageView(postImages: post)
        }
    }
    Spacer()
}
.onTapGesture {
    self.images
    print("ok")
}
.navigationBarTitle("Choose your Pizza")
.padding()
2 Answers

Assuming you have a struct for your Item, you need to conform it to Identifiable:

struct Item: Identifiable {
    var id: String { name } // needed for `Identifiable`

    let name: String
    let imageName: String
    let description: String
}

Then in your main view:

struct ContentView: View {
    let items = [
        Item(name: "item1", imageName: "circle", description: "some description of item 1"),
        Item(name: "item2", imageName: "circle", description: "some description of item 2"),
        Item(name: "item3", imageName: "circle", description: "some description of item 3"),
    ]

    @State var selectedItem: Item? // <- track the selected item

    var body: some View {
        NavigationView {
            HStack {
                ForEach(items, id: \.id) { item in
                    ImageView(imageName: item.imageName)
                        .onTapGesture { 
                            self.selectedItem = item // select the tapped item
                        }
                }
                Spacer()
            }
            .navigationBarTitle("Choose your Pizza")
            .padding()
        }
        .sheet(item: $selectedItem) { item in // show a new sheet if selectedItem is not `nil`
            DetailView(item: item)
        }
    }
}

If you have a custom view for your image:

struct ImageView: View {
    let imageName: String

    var body: some View {
        Image(systemName: imageName)
    }
}

you can create a detailed view for your item (with item description etc):

struct DetailView: View {
    let item: Item

    var body: some View {
        VStack {
            Text(item.name)
            Image(systemName: item.imageName)
            Text(item.description)
        }
    }
}

EDIT

Here is a different approach using the same View to display an image or an image with its description:

struct ContentView: View {
    @State var items = [
        Item(name: "item1", imageName: "circle", description: "some description of item 1"),
        Item(name: "item2", imageName: "circle", description: "some description of item 2"),
        Item(name: "item3", imageName: "circle", description: "some description of item 3"),
    ]

    var body: some View {
        NavigationView {
            HStack {
                ForEach(items, id: \.self) { item in
                    DetailView(item: item)
                }
                Spacer()
            }
            .navigationBarTitle("Choose your Pizza")
            .padding()
        }
    }
}
struct DetailView: View {
    @State var showDescription = false
    let item: Item

    var body: some View {
        VStack {
            Text(item.name)
            Image(systemName: item.imageName)
            if showDescription {
                Text(item.description)
            }
        }
        .onTapGesture {
            self.showDescription.toggle()
        }
    }
}

and conform Item to Hashable:

struct Item: Hashable

or instead of:

ForEach(items, id: \.self)

specify the id explicitly:

ForEach(items, id: \.name)

I also took a stab at it, for what it's worth.

(BTW @pawello2222's answer is also 100% correct. There is more than one way to achieve what you described)

I first created a pizza menu (using placeholder images):

Main pizza list view

Upon selecting an item from the menu, you are taken to the chosen pizza detail:

Chosen pizza detail view

I'm using NavigationView and NavigationLink to navigate between views.

Here's the full source code (for best results run in Swift Playgrounds):


import PlaygroundSupport // For running in Swift Playground only
import SwiftUI

// The main pizza listing page (view)
struct PizzaList: View {
    
    // Array of pizza images
    let pizzaImages = [
        Image(systemName: "circle.fill"),
        Image(systemName: "square.fill"),
        Image(systemName: "triangle.fill")
    ]
    
    // Array of pizza descriptions
    let pizzaDescriptions = [
        "Circular Pizza",
        "Square Pizza",
        "Triangle Pizza"
    ]
    
    var body: some View {
        // Wrap in navigation view to be able to switch between "pages"
        NavigationView {
            VStack {
                ForEach(0 ..< pizzaImages.count) { index in
                    // Wrap in navigation link to provide a link to the next "page"
                    NavigationLink(
                        // This is the contents of the chosen pizza page
                        destination: PizzaDetail(
                            pizzaImage: self.pizzaImages[index],
                            description: self.pizzaDescriptions[index]
                        )
                    ) {
                        // This is an item on the main list
                        PizzaRow(
                            pizzaImage: self.pizzaImages[index],
                            description: self.pizzaDescriptions[index]
                        )
                    }
                }
            }
            .navigationBarTitle("Available Pizzas")
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

// Layout for each pizza item on the list
struct PizzaRow: View {
    
    let pizzaImage: Image
    let description: String
    
    var body: some View {
        HStack {
            Spacer()
            
            pizzaImage
                .resizable()
                .aspectRatio(contentMode: .fit)
                .foregroundColor(.orange)
            Spacer()
            
            Text(self.description)
            Spacer()
        }
        .padding()
    }
}

// Layout for the chosen pizza page
struct PizzaDetail: View {
    
    let pizzaImage: Image
    let description: String
    
    var body: some View {
        VStack {
            Spacer()
            Text(description)
                .font(.title)
            
            Spacer()
            
            pizzaImage
                .resizable()
                .aspectRatio(contentMode: .fit)
            Spacer()
        }
        .padding()
        .navigationBarTitle("Your Pizza Choice")
    }
}

// For Playgrounds only
PlaygroundPage.current.setLiveView(PizzaList())
Related