1

Foreach on view must be presented with a View to process.

struct Home : View {

  private var numberOfImages = 3
  @State  var isPresented : Bool = false  
  @State  var currentImage : String = ""

  var body: some View {
    VStack {
      TabView {
        ForEach(1..<numberOfImages+1, id: \.self) { num in
    
        Image("someimage")
            .resizable()
            .scaledToFill()
            .onTapGesture() {
                currentImage = "top_00\(num)"
                isPresented.toggle()
            }
        }
    }.fullScreenCover(isPresented: $isPresented, content: {FullScreenModalView(imageName: currentImage) } )
  }
}

I'm trying to display an image in fullScreenCover. My problem is that the first image is empty. Yes, we can solve this defining at the beginning, however, this will complicate the code according to my experiences.

My question is, is it possible to assign a value to currentImage before the onTapGesture processed.

In short, what is the good practice here.

kelalaka
  • 5,064
  • 5
  • 27
  • 44

2 Answers2

3

What you need is to use this modifier to present your full screen modal:

func fullScreenCover<Item, Content>(item: Binding<Item?>, onDismiss: (() -> Void)? = nil, content: @escaping (Item) -> Content) -> some View where Item : Identifiable, Content : View

You pass in a binding to an optional and uses the non optional value to construct a destination:

struct ContentView: View {
    
    private let imageNames = ["globe.americas.fill", "globe.europe.africa.fill", "globe.asia.australia.fill"]
    @State var selectedImage: String?
    
    var body: some View {
        VStack {
            TabView {
                ForEach(imageNames, id: \.self) { imageName in
                    Image(systemName: imageName)
                        .resizable()
                        .scaledToFit()
                        .padding()
                        .onTapGesture() {
                            selectedImage = imageName
                           
                        }
                }
            }
            .tabViewStyle(.page(indexDisplayMode: .automatic))
            .fullScreenCover(item: $selectedImage) { imageName in
                Destination(imageName: imageName)
            }
        }
    }
}

struct Destination: View {
    
    let imageName: String
    
    var body: some View {
        ZStack {
            Color.blue
            Image(
                systemName: imageName
            )
            .resizable()
            .scaledToFit()
            .foregroundColor(.green)
        }
        .edgesIgnoringSafeArea(.all)
    }
}

You will have to make String identifiable for this example to work (not recommended):

extension String: Identifiable {
    public var id: String { self }
}
LuLuGaGa
  • 13,089
  • 6
  • 49
  • 57
2

Building upon @LuLuGaGa’s answer (accept that answer, not this one), instead of making String Identifiable, create a new Identifiable struct to hold the image info. This guarantees each image will have a unique identity, even if they use the same base image name. Also, the ForEach loop now becomes ForEach(imageInfos) since the array contains Identifiable objects.

Use map to turn image name strings into [ImageInfo] by calling the ImageInfo initializer with each name.

This example also puts the displayed image into its own view which can be dismissed with a tap.

import SwiftUI

struct ImageInfo: Identifiable {
    let name: String
    let id = UUID()
}

struct ContentView: View {
    private let imageInfos = [
        "globe.americas.fill", 
        "globe.europe.africa.fill",
        "globe.asia.australia.fill"
    ].map(ImageInfo.init)
   
    @State var selectedImage: ImageInfo?
    
    var body: some View {
        VStack {
            TabView {
                ForEach(imageInfos) { imageInfo in
                    Image(systemName: imageInfo.name)
                        .resizable()
                        .scaledToFit()
                        .padding()
                        .onTapGesture() {
                            selectedImage = imageInfo
                            
                        }
                }
            }
            .tabViewStyle(.page(indexDisplayMode: .automatic))
            .fullScreenCover(item: $selectedImage) { imageInfo in
                ImageDisplay(info: imageInfo)
            }
        }
    }
}

struct ImageDisplay: View {
    let info: ImageInfo
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        ZStack {
            Color.blue
            Image(
                systemName: info.name
            )
            .resizable()
            .scaledToFit()
            .foregroundColor(.green)
        }
        .edgesIgnoringSafeArea(.all)
        .onTapGesture {
            dismiss()
        }
    }
}
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • I was too lazy to do that but you are 100% right - making String identifiable is not the best thing to do to put t mildly. – LuLuGaGa Sep 30 '22 at 14:34
  • Yes, normally I need a unique tag, that I've omitted for simplicity of my problem. – kelalaka Sep 30 '22 at 16:15