Search code examples
iosswiftuiswiftui-foreachswiftui-ontapgesture

SwiftUI View Inside A ForEach Has Wrong ID From OnTapGesture


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)


Solution

  • 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)")
        }
    }