SwiftUI View Inside A ForEach Has Wrong ID From OnTapGesture

536 Views Asked by At

I am doing a ForEach and creating views and adding an on tap gesture

// objects = [MyObject]
                    VStack {
                        ForEach(objects, id: \.id) { obj in
                            MyView(imageURL: obj.imageURL)// <=== This is the RIGHT url for each object
                            .onTapGesture {
                                didTap(obj.objectId) // <== This is the WRONG id for each object
                            }
                        }
                    }

The id is a string based on some properties that make it unique

struct MyObject: Codable, Identifiable {
    var id: String {
        get { return uniquePropertyString }
    }

    var imageURL: String
    var objectId: String

The on tap gesture is returning the ID of an object 2 indexes deeper in the array. I thought by having a unique unchanging id property this wouldn't happen but it still is.

Can you suggest why the onTap gesture is wrong in this ForEach?

UPDATE In my case, it was eventually found that there was an invisible hitbox extending outside of the views. So tapping on a view was actually tapping another view (overlapping hitboxes).

UPDATE There was an Async image in the view which was large and resized smaller. Even though it was clipped and fit inside it, the fullsize of the image (hidden) was acting as a hitbox outside the view

Solution Adding .contentShape(Rectangle()) to the AsyncImage fixed it (answer from: Make SwiftUI tap not extend beyond bounds when clipped)

1

There are 1 best solutions below

7
workingdog support Ukraine On BEST ANSWER

Here is the code I use to test your issue. Let us know if this does not work for you.

See also: Identifiable

struct MyObject: Codable, Identifiable {
//    var id: String {
//        get { return uniquePropertyString }
//    }
    
     let id = UUID().uuidString  // <--- this works
    
//    var uniquePropertyString: String {
//        UUID().uuidString  // <-- for testing
//    }
    
    var imageURL: String
    var objectId: String
}

struct MyView: View {
    @State var imageURL: String
    
    var body: some View {
        Text(imageURL).border(.red)
    }
}

struct ContentView: View {
    let objects = [MyObject(imageURL: "url1", objectId: "obj-1"),
                   MyObject(imageURL: "url2", objectId: "obj-2"),
                   MyObject(imageURL: "url3", objectId: "obj-3")]
    
    var body: some View {
        VStack(spacing: 33) {
            ForEach(objects) { obj in
                //let _ = print("-----> id: \(obj.id)")
                MyView(imageURL: obj.imageURL)
                    .onTapGesture {
                        didTap(obj.objectId)
                    }
            }
        }
    }
    
    func didTap(_ objid: String) {
        print("-----> objid: \(objid)")
    }
}